blob: b4a6416be6eb2e8bf4cd66d0e56310bd96be751e [file] [log] [blame]
/*
* 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 = "BleAdvertiseService";
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<Integer, AdvertiseCallback> 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<Integer, AdvertiseCallback>();
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.");
}
}
}