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 }