/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.cts.verifier.bluetooth; import java.util.UUID; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothGattServerCallback; import android.bluetooth.BluetoothManager; import android.bluetooth.le.BluetoothLeAdvertiser; import android.bluetooth.le.AdvertiseCallback; import android.bluetooth.le.AdvertiseData; import android.bluetooth.le.AdvertiseSettings; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.ParcelUuid; import android.util.Log; import android.widget.Toast; public class BleAdvertiserService extends Service { public static final boolean DEBUG = true; public static final String TAG = "BleAdvertiserService"; public static final int COMMAND_START_ADVERTISE = 0; public static final int COMMAND_STOP_ADVERTISE = 1; public static final int COMMAND_START_POWER_LEVEL = 2; public static final int COMMAND_STOP_POWER_LEVEL = 3; public static final int COMMAND_START_SCANNABLE = 4; public static final int COMMAND_STOP_SCANNABLE = 5; public static final int COMMAND_START_UNSCANNABLE = 6; public static final int COMMAND_STOP_UNSCANNABLE = 7; public static final String BLE_ADV_NOT_SUPPORT = "com.android.cts.verifier.bluetooth.BLE_ADV_NOT_SUPPORT"; public static final String BLE_START_ADVERTISE = "com.android.cts.verifier.bluetooth.BLE_START_ADVERTISE"; public static final String BLE_STOP_ADVERTISE = "com.android.cts.verifier.bluetooth.BLE_STOP_ADVERTISE"; public static final String BLE_START_POWER_LEVEL = "com.android.cts.verifier.bluetooth.BLE_START_POWER_LEVEL"; public static final String BLE_STOP_POWER_LEVEL = "com.android.cts.verifier.bluetooth.BLE_STOP_POWER_LEVEL"; public static final String BLE_START_SCANNABLE = "com.android.cts.verifier.bluetooth.BLE_START_SCANNABLE"; public static final String BLE_START_UNSCANNABLE = "com.android.cts.verifier.bluetooth.BLE_START_UNSCANNABLE"; public static final String BLE_STOP_SCANNABLE = "com.android.cts.verifier.bluetooth.BLE_STOP_SCANNABLE"; public static final String BLE_STOP_UNSCANNABLE = "com.android.cts.verifier.bluetooth.BLE_STOP_UNSCANNABLE"; public static final String EXTRA_COMMAND = "com.android.cts.verifier.bluetooth.EXTRA_COMMAND"; protected static final UUID PRIVACY_MAC_UUID = UUID.fromString("00009999-0000-1000-8000-00805f9b34fb"); protected static final UUID POWER_LEVEL_UUID = UUID.fromString("00008888-0000-1000-8000-00805f9b34fb"); protected static final UUID SCAN_RESP_UUID = UUID.fromString("00007777-0000-1000-8000-00805f9b34fb"); protected static final UUID SCANNABLE_UUID = UUID.fromString("00006666-0000-1000-8000-00805f9b34fb"); protected static final UUID UNSCANNABLE_UUID = UUID.fromString("00005555-0000-1000-8000-00805f9b34fb"); public static final byte MANUFACTURER_TEST_ID = (byte)0x07; public static final byte[] PRIVACY_MAC_DATA = new byte[]{3, 1, 4}; public static final byte[] PRIVACY_RESPONSE = new byte[]{9, 2, 6}; public static final byte[] POWER_LEVEL_DATA = new byte[]{1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // 15 bytes public static final byte[] POWER_LEVEL_MASK = new byte[]{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // 15 bytes public static final int POWER_LEVEL_DATA_LENGTH = 15; public static final byte[] SCANNABLE_DATA = new byte[]{5, 3, 5}; public static final byte[] UNSCANNABLE_DATA = new byte[]{8, 9, 7}; private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private BluetoothLeAdvertiser mAdvertiser; private BluetoothGattServer mGattServer; private AdvertiseCallback mCallback; private Handler mHandler; private int[] mPowerLevel; private Map mPowerCallback; private int mAdvertiserStatus; private AdvertiseCallback mScannableCallback; private AdvertiseCallback mUnscannableCallback; @Override public void onCreate() { super.onCreate(); mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = mBluetoothManager.getAdapter(); mAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser(); mGattServer = mBluetoothManager.openGattServer(getApplicationContext(), new BluetoothGattServerCallback() {}); mHandler = new Handler(); mAdvertiserStatus = 0; mCallback = new BLEAdvertiseCallback(); mScannableCallback = new BLEAdvertiseCallback(); mUnscannableCallback = new BLEAdvertiseCallback(); mPowerLevel = new int[]{ AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW, AdvertiseSettings.ADVERTISE_TX_POWER_LOW, AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM, AdvertiseSettings.ADVERTISE_TX_POWER_HIGH}; mPowerCallback = new HashMap(); for (int x : mPowerLevel) { mPowerCallback.put(x, new BLEAdvertiseCallback()); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null) handleIntent(intent); return START_NOT_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); if (mAdvertiser != null) { stopAdvertiser(); } } private void stopAdvertiser() { if (mAdvertiser == null) { mAdvertiserStatus = 0; return; } if ((mAdvertiserStatus & (1 << COMMAND_START_ADVERTISE)) > 0) { mAdvertiser.stopAdvertising(mCallback); } if ((mAdvertiserStatus & (1 << COMMAND_START_POWER_LEVEL)) > 0) { for (int t : mPowerLevel) { mAdvertiser.stopAdvertising(mPowerCallback.get(t)); } } if ((mAdvertiserStatus & (1 << COMMAND_START_SCANNABLE)) > 0) { mAdvertiser.stopAdvertising(mScannableCallback); } if ((mAdvertiserStatus & (1 << COMMAND_START_UNSCANNABLE)) > 0) { mAdvertiser.stopAdvertising(mUnscannableCallback); } mAdvertiserStatus = 0; } private AdvertiseData generateAdvertiseData(UUID uuid, byte[] data) { return new AdvertiseData.Builder() .addManufacturerData(MANUFACTURER_TEST_ID, new byte[]{MANUFACTURER_TEST_ID, 0}) .addServiceData(new ParcelUuid(uuid), data) .setIncludeTxPowerLevel(true) .build(); } private AdvertiseSettings generateSetting(int power) { return new AdvertiseSettings.Builder() .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) .setTxPowerLevel(power) .setConnectable(false) .build(); } private void handleIntent(Intent intent) { if (mBluetoothAdapter != null && !mBluetoothAdapter.isMultipleAdvertisementSupported()) { showMessage("Multiple advertisement is not supported."); sendBroadcast(new Intent(BLE_ADV_NOT_SUPPORT)); return; } else if (mAdvertiser == null) { showMessage("Cannot start advertising on this device."); return; } int command = intent.getIntExtra(EXTRA_COMMAND, -1); if (command >= 0) { stopAdvertiser(); mAdvertiserStatus |= (1 << command); } switch (command) { case COMMAND_START_ADVERTISE: AdvertiseData data = generateAdvertiseData(PRIVACY_MAC_UUID, PRIVACY_MAC_DATA); AdvertiseData response = generateAdvertiseData(SCAN_RESP_UUID, PRIVACY_RESPONSE); AdvertiseSettings setting = generateSetting(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM); mAdvertiser.startAdvertising(setting, data, response, mCallback); sendBroadcast(new Intent(BLE_START_ADVERTISE)); break; case COMMAND_STOP_ADVERTISE: sendBroadcast(new Intent(BLE_STOP_ADVERTISE)); break; case COMMAND_START_POWER_LEVEL: for (int t : mPowerLevel) { // Service data: // field overhead = 2 bytes // uuid = 2 bytes // data = 15 bytes // Manufacturer data: // field overhead = 2 bytes // Specific data length = 2 bytes // data length = 2 bytes // Include power level: // field overhead = 2 bytes // 1 byte // Connectable flag: 3 bytes (0 byte for Android 5.1+) // SUM = 31 bytes byte[] dataBytes = new byte[POWER_LEVEL_DATA_LENGTH]; dataBytes[0] = 0x01; dataBytes[1] = 0x05; for (int i = 2; i < POWER_LEVEL_DATA_LENGTH; i++) { dataBytes[i] = (byte)t; } AdvertiseData d = generateAdvertiseData(POWER_LEVEL_UUID, dataBytes); AdvertiseSettings settings = generateSetting(t); mAdvertiser.startAdvertising(settings, d, mPowerCallback.get(t)); } sendBroadcast(new Intent(BLE_START_POWER_LEVEL)); break; case COMMAND_STOP_POWER_LEVEL: sendBroadcast(new Intent(BLE_STOP_POWER_LEVEL)); break; case COMMAND_START_SCANNABLE: data = generateAdvertiseData(SCANNABLE_UUID, SCANNABLE_DATA); setting = generateSetting(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM); mAdvertiser.startAdvertising(setting, data, mScannableCallback); sendBroadcast(new Intent(BLE_START_SCANNABLE)); break; case COMMAND_START_UNSCANNABLE: data = generateAdvertiseData(UNSCANNABLE_UUID, UNSCANNABLE_DATA); setting = generateSetting(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM); mAdvertiser.startAdvertising(setting, data, mUnscannableCallback); sendBroadcast(new Intent(BLE_START_UNSCANNABLE)); break; case COMMAND_STOP_SCANNABLE: sendBroadcast(new Intent(BLE_STOP_SCANNABLE)); break; case COMMAND_STOP_UNSCANNABLE: sendBroadcast(new Intent(BLE_STOP_UNSCANNABLE)); break; default: showMessage("Unrecognized command: " + command); break; } } private void showMessage(final String msg) { mHandler.post(new Runnable() { public void run() { Toast.makeText(BleAdvertiserService.this, msg, Toast.LENGTH_SHORT).show(); } }); } private class BLEAdvertiseCallback extends AdvertiseCallback { @Override public void onStartFailure(int errorCode) { Log.e(TAG, "fail. Error code: " + errorCode); } @Override public void onStartSuccess(AdvertiseSettings setting) { if (DEBUG) Log.d(TAG, "success."); } } }