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