1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.verifier.bluetooth;
18 
19 import java.util.UUID;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 
25 import android.app.Service;
26 import android.bluetooth.BluetoothAdapter;
27 import android.bluetooth.BluetoothGattServer;
28 import android.bluetooth.BluetoothGattServerCallback;
29 import android.bluetooth.BluetoothManager;
30 import android.bluetooth.le.BluetoothLeAdvertiser;
31 import android.bluetooth.le.AdvertiseCallback;
32 import android.bluetooth.le.AdvertiseData;
33 import android.bluetooth.le.AdvertiseSettings;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.os.Handler;
37 import android.os.IBinder;
38 import android.os.ParcelUuid;
39 import android.util.Log;
40 import android.widget.Toast;
41 
42 public class BleAdvertiserService extends Service {
43 
44     public static final boolean DEBUG = true;
45     public static final String TAG = "BleAdvertiserService";
46 
47     public static final int COMMAND_START_ADVERTISE = 0;
48     public static final int COMMAND_STOP_ADVERTISE = 1;
49     public static final int COMMAND_START_POWER_LEVEL = 2;
50     public static final int COMMAND_STOP_POWER_LEVEL = 3;
51     public static final int COMMAND_START_SCANNABLE = 4;
52     public static final int COMMAND_STOP_SCANNABLE = 5;
53     public static final int COMMAND_START_UNSCANNABLE = 6;
54     public static final int COMMAND_STOP_UNSCANNABLE = 7;
55 
56     public static final String BLE_ADV_NOT_SUPPORT =
57             "com.android.cts.verifier.bluetooth.BLE_ADV_NOT_SUPPORT";
58     public static final String BLE_START_ADVERTISE =
59             "com.android.cts.verifier.bluetooth.BLE_START_ADVERTISE";
60     public static final String BLE_STOP_ADVERTISE =
61             "com.android.cts.verifier.bluetooth.BLE_STOP_ADVERTISE";
62     public static final String BLE_START_POWER_LEVEL =
63             "com.android.cts.verifier.bluetooth.BLE_START_POWER_LEVEL";
64     public static final String BLE_STOP_POWER_LEVEL =
65             "com.android.cts.verifier.bluetooth.BLE_STOP_POWER_LEVEL";
66     public static final String BLE_START_SCANNABLE =
67             "com.android.cts.verifier.bluetooth.BLE_START_SCANNABLE";
68     public static final String BLE_START_UNSCANNABLE =
69             "com.android.cts.verifier.bluetooth.BLE_START_UNSCANNABLE";
70     public static final String BLE_STOP_SCANNABLE =
71             "com.android.cts.verifier.bluetooth.BLE_STOP_SCANNABLE";
72     public static final String BLE_STOP_UNSCANNABLE =
73             "com.android.cts.verifier.bluetooth.BLE_STOP_UNSCANNABLE";
74 
75     public static final String EXTRA_COMMAND =
76             "com.android.cts.verifier.bluetooth.EXTRA_COMMAND";
77 
78     protected static final UUID PRIVACY_MAC_UUID =
79             UUID.fromString("00009999-0000-1000-8000-00805f9b34fb");
80     protected static final UUID POWER_LEVEL_UUID =
81             UUID.fromString("00008888-0000-1000-8000-00805f9b34fb");
82     protected static final UUID SCAN_RESP_UUID =
83             UUID.fromString("00007777-0000-1000-8000-00805f9b34fb");
84     protected static final UUID SCANNABLE_UUID =
85             UUID.fromString("00006666-0000-1000-8000-00805f9b34fb");
86     protected static final UUID UNSCANNABLE_UUID =
87             UUID.fromString("00005555-0000-1000-8000-00805f9b34fb");
88 
89     public static final byte MANUFACTURER_TEST_ID = (byte)0x07;
90     public static final byte[] PRIVACY_MAC_DATA = new byte[]{3, 1, 4};
91     public static final byte[] PRIVACY_RESPONSE = new byte[]{9, 2, 6};
92     public static final byte[] POWER_LEVEL_DATA = new byte[]{1, 5, 0, 0, 0,
93         0, 0, 0, 0, 0, 0, 0, 0, 0, 0};  // 15 bytes
94     public static final byte[] POWER_LEVEL_MASK = new byte[]{1, 1, 0, 0, 0,
95         0, 0, 0, 0, 0, 0, 0, 0, 0, 0};  // 15 bytes
96     public static final int POWER_LEVEL_DATA_LENGTH = 15;
97     public static final byte[] SCANNABLE_DATA = new byte[]{5, 3, 5};
98     public static final byte[] UNSCANNABLE_DATA = new byte[]{8, 9, 7};
99 
100     private BluetoothManager mBluetoothManager;
101     private BluetoothAdapter mBluetoothAdapter;
102     private BluetoothLeAdvertiser mAdvertiser;
103     private BluetoothGattServer mGattServer;
104     private AdvertiseCallback mCallback;
105     private Handler mHandler;
106 
107     private int[] mPowerLevel;
108     private Map<Integer, AdvertiseCallback> mPowerCallback;
109     private int mAdvertiserStatus;
110 
111     private AdvertiseCallback mScannableCallback;
112     private AdvertiseCallback mUnscannableCallback;
113 
114     @Override
onCreate()115     public void onCreate() {
116         super.onCreate();
117 
118         mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
119         mBluetoothAdapter = mBluetoothManager.getAdapter();
120         mAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
121         mGattServer = mBluetoothManager.openGattServer(getApplicationContext(),
122             new BluetoothGattServerCallback() {});
123         mHandler = new Handler();
124         mAdvertiserStatus = 0;
125 
126         mCallback = new BLEAdvertiseCallback();
127         mScannableCallback = new BLEAdvertiseCallback();
128         mUnscannableCallback = new BLEAdvertiseCallback();
129         mPowerLevel = new int[]{
130             AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW,
131             AdvertiseSettings.ADVERTISE_TX_POWER_LOW,
132             AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM,
133             AdvertiseSettings.ADVERTISE_TX_POWER_HIGH};
134         mPowerCallback = new HashMap<Integer, AdvertiseCallback>();
135         for (int x : mPowerLevel) {
136             mPowerCallback.put(x, new BLEAdvertiseCallback());
137         }
138     }
139 
140     @Override
onStartCommand(Intent intent, int flags, int startId)141     public int onStartCommand(Intent intent, int flags, int startId) {
142         if (intent != null) handleIntent(intent);
143         return START_NOT_STICKY;
144     }
145 
146     @Override
onBind(Intent intent)147     public IBinder onBind(Intent intent) {
148         return null;
149     }
150 
151     @Override
onDestroy()152     public void onDestroy() {
153         super.onDestroy();
154         if (mAdvertiser != null) {
155             stopAdvertiser();
156         }
157     }
158 
stopAdvertiser()159     private void stopAdvertiser() {
160         if (mAdvertiser == null) {
161             mAdvertiserStatus = 0;
162             return;
163         }
164         if ((mAdvertiserStatus & (1 << COMMAND_START_ADVERTISE)) > 0) {
165             mAdvertiser.stopAdvertising(mCallback);
166         }
167         if ((mAdvertiserStatus & (1 << COMMAND_START_POWER_LEVEL)) > 0) {
168             for (int t : mPowerLevel) {
169                 mAdvertiser.stopAdvertising(mPowerCallback.get(t));
170             }
171         }
172         if ((mAdvertiserStatus & (1 << COMMAND_START_SCANNABLE)) > 0) {
173             mAdvertiser.stopAdvertising(mScannableCallback);
174         }
175         if ((mAdvertiserStatus & (1 << COMMAND_START_UNSCANNABLE)) > 0) {
176             mAdvertiser.stopAdvertising(mUnscannableCallback);
177         }
178         mAdvertiserStatus = 0;
179     }
180 
generateAdvertiseData(UUID uuid, byte[] data)181     private AdvertiseData generateAdvertiseData(UUID uuid, byte[] data) {
182         return new AdvertiseData.Builder()
183             .addManufacturerData(MANUFACTURER_TEST_ID, new byte[]{MANUFACTURER_TEST_ID, 0})
184             .addServiceData(new ParcelUuid(uuid), data)
185             .setIncludeTxPowerLevel(true)
186             .build();
187     }
188 
generateSetting(int power)189     private AdvertiseSettings generateSetting(int power) {
190         return new AdvertiseSettings.Builder()
191             .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
192             .setTxPowerLevel(power)
193             .setConnectable(false)
194             .build();
195     }
196 
handleIntent(Intent intent)197     private void handleIntent(Intent intent) {
198         if (mBluetoothAdapter != null && !mBluetoothAdapter.isMultipleAdvertisementSupported()) {
199             showMessage("Multiple advertisement is not supported.");
200             sendBroadcast(new Intent(BLE_ADV_NOT_SUPPORT));
201             return;
202         } else if (mAdvertiser == null) {
203             showMessage("Cannot start advertising on this device.");
204             return;
205         }
206         int command = intent.getIntExtra(EXTRA_COMMAND, -1);
207         if (command >= 0) {
208             stopAdvertiser();
209             mAdvertiserStatus |= (1 << command);
210         }
211 
212         switch (command) {
213             case COMMAND_START_ADVERTISE:
214                 AdvertiseData data = generateAdvertiseData(PRIVACY_MAC_UUID, PRIVACY_MAC_DATA);
215                 AdvertiseData response = generateAdvertiseData(SCAN_RESP_UUID, PRIVACY_RESPONSE);
216                 AdvertiseSettings setting =
217                         generateSetting(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM);
218 
219                 mAdvertiser.startAdvertising(setting, data, response, mCallback);
220                 sendBroadcast(new Intent(BLE_START_ADVERTISE));
221                 break;
222             case COMMAND_STOP_ADVERTISE:
223                 sendBroadcast(new Intent(BLE_STOP_ADVERTISE));
224                 break;
225             case COMMAND_START_POWER_LEVEL:
226                 for (int t : mPowerLevel) {
227                     // Service data:
228                     //    field overhead = 2 bytes
229                     //    uuid = 2 bytes
230                     //    data = 15 bytes
231                     // Manufacturer data:
232                     //    field overhead = 2 bytes
233                     //    Specific data length = 2 bytes
234                     //    data length = 2 bytes
235                     // Include power level:
236                     //    field overhead = 2 bytes
237                     //    1 byte
238                     // Connectable flag: 3 bytes (0 byte for Android 5.1+)
239                     // SUM = 31 bytes
240                     byte[] dataBytes = new byte[POWER_LEVEL_DATA_LENGTH];
241                     dataBytes[0] = 0x01;
242                     dataBytes[1] = 0x05;
243                     for (int i = 2; i < POWER_LEVEL_DATA_LENGTH; i++) {
244                         dataBytes[i] = (byte)t;
245                     }
246                     AdvertiseData d = generateAdvertiseData(POWER_LEVEL_UUID, dataBytes);
247                     AdvertiseSettings settings = generateSetting(t);
248                     mAdvertiser.startAdvertising(settings, d, mPowerCallback.get(t));
249                 }
250                 sendBroadcast(new Intent(BLE_START_POWER_LEVEL));
251                 break;
252             case COMMAND_STOP_POWER_LEVEL:
253                 sendBroadcast(new Intent(BLE_STOP_POWER_LEVEL));
254                 break;
255             case COMMAND_START_SCANNABLE:
256                 data = generateAdvertiseData(SCANNABLE_UUID, SCANNABLE_DATA);
257                 setting = generateSetting(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM);
258 
259                 mAdvertiser.startAdvertising(setting, data, mScannableCallback);
260                 sendBroadcast(new Intent(BLE_START_SCANNABLE));
261                 break;
262             case COMMAND_START_UNSCANNABLE:
263                 data = generateAdvertiseData(UNSCANNABLE_UUID, UNSCANNABLE_DATA);
264                 setting = generateSetting(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM);
265 
266                 mAdvertiser.startAdvertising(setting, data, mUnscannableCallback);
267                 sendBroadcast(new Intent(BLE_START_UNSCANNABLE));
268                 break;
269             case COMMAND_STOP_SCANNABLE:
270                 sendBroadcast(new Intent(BLE_STOP_SCANNABLE));
271                 break;
272             case COMMAND_STOP_UNSCANNABLE:
273                 sendBroadcast(new Intent(BLE_STOP_UNSCANNABLE));
274                 break;
275             default:
276                 showMessage("Unrecognized command: " + command);
277                 break;
278         }
279     }
280 
showMessage(final String msg)281     private void showMessage(final String msg) {
282         mHandler.post(new Runnable() {
283             public void run() {
284                 Toast.makeText(BleAdvertiserService.this, msg, Toast.LENGTH_SHORT).show();
285             }
286         });
287     }
288 
289     private class BLEAdvertiseCallback extends AdvertiseCallback {
290         @Override
onStartFailure(int errorCode)291         public void onStartFailure(int errorCode) {
292             Log.e(TAG, "fail. Error code: " + errorCode);
293         }
294 
295         @Override
onStartSuccess(AdvertiseSettings setting)296         public void onStartSuccess(AdvertiseSettings setting) {
297             if (DEBUG) Log.d(TAG, "success.");
298         }
299     }
300 }
301