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