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