Home | History | Annotate | Download | only in com.example.android.bluetoothadvertisements
      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
     57     public void onCreate() {
     58         running = true;
     59         initialize();
     60         startAdvertising();
     61         setTimeout();
     62         super.onCreate();
     63     }
     64 
     65     @Override
     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
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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
    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
    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      */
    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 }