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 }