1 package com.example.android.bluetoothadvertisements; 2 3 import android.app.Notification; 4 import android.app.PendingIntent; 5 import android.app.Service; 6 import android.bluetooth.BluetoothAdapter; 7 import android.bluetooth.BluetoothManager; 8 import android.bluetooth.le.AdvertiseCallback; 9 import android.bluetooth.le.AdvertiseData; 10 import android.bluetooth.le.AdvertiseSettings; 11 import android.bluetooth.le.BluetoothLeAdvertiser; 12 import android.content.Context; 13 import android.content.Intent; 14 import android.os.Handler; 15 import android.os.IBinder; 16 import android.util.Log; 17 import android.widget.Toast; 18 19 import java.util.concurrent.TimeUnit; 20 21 /** 22 * Manages BLE Advertising independent of the main app. 23 * If the app goes off screen (or gets killed completely) advertising can continue because this 24 * Service is maintaining the necessary Callback in memory. 25 */ 26 public class AdvertiserService extends Service { 27 28 private static final String TAG = AdvertiserService.class.getSimpleName(); 29 30 private static final int FOREGROUND_NOTIFICATION_ID = 1; 31 32 /** 33 * A global variable to let AdvertiserFragment check if the Service is running without needing 34 * to start or bind to it. 35 * This is the best practice method as defined here: 36 * https://groups.google.com/forum/#!topic/android-developers/jEvXMWgbgzE 37 */ 38 public static boolean running = false; 39 40 public static final String ADVERTISING_FAILED = 41 "com.example.android.bluetoothadvertisements.advertising_failed"; 42 43 public static final String ADVERTISING_FAILED_EXTRA_CODE = "failureCode"; 44 45 public static final int ADVERTISING_TIMED_OUT = 6; 46 47 private BluetoothLeAdvertiser mBluetoothLeAdvertiser; 48 49 private AdvertiseCallback mAdvertiseCallback; 50 51 private Handler mHandler; 52 53 private Runnable timeoutRunnable; 54 55 /** 56 * Length of time to allow advertising before automatically shutting off. (10 minutes) 57 */ 58 private long TIMEOUT = TimeUnit.MILLISECONDS.convert(10, TimeUnit.MINUTES); 59 60 @Override 61 public void onCreate() { 62 running = true; 63 initialize(); 64 startAdvertising(); 65 setTimeout(); 66 super.onCreate(); 67 } 68 69 @Override 70 public void onDestroy() { 71 /** 72 * Note that onDestroy is not guaranteed to be called quickly or at all. Services exist at 73 * the whim of the system, and onDestroy can be delayed or skipped entirely if memory need 74 * is critical. 75 */ 76 running = false; 77 stopAdvertising(); 78 mHandler.removeCallbacks(timeoutRunnable); 79 stopForeground(true); 80 super.onDestroy(); 81 } 82 83 /** 84 * Required for extending service, but this will be a Started Service only, so no need for 85 * binding. 86 */ 87 @Override 88 public IBinder onBind(Intent intent) { 89 return null; 90 } 91 92 /** 93 * Get references to system Bluetooth objects if we don't have them already. 94 */ 95 private void initialize() { 96 if (mBluetoothLeAdvertiser == null) { 97 BluetoothManager mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 98 if (mBluetoothManager != null) { 99 BluetoothAdapter mBluetoothAdapter = mBluetoothManager.getAdapter(); 100 if (mBluetoothAdapter != null) { 101 mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser(); 102 } else { 103 Toast.makeText(this, getString(R.string.bt_null), Toast.LENGTH_LONG).show(); 104 } 105 } else { 106 Toast.makeText(this, getString(R.string.bt_null), Toast.LENGTH_LONG).show(); 107 } 108 } 109 110 } 111 112 /** 113 * Starts a delayed Runnable that will cause the BLE Advertising to timeout and stop after a 114 * set amount of time. 115 */ 116 private void setTimeout(){ 117 mHandler = new Handler(); 118 timeoutRunnable = new Runnable() { 119 @Override 120 public void run() { 121 Log.d(TAG, "AdvertiserService has reached timeout of "+TIMEOUT+" milliseconds, stopping advertising."); 122 sendFailureIntent(ADVERTISING_TIMED_OUT); 123 stopSelf(); 124 } 125 }; 126 mHandler.postDelayed(timeoutRunnable, TIMEOUT); 127 } 128 129 /** 130 * Starts BLE Advertising. 131 */ 132 private void startAdvertising() { 133 goForeground(); 134 135 Log.d(TAG, "Service: Starting Advertising"); 136 137 if (mAdvertiseCallback == null) { 138 AdvertiseSettings settings = buildAdvertiseSettings(); 139 AdvertiseData data = buildAdvertiseData(); 140 mAdvertiseCallback = new SampleAdvertiseCallback(); 141 142 if (mBluetoothLeAdvertiser != null) { 143 mBluetoothLeAdvertiser.startAdvertising(settings, data, 144 mAdvertiseCallback); 145 } 146 } 147 } 148 149 /** 150 * Move service to the foreground, to avoid execution limits on background processes. 151 * 152 * Callers should call stopForeground(true) when background work is complete. 153 */ 154 private void goForeground() { 155 Intent notificationIntent = new Intent(this, MainActivity.class); 156 PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, 157 notificationIntent, 0); 158 Notification n = new Notification.Builder(this) 159 .setContentTitle("Advertising device via Bluetooth") 160 .setContentText("This device is discoverable to others nearby.") 161 .setSmallIcon(R.drawable.ic_launcher) 162 .setContentIntent(pendingIntent) 163 .build(); 164 startForeground(FOREGROUND_NOTIFICATION_ID, n); 165 } 166 167 /** 168 * Stops BLE Advertising. 169 */ 170 private void stopAdvertising() { 171 Log.d(TAG, "Service: Stopping Advertising"); 172 if (mBluetoothLeAdvertiser != null) { 173 mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback); 174 mAdvertiseCallback = null; 175 } 176 } 177 178 /** 179 * Returns an AdvertiseData object which includes the Service UUID and Device Name. 180 */ 181 private AdvertiseData buildAdvertiseData() { 182 183 /** 184 * Note: There is a strict limit of 31 Bytes on packets sent over BLE Advertisements. 185 * This includes everything put into AdvertiseData including UUIDs, device info, & 186 * arbitrary service or manufacturer data. 187 * Attempting to send packets over this limit will result in a failure with error code 188 * AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE. Catch this error in the 189 * onStartFailure() method of an AdvertiseCallback implementation. 190 */ 191 192 AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder(); 193 dataBuilder.addServiceUuid(Constants.Service_UUID); 194 dataBuilder.setIncludeDeviceName(true); 195 196 /* For example - this will cause advertising to fail (exceeds size limit) */ 197 //String failureData = "asdghkajsghalkxcjhfa;sghtalksjcfhalskfjhasldkjfhdskf"; 198 //dataBuilder.addServiceData(Constants.Service_UUID, failureData.getBytes()); 199 200 return dataBuilder.build(); 201 } 202 203 /** 204 * Returns an AdvertiseSettings object set to use low power (to help preserve battery life) 205 * and disable the built-in timeout since this code uses its own timeout runnable. 206 */ 207 private AdvertiseSettings buildAdvertiseSettings() { 208 AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder(); 209 settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER); 210 settingsBuilder.setTimeout(0); 211 return settingsBuilder.build(); 212 } 213 214 /** 215 * Custom callback after Advertising succeeds or fails to start. Broadcasts the error code 216 * in an Intent to be picked up by AdvertiserFragment and stops this Service. 217 */ 218 private class SampleAdvertiseCallback extends AdvertiseCallback { 219 220 @Override 221 public void onStartFailure(int errorCode) { 222 super.onStartFailure(errorCode); 223 224 Log.d(TAG, "Advertising failed"); 225 sendFailureIntent(errorCode); 226 stopSelf(); 227 228 } 229 230 @Override 231 public void onStartSuccess(AdvertiseSettings settingsInEffect) { 232 super.onStartSuccess(settingsInEffect); 233 Log.d(TAG, "Advertising successfully started"); 234 } 235 } 236 237 /** 238 * Builds and sends a broadcast intent indicating Advertising has failed. Includes the error 239 * code as an extra. This is intended to be picked up by the {@code AdvertiserFragment}. 240 */ 241 private void sendFailureIntent(int errorCode){ 242 Intent failureIntent = new Intent(); 243 failureIntent.setAction(ADVERTISING_FAILED); 244 failureIntent.putExtra(ADVERTISING_FAILED_EXTRA_CODE, errorCode); 245 sendBroadcast(failureIntent); 246 } 247 248 } 249