From 27bd5f277ccf471f2fa9cd9151a2a226b51bc825 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Tue, 15 Jul 2014 23:12:20 -0700 Subject: [PATCH] Add an AdvertiseManager for LE advertise operations. Move all advertise logic out of GattServiceStateMachine. Change-Id: I9bc9be29372e79e863fc6a0b16d4808918bfb404 --- jni/com_android_bluetooth_gatt.cpp | 23 +- src/com/android/bluetooth/Utils.java | 60 ++- .../android/bluetooth/gatt/AdvertiseClient.java | 38 +- .../android/bluetooth/gatt/AdvertiseManager.java | 429 +++++++++++++++++++++ src/com/android/bluetooth/gatt/GattService.java | 103 ++--- .../bluetooth/gatt/GattServiceStateMachine.java | 254 +----------- 6 files changed, 564 insertions(+), 343 deletions(-) create mode 100644 src/com/android/bluetooth/gatt/AdvertiseManager.java diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp index c773a599..f27bde4b 100644 --- a/jni/com_android_bluetooth_gatt.cpp +++ b/jni/com_android_bluetooth_gatt.cpp @@ -833,10 +833,10 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_onScanFilterConfig = env->GetMethodID(clazz, "onScanFilterConfig", "(IIIII)V"); method_onScanFilterParamsConfigured = env->GetMethodID(clazz, "onScanFilterParamsConfigured", "(IIII)V"); method_onScanFilterEnableDisabled = env->GetMethodID(clazz, "onScanFilterEnableDisabled", "(III)V"); - method_onMultiAdvEnable = env->GetMethodID(clazz, "onClientEnable", "(II)V"); - method_onMultiAdvUpdate = env->GetMethodID(clazz, "onClientUpdate", "(II)V"); - method_onMultiAdvSetAdvData = env->GetMethodID(clazz, "onClientData", "(II)V"); - method_onMultiAdvDisable = env->GetMethodID(clazz, "onClientDisable", "(II)V"); + method_onMultiAdvEnable = env->GetMethodID(clazz, "onAdvertiseInstanceEnabled", "(II)V"); + method_onMultiAdvUpdate = env->GetMethodID(clazz, "onAdvertiseDataUpdated", "(II)V"); + method_onMultiAdvSetAdvData = env->GetMethodID(clazz, "onAdvertiseDataSet", "(II)V"); + method_onMultiAdvDisable = env->GetMethodID(clazz, "onAdvertiseInstanceDisabled", "(II)V"); method_onClientCongestion = env->GetMethodID(clazz, "onClientCongestion", "(IZ)V"); method_onBatchScanStorageConfigured = env->GetMethodID(clazz, "onBatchScanStorageConfigured", "(II)V"); method_onBatchScanStartStopped = env->GetMethodID(clazz, "onBatchScanStartStopped", "(III)V"); @@ -1700,6 +1700,14 @@ static void gattTestNative(JNIEnv *env, jobject object, jint command, * JNI function definitinos */ +// JNI functions defined in AdvertiseManager class. +static JNINativeMethod sAdvertiseMethods[] = { + {"gattClientEnableAdvNative", "(IIIIII)V", (void *) gattClientEnableAdvNative}, + {"gattClientUpdateAdvNative", "(IIIIII)V", (void *) gattClientUpdateAdvNative}, + {"gattClientSetAdvDataNative", "(IZZZI[B[B[B)V", (void *) gattClientSetAdvDataNative}, + {"gattClientDisableAdvNative", "(I)V", (void *) gattClientDisableAdvNative}, +}; + // JNI functions defined in GattStateMachine class. static JNINativeMethod sStateMachineMethods[] = { {"gattClientScanNative", "(Z)V", (void *) gattClientScanNative}, @@ -1713,10 +1721,6 @@ static JNINativeMethod sStateMachineMethods[] = { {"gattClientScanFilterDeleteNative", "(IIIIIJJJJLjava/lang/String;Ljava/lang/String;B[B[B)V", (void *) gattClientScanFilterDeleteNative}, {"gattClientScanFilterClearNative", "(II)V", (void *) gattClientScanFilterClearNative}, {"gattClientScanFilterEnableNative", "(IZ)V", (void *) gattClientScanFilterEnableNative}, - {"gattClientEnableAdvNative", "(IIIIII)V", (void *) gattClientEnableAdvNative}, - {"gattClientUpdateAdvNative", "(IIIIII)V", (void *) gattClientUpdateAdvNative}, - {"gattClientSetAdvDataNative", "(IZZZI[B[B[B)V", (void *) gattClientSetAdvDataNative}, - {"gattClientDisableAdvNative", "(I)V", (void *) gattClientDisableAdvNative}, }; // JNI functions defined in GattService class. @@ -1769,6 +1773,9 @@ int register_com_android_bluetooth_gatt(JNIEnv* env) int register_success = jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/GattServiceStateMachine", sStateMachineMethods, NELEM(sStateMachineMethods)); + register_success &= + jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/AdvertiseManager$AdvertiseNative", + sAdvertiseMethods, NELEM(sAdvertiseMethods)); return register_success & jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/GattService", sMethods, NELEM(sMethods)); diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java index 8d3ee6a9..0c85a7a2 100644 --- a/src/com/android/bluetooth/Utils.java +++ b/src/com/android/bluetooth/Utils.java @@ -19,6 +19,7 @@ package com.android.bluetooth; import android.app.ActivityManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.content.ContextWrapper; import android.os.Binder; import android.os.ParcelUuid; import android.os.UserHandle; @@ -41,7 +42,7 @@ final public class Utils { static final int BD_UUID_LEN = 16; // bytes public static String getAddressStringFromByte(byte[] address) { - if (address == null || address.length !=6) { + if (address == null || address.length != BD_ADDR_LEN) { return null; } @@ -58,9 +59,9 @@ final public class Utils { int i, j = 0; byte[] output = new byte[BD_ADDR_LEN]; - for (i = 0; i < address.length();i++) { + for (i = 0; i < address.length(); i++) { if (address.charAt(i) != ':') { - output[j] = (byte) Integer.parseInt(address.substring(i, i+2), 16); + output[j] = (byte) Integer.parseInt(address.substring(i, i + 2), BD_UUID_LEN); j++; i++; } @@ -115,14 +116,14 @@ final public class Utils { uuid = uuids[i].getUuid(); msb = uuid.getMostSignificantBits(); lsb = uuid.getLeastSignificantBits(); - converter.putLong(i * 16, msb); - converter.putLong(i * 16 + 8, lsb); + converter.putLong(i * BD_UUID_LEN, msb); + converter.putLong(i * BD_UUID_LEN + 8, lsb); } return converter.array(); } public static ParcelUuid[] byteArrayToUuid(byte[] val) { - int numUuids = val.length/BD_UUID_LEN; + int numUuids = val.length / BD_UUID_LEN; ParcelUuid[] puuids = new ParcelUuid[numUuids]; UUID uuid; int offset = 0; @@ -133,27 +134,33 @@ final public class Utils { for (int i = 0; i < numUuids; i++) { puuids[i] = new ParcelUuid(new UUID(converter.getLong(offset), converter.getLong(offset + 8))); - offset += 16; + offset += BD_UUID_LEN; } return puuids; } public static String debugGetAdapterStateString(int state) { switch (state) { - case BluetoothAdapter.STATE_OFF : return "STATE_OFF"; - case BluetoothAdapter.STATE_ON : return "STATE_ON"; - case BluetoothAdapter.STATE_TURNING_ON : return "STATE_TURNING_ON"; - case BluetoothAdapter.STATE_TURNING_OFF : return "STATE_TURNING_OFF"; - default : return "UNKNOWN"; + case BluetoothAdapter.STATE_OFF: + return "STATE_OFF"; + case BluetoothAdapter.STATE_ON: + return "STATE_ON"; + case BluetoothAdapter.STATE_TURNING_ON: + return "STATE_TURNING_ON"; + case BluetoothAdapter.STATE_TURNING_OFF: + return "STATE_TURNING_OFF"; + default: + return "UNKNOWN"; } } - public static void copyStream(InputStream is, OutputStream os, int bufferSize) throws IOException { - if (is != null && os!=null) { + public static void copyStream(InputStream is, OutputStream os, int bufferSize) + throws IOException { + if (is != null && os != null) { byte[] buffer = new byte[bufferSize]; - int bytesRead=0; - while ( (bytesRead = is.read(buffer))>=0) { - os.write(buffer,0,bytesRead); + int bytesRead = 0; + while ((bytesRead = is.read(buffer)) >= 0) { + os.write(buffer, 0, bytesRead); } } } @@ -163,7 +170,7 @@ final public class Utils { try { is.close(); } catch (Throwable t) { - Log.d(TAG,"Error closing stream",t); + Log.d(TAG, "Error closing stream", t); } } } @@ -173,7 +180,7 @@ final public class Utils { try { os.close(); } catch (Throwable t) { - Log.d(TAG,"Error closing stream",t); + Log.d(TAG, "Error closing stream", t); } } } @@ -190,11 +197,24 @@ final public class Utils { int foregroundUser = ActivityManager.getCurrentUser(); ok = (foregroundUser == callingUser); } catch (Exception ex) { - Log.e(TAG,"checkIfCallerIsSelfOrForegroundUser: Exception ex=" + ex); + Log.e(TAG, "checkIfCallerIsSelfOrForegroundUser: Exception ex=" + ex); ok = false; } finally { Binder.restoreCallingIdentity(ident); } return ok; } + + /** + * Enforce the context has android.Manifest.permission.BLUETOOTH_ADMIN permission. A + * {@link SecurityException} would be thrown if neither the calling process or the application + * does not have BLUETOOTH_ADMIN permission. + * + * @param context Context for the permission check. + */ + public static void enforceAdminPermission(ContextWrapper context) { + context.enforceCallingOrSelfPermission(android.Manifest.permission.BLUETOOTH_ADMIN, + "Need BLUETOOTH_ADMIN permission"); + } + } diff --git a/src/com/android/bluetooth/gatt/AdvertiseClient.java b/src/com/android/bluetooth/gatt/AdvertiseClient.java index 39d0adf7..1f3fd0ff 100644 --- a/src/com/android/bluetooth/gatt/AdvertiseClient.java +++ b/src/com/android/bluetooth/gatt/AdvertiseClient.java @@ -20,7 +20,11 @@ import android.annotation.Nullable; import android.bluetooth.le.AdvertiseData; import android.bluetooth.le.AdvertiseSettings; +import java.util.Objects; + /** + * Helper class that represents a client for Bluetooth LE advertise operations. + * * @hide */ class AdvertiseClient { @@ -30,11 +34,41 @@ class AdvertiseClient { @Nullable AdvertiseData scanResponse; - AdvertiseClient(int clientIf, AdvertiseSettings settings, AdvertiseData data, + /** + * @param clientIf - Identifier of the client. + */ + public AdvertiseClient(int clientIf) { + this.clientIf = clientIf; + } + + /** + * @param clientIf - Identifier of the client. + * @param settings - Settings for the advertising. + * @param advertiseData - Advertise data broadcasted over the air. + * @param scanResponse - Response of scan request, could be null. + */ + AdvertiseClient(int clientIf, AdvertiseSettings settings, AdvertiseData advertiseData, AdvertiseData scanResponse) { this.clientIf = clientIf; this.settings = settings; - this.advertiseData = data; + this.advertiseData = advertiseData; this.scanResponse = scanResponse; } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + AdvertiseClient other = (AdvertiseClient) obj; + return clientIf == other.clientIf; + } + + @Override + public int hashCode() { + return Objects.hash(clientIf); + } } diff --git a/src/com/android/bluetooth/gatt/AdvertiseManager.java b/src/com/android/bluetooth/gatt/AdvertiseManager.java new file mode 100644 index 00000000..fabf0735 --- /dev/null +++ b/src/com/android/bluetooth/gatt/AdvertiseManager.java @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2014 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.bluetooth.gatt; + +import android.bluetooth.le.AdvertiseCallback; +import android.bluetooth.le.AdvertiseData; +import android.bluetooth.le.AdvertiseSettings; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; + +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Manages Bluetooth LE advertising operations and interacts with bluedroid stack. + * + * @hide + */ +class AdvertiseManager { + private static final boolean DBG = GattServiceConfig.DBG; + private static final String TAG = GattServiceConfig.TAG_PREFIX + "AdvertiseManager"; + + // Timeout for each controller operation. + private static final int OPERATION_TIME_OUT_MILLIS = 500; + + // Message for advertising operations. + private static final int MSG_START_ADVERTISING = 0; + private static final int MSG_STOP_ADVERTISING = 1; + + private final GattService mService; + private final Set mAdvertiseClients; + private final AdvertiseNative mAdvertiseNative; + + // Handles advertise operations. + private ClientHandler mHandler; + + // CountDownLatch for blocking advertise operations. + private CountDownLatch mLatch; + + /** + * Constructor of {@link AdvertiseManager}. + */ + AdvertiseManager(GattService service) { + mService = service; + logd("advertise manager created"); + mAdvertiseClients = new HashSet(); + mAdvertiseNative = new AdvertiseNative(); + } + + /** + * Start a {@link HandlerThread} that handles advertising operations. + */ + void start() { + HandlerThread thread = new HandlerThread("BluetoothAdvertiseManager"); + thread.start(); + mHandler = new ClientHandler(thread.getLooper()); + } + + void cleanup() { + logd("advertise clients cleared"); + mAdvertiseClients.clear(); + } + + /** + * Start BLE advertising. + * + * @param client Advertise client. + */ + void startAdvertising(AdvertiseClient client) { + if (client == null) { + return; + } + Message message = new Message(); + message.what = MSG_START_ADVERTISING; + message.obj = client; + mHandler.sendMessage(message); + } + + /** + * Stop BLE advertising. + */ + void stopAdvertising(AdvertiseClient client) { + if (client == null) { + return; + } + Message message = new Message(); + message.what = MSG_STOP_ADVERTISING; + message.obj = client; + mHandler.sendMessage(message); + } + + /** + * Signals the callback is received. + * + * @param clientIf Identifier for the client. + * @param status Status of the callback. + */ + void callbackDone(int clientIf, int status) { + if (status == AdvertiseCallback.ADVERTISE_SUCCESS) { + mLatch.countDown(); + } else { + // Note in failure case we'll wait for the latch to timeout(which takes 100ms) and + // the mClientHandler thread will be blocked till timeout. + postCallback(clientIf, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); + } + } + + // Post callback status to app process. + private void postCallback(int clientIf, int status) { + try { + mService.onMultipleAdvertiseCallback(clientIf, status); + } catch (RemoteException e) { + loge("failed onMultipleAdvertiseCallback", e); + } + } + + // Handler class that handles BLE advertising operations. + private class ClientHandler extends Handler { + + ClientHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + logd("message : " + msg.what); + AdvertiseClient client = (AdvertiseClient) msg.obj; + switch (msg.what) { + case MSG_START_ADVERTISING: + handleStartAdvertising(client); + break; + case MSG_STOP_ADVERTISING: + handleStopAdvertising(client); + break; + default: + // Shouldn't happen. + Log.e(TAG, "recieve an unknown message : " + msg.what); + break; + } + } + + private void handleStartAdvertising(AdvertiseClient client) { + Utils.enforceAdminPermission(mService); + int clientIf = client.clientIf; + if (mAdvertiseClients.contains(clientIf)) { + postCallback(clientIf, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); + return; + } + + if (mAdvertiseClients.size() >= maxAdvertiseInstances()) { + postCallback(clientIf, + AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS); + return; + } + + if (!isAllServiceRegistered(client)) { + postCallback(clientIf, AdvertiseCallback.ADVERTISE_FAILED_SERVICE_UNKNOWN); + } + if (!mAdvertiseNative.startAdverising(client)) { + postCallback(clientIf, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); + return; + } + mAdvertiseClients.add(client); + postCallback(clientIf, AdvertiseCallback.ADVERTISE_SUCCESS); + } + + // Handles stop advertising. + private void handleStopAdvertising(AdvertiseClient client) { + Utils.enforceAdminPermission(mService); + if (client == null) { + return; + } + int clientIf = client.clientIf; + logd("advertise clients size " + mAdvertiseClients.size()); + if (mAdvertiseClients.contains(client)) { + mAdvertiseNative.stopAdvertising(client); + mAdvertiseClients.remove(clientIf); + } + } + + // Returns maximum advertise instances supported by controller. + private int maxAdvertiseInstances() { + AdapterService adapter = AdapterService.getAdapterService(); + int numOfAdvtInstances = adapter.getNumOfAdvertisementInstancesSupported(); + // Note numOfAdvtInstances includes the standard advertising instance. + // TODO: remove - 1 once the stack is able to include standard instance for multiple + // advertising. + return numOfAdvtInstances - 1; + } + + // Check whether all service uuids have been registered to GATT server. + private boolean isAllServiceRegistered(AdvertiseClient client) { + List registeredUuids = mService.getRegisteredServiceUuids(); + return containsAll(registeredUuids, client.advertiseData) && + containsAll(registeredUuids, client.scanResponse); + } + + // Check whether the registeredUuids contains all uuids in advertiseData. + private boolean containsAll(List registeredUuids, AdvertiseData advertiseData) { + if (advertiseData == null) { + return true; + } + List advertiseUuids = advertiseData.getServiceUuids(); + if (advertiseUuids == null) { + return true; + } + return registeredUuids.containsAll(advertiseUuids); + } + } + + // Class that wraps advertise native related constants, methods etc. + private class AdvertiseNative { + // Advertise interval for different modes. + private static final int ADVERTISING_INTERVAL_HIGH_MILLS = 1000; + private static final int ADVERTISING_INTERVAL_MEDIUM_MILLS = 250; + private static final int ADVERTISING_INTERVAL_LOW_MILLS = 100; + + // Add some randomness to the advertising min/max interval so the controller can do some + // optimization. + private static final int ADVERTISING_INTERVAL_DELTA_UNIT = 10; + private static final int ADVERTISING_INTERVAL_MICROS_PER_UNIT = 625; + + // The following constants should be kept the same as those defined in bt stack. + private static final int ADVERTISING_CHANNEL_37 = 1 << 0; + private static final int ADVERTISING_CHANNEL_38 = 1 << 1; + private static final int ADVERTISING_CHANNEL_39 = 1 << 2; + private static final int ADVERTISING_CHANNEL_ALL = + ADVERTISING_CHANNEL_37 | ADVERTISING_CHANNEL_38 | ADVERTISING_CHANNEL_39; + + private static final int ADVERTISING_TX_POWER_MIN = 0; + private static final int ADVERTISING_TX_POWER_LOW = 1; + private static final int ADVERTISING_TX_POWER_MID = 2; + private static final int ADVERTISING_TX_POWER_UPPER = 3; + // Note this is not exposed to the Java API. + private static final int ADVERTISING_TX_POWER_MAX = 4; + + // Note we don't expose connectable directed advertising to API. + private static final int ADVERTISING_EVENT_TYPE_CONNECTABLE = 0; + private static final int ADVERTISING_EVENT_TYPE_SCANNABLE = 2; + private static final int ADVERTISING_EVENT_TYPE_NON_CONNECTABLE = 3; + + boolean startAdverising(AdvertiseClient client) { + int clientIf = client.clientIf; + resetCountDownLatch(); + mAdvertiseNative.enableAdvertising(client); + if (!waitForCallback()) { + return false; + } + resetCountDownLatch(); + mAdvertiseNative.setAdvertisingData(clientIf, client.advertiseData, false); + if (!waitForCallback()) { + return false; + } + if (client.scanResponse != null) { + resetCountDownLatch(); + mAdvertiseNative.setAdvertisingData(clientIf, client.scanResponse, true); + if (!waitForCallback()) { + return false; + } + } + return true; + } + + void stopAdvertising(AdvertiseClient client) { + gattClientDisableAdvNative(client.clientIf); + } + + private void resetCountDownLatch() { + mLatch = new CountDownLatch(1); + } + + // Returns true if mLatch reaches 0, false if timeout or interrupted. + private boolean waitForCallback() { + try { + return mLatch.await(OPERATION_TIME_OUT_MILLIS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + return false; + } + } + + private void enableAdvertising(AdvertiseClient client) { + int clientIf = client.clientIf; + int minAdvertiseUnit = (int) getAdvertisingIntervalUnit(client.settings); + int maxAdvertiseUnit = minAdvertiseUnit + ADVERTISING_INTERVAL_DELTA_UNIT; + int advertiseEventType = getAdvertisingEventType(client); + int txPowerLevel = getTxPowerLevel(client.settings); + gattClientEnableAdvNative( + clientIf, + minAdvertiseUnit, maxAdvertiseUnit, + advertiseEventType, + ADVERTISING_CHANNEL_ALL, + txPowerLevel); + } + + private void setAdvertisingData(int clientIf, AdvertiseData data, boolean isScanResponse) { + if (data == null) { + return; + } + boolean includeName = true; + boolean includeTxPower = data.getIncludeTxPowerLevel(); + int appearance = 0; + byte[] manufacturerData = data.getManufacturerSpecificData() == null ? new byte[0] + : data.getManufacturerSpecificData(); + byte[] serviceData = data.getServiceData() == null ? new byte[0] + : data.getServiceData(); + + byte[] serviceUuids; + if (data.getServiceUuids() == null) { + serviceUuids = new byte[0]; + } else { + ByteBuffer advertisingUuidBytes = ByteBuffer.allocate( + data.getServiceUuids().size() * 16) + .order(ByteOrder.LITTLE_ENDIAN); + for (ParcelUuid parcelUuid : data.getServiceUuids()) { + UUID uuid = parcelUuid.getUuid(); + // Least significant bits first as the advertising uuid should be in + // little-endian. + advertisingUuidBytes.putLong(uuid.getLeastSignificantBits()) + .putLong(uuid.getMostSignificantBits()); + } + serviceUuids = advertisingUuidBytes.array(); + } + gattClientSetAdvDataNative(clientIf, isScanResponse, includeName, includeTxPower, + appearance, + manufacturerData, serviceData, serviceUuids); + } + + // Convert settings tx power level to stack tx power level. + private int getTxPowerLevel(AdvertiseSettings settings) { + switch (settings.getTxPowerLevel()) { + case AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW: + return ADVERTISING_TX_POWER_MIN; + case AdvertiseSettings.ADVERTISE_TX_POWER_LOW: + return ADVERTISING_TX_POWER_LOW; + case AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM: + return ADVERTISING_TX_POWER_MID; + case AdvertiseSettings.ADVERTISE_TX_POWER_HIGH: + return ADVERTISING_TX_POWER_UPPER; + default: + // Shouldn't happen, just in case. + return ADVERTISING_TX_POWER_MID; + } + } + + // Convert advertising event type to stack values. + private int getAdvertisingEventType(AdvertiseClient client) { + AdvertiseSettings settings = client.settings; + if (settings.getIsConnectable()) { + return ADVERTISING_EVENT_TYPE_CONNECTABLE; + } + return client.scanResponse == null ? ADVERTISING_EVENT_TYPE_NON_CONNECTABLE + : ADVERTISING_EVENT_TYPE_SCANNABLE; + } + + // Convert advertising milliseconds to advertising units(one unit is 0.625 millisecond). + private long getAdvertisingIntervalUnit(AdvertiseSettings settings) { + switch (settings.getMode()) { + case AdvertiseSettings.ADVERTISE_MODE_LOW_POWER: + return millsToUnit(ADVERTISING_INTERVAL_HIGH_MILLS); + case AdvertiseSettings.ADVERTISE_MODE_BALANCED: + return millsToUnit(ADVERTISING_INTERVAL_MEDIUM_MILLS); + case AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY: + return millsToUnit(ADVERTISING_INTERVAL_LOW_MILLS); + default: + // Shouldn't happen, just in case. + return millsToUnit(ADVERTISING_INTERVAL_HIGH_MILLS); + } + } + + private long millsToUnit(int millisecond) { + return TimeUnit.MILLISECONDS.toMicros(millisecond) + / ADVERTISING_INTERVAL_MICROS_PER_UNIT; + } + + // Native functions + private native void gattClientDisableAdvNative(int client_if); + + private native void gattClientEnableAdvNative(int client_if, + int min_interval, int max_interval, int adv_type, int chnl_map, + int tx_power); + + private native void gattClientUpdateAdvNative(int client_if, + int min_interval, int max_interval, int adv_type, int chnl_map, + int tx_power); + + private native void gattClientSetAdvDataNative(int client_if, + boolean set_scan_rsp, boolean incl_name, boolean incl_txpower, int appearance, + byte[] manufacturer_data, byte[] service_data, byte[] service_uuid); + } + + private void logd(String s) { + if (DBG) { + Log.d(TAG, s); + } + } + + private void loge(String s, Exception e) { + Log.e(TAG, s, e); + } + +} diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java index 1334e135..c3568692 100644 --- a/src/com/android/bluetooth/gatt/GattService.java +++ b/src/com/android/bluetooth/gatt/GattService.java @@ -20,7 +20,6 @@ import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothGatt; import android.bluetooth.IBluetoothGattCallback; import android.bluetooth.IBluetoothGattServerCallback; @@ -38,6 +37,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; +import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.ProfileService; import java.util.ArrayList; @@ -149,6 +149,7 @@ public class GattService extends ProfileService { private List mScanQueue = new ArrayList(); private GattServiceStateMachine mStateMachine; + private AdvertiseManager mAdvertiseManager; private ScanClient getScanClient(int appIf, boolean isServer) { for(ScanClient client : mScanQueue) { if (client.appIf == appIf && client.isServer == isServer) { @@ -188,6 +189,8 @@ public class GattService extends ProfileService { if (DBG) Log.d(TAG, "start()"); initializeNative(); mStateMachine = GattServiceStateMachine.make(this); + mAdvertiseManager = new AdvertiseManager(this); + mAdvertiseManager.start(); return true; } @@ -208,6 +211,7 @@ public class GattService extends ProfileService { if (DBG) Log.d(TAG, "cleanup()"); cleanupNative(); mStateMachine.cleanup(); + mAdvertiseManager.cleanup(); return true; } @@ -238,6 +242,8 @@ public class GattService extends ProfileService { } else { stopMultiAdvertising(mAppIf); } + // TODO: Move unregisterClient after stop scan/advertise callback to avoid race + // condition. unregisterClient(mAppIf); } } @@ -1108,46 +1114,37 @@ public class GattService extends ProfileService { if (DBG) Log.d(TAG, "onAdvertiseCallback,- clientIf=" + clientIf + ", status=" + status); } - void onClientEnable(int status, int clientIf) throws RemoteException{ - if (DBG) Log.d(TAG, "onClientEnable() - clientIf=" + clientIf + ", status=" + status); - if (status == 0) { - Message message = mStateMachine.obtainMessage( - GattServiceStateMachine.SET_ADVERTISING_DATA); - message.arg1 = clientIf; - mStateMachine.sendMessage(message); - } else { - Message message = - mStateMachine.obtainMessage(GattServiceStateMachine.CANCEL_ADVERTISING); - message.arg1 = clientIf; - mStateMachine.sendMessage(message); - } + // Followings are callbacks for Bluetooth LE Advertise operations. + // Start advertising flow is + // enable advertising instance -> onAdvertiseInstaceEnabled + // -> set advertise data -> onAdvertiseDataSet + // -> set scan response -> onAdvertiseDataSet + + // Callback when advertise instance is enabled. + void onAdvertiseInstanceEnabled(int status, int clientIf) { + if (DBG) Log.d(TAG, "onAdvertiseInstanceEnabled() - " + + "clientIf=" + clientIf + ", status=" + status); + mAdvertiseManager.callbackDone(clientIf, status); } - void onClientUpdate(int status, int client_if) throws RemoteException { - if (DBG) Log.d(TAG, "onClientUpdate() - client_if=" + client_if + // Not really used. + void onAdvertiseDataUpdated(int status, int client_if) { + if (DBG) Log.d(TAG, "onAdvertiseDataUpdated() - client_if=" + client_if + ", status=" + status); } - void onClientData(int status, int clientIf) throws RemoteException{ - if (DBG) Log.d(TAG, "onClientData() - clientIf=" + clientIf + // Callback when advertise data or scan response is set. + void onAdvertiseDataSet(int status, int clientIf) { + if (DBG) Log.d(TAG, "onAdvertiseDataSet() - clientIf=" + clientIf + ", status=" + status); - - ClientMap.App app = mClientMap.getById(clientIf); - if (app != null) { - if (status == 0) { - app.callback.onMultiAdvertiseCallback(AdvertiseCallback.ADVERTISE_SUCCESS); - } else { - app.callback.onMultiAdvertiseCallback( - AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); - } - } + mAdvertiseManager.callbackDone(clientIf, status); } - void onClientDisable(int status, int client_if) throws RemoteException{ - if (DBG) Log.d(TAG, "onClientDisable() - client_if=" + client_if + // Callback when advertise instance is disabled + void onAdvertiseInstanceDisabled(int status, int clientIf) throws RemoteException { + if (DBG) Log.d(TAG, "onAdvertiseInstanceEnabled() - clientIf=" + clientIf + ", status=" + status); - - ClientMap.App app = mClientMap.getById(client_if); + ClientMap.App app = mClientMap.getById(clientIf); if (app != null) { Log.d(TAG, "Client app is not null!"); if (status == 0) { @@ -1387,40 +1384,24 @@ public class GattService extends ProfileService { gattClientDisconnectNative(clientIf, address, connId != null ? connId : 0); } - synchronized List getAdvServiceUuids() { - enforcePrivilegedPermission(); - boolean fullUuidFound = false; - List serviceUuids = new ArrayList(); - for (HandleMap.Entry entry : mHandleMap.mEntries) { - if (entry.advertisePreferred) { - ParcelUuid parcelUuid = new ParcelUuid(entry.uuid); - if (BluetoothUuid.is16BitUuid(parcelUuid)) { - serviceUuids.add(parcelUuid); - } else { - // Allow at most one 128 bit service uuid to be advertised. - if (!fullUuidFound) { - fullUuidFound = true; - serviceUuids.add(parcelUuid); - } - } - } - } - return serviceUuids; - } - void startMultiAdvertising(int clientIf, AdvertiseData advertiseData, AdvertiseData scanResponse, AdvertiseSettings settings) { - enforceAdminPermission(); - Message message = mStateMachine.obtainMessage(GattServiceStateMachine.START_ADVERTISING); - message.obj = new AdvertiseClient(clientIf, settings, advertiseData, scanResponse); - mStateMachine.sendMessage(message); + mAdvertiseManager.startAdvertising(new AdvertiseClient(clientIf, settings, advertiseData, + scanResponse)); } void stopMultiAdvertising(int clientIf) { - enforceAdminPermission(); - Message message = mStateMachine.obtainMessage(GattServiceStateMachine.STOP_ADVERTISING); - message.arg1 = clientIf; - mStateMachine.sendMessage(message); + mAdvertiseManager.stopAdvertising(new AdvertiseClient(clientIf)); + } + + + synchronized List getRegisteredServiceUuids() { + Utils.enforceAdminPermission(this); + List serviceUuids = new ArrayList(); + for (HandleMap.Entry entry : mHandleMap.mEntries) { + serviceUuids.add(new ParcelUuid(entry.uuid)); + } + return serviceUuids; } List getConnectedDevices() { diff --git a/src/com/android/bluetooth/gatt/GattServiceStateMachine.java b/src/com/android/bluetooth/gatt/GattServiceStateMachine.java index 9141d266..6c1a94f5 100644 --- a/src/com/android/bluetooth/gatt/GattServiceStateMachine.java +++ b/src/com/android/bluetooth/gatt/GattServiceStateMachine.java @@ -17,39 +17,25 @@ package com.android.bluetooth.gatt; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.le.AdvertiseCallback; -import android.bluetooth.le.AdvertiseSettings; -import android.bluetooth.le.AdvertiseData; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanSettings; import android.os.Message; -import android.os.ParcelUuid; -import android.os.RemoteException; import com.android.bluetooth.btservice.AdapterService; import com.android.internal.util.State; import com.android.internal.util.StateMachine; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.util.ArrayDeque; -import java.util.Arrays; import java.util.Deque; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; -import java.util.UUID; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; /** - * The state machine that handles state transitions for GATT related operations, including BLE scan, - * advertising and connection. + * The state machine that handles state transitions for BLE scan/ *

* Scan state transitions are Idle -> ScanStarting -> Scanning -> Idle. *

- * TODO:add connection states. move scan clients and related callbacks to state machine. * * @hide */ @@ -64,14 +50,8 @@ public class GattServiceStateMachine extends StateMachine { */ static final int STOP_BLE_SCAN = 2; - static final int START_ADVERTISING = 3; - static final int STOP_ADVERTISING = 4; - // Message for internal state transitions. static final int ENABLE_BLE_SCAN = 11; - static final int ENABLE_ADVERTISING = 12; - static final int SET_ADVERTISING_DATA = 13; - static final int CANCEL_ADVERTISING = 14; // The order to add BLE filters is enable filter -> add filter -> config filter. static final int ADD_BLE_SCAN_FILTER = 15; @@ -82,32 +62,6 @@ public class GattServiceStateMachine extends StateMachine { private static final String TAG = "GattServiceStateMachine"; private static final boolean DBG = true; - private static final int ADVERTISING_INTERVAL_HIGH_MILLS = 1000; - private static final int ADVERTISING_INTERVAL_MEDIUM_MILLS = 250; - private static final int ADVERTISING_INTERVAL_LOW_MILLS = 100; - // Add some randomness to the advertising min/max interval so the controller can do some - // optimization. - private static final int ADVERTISING_INTERVAL_DELTA_UNIT = 10; - private static final int ADVERTISING_INTERVAL_MICROS_PER_UNIT = 625; - - // The following constants should be kept the same as those defined in bt stack. - private static final int ADVERTISING_CHANNEL_37 = 1 << 0; - private static final int ADVERTISING_CHANNEL_38 = 1 << 1; - private static final int ADVERTISING_CHANNEL_39 = 1 << 2; - private static final int ADVERTISING_CHANNEL_ALL = - ADVERTISING_CHANNEL_37 | ADVERTISING_CHANNEL_38 | ADVERTISING_CHANNEL_39; - - private static final int ADVERTISING_TX_POWER_MIN = 0; - private static final int ADVERTISING_TX_POWER_LOW = 1; - private static final int ADVERTISING_TX_POWER_MID = 2; - private static final int ADVERTISING_TX_POWER_UPPER = 3; - private static final int ADVERTISING_TX_POWER_MAX = 4; - - // Note we don't expose connectable directed advertising to API. - private static final int ADVERTISING_EVENT_TYPE_CONNECTABLE = 0; - private static final int ADVERTISING_EVENT_TYPE_SCANNABLE = 2; - private static final int ADVERTISING_EVENT_TYPE_NON_CONNECTABLE = 3; - // Result type defined in bt stack. static final int SCAN_RESULT_TYPE_TRUNCATED = 1; static final int SCAN_RESULT_TYPE_FULL = 2; @@ -121,14 +75,12 @@ public class GattServiceStateMachine extends StateMachine { private static final int ALLOW_ALL_FILTER_SELECTION = 0; private final GattService mService; - private final Map mAdvertiseClients; // Keep track of whether scan filters exist. private boolean hasFilter = false; // All states for the state machine. private final Idle mIdle; private final ScanStarting mScanStarting; - private final AdvertiseStarting mAdvertiseStarting; // A count down latch used to block on stack callback. MUST reset before use. private CountDownLatch mCallbackLatch; @@ -144,14 +96,11 @@ public class GattServiceStateMachine extends StateMachine { // Add all possible states to the state machine. mScanStarting = new ScanStarting(); mIdle = new Idle(); - mAdvertiseStarting = new AdvertiseStarting(); - mAdvertiseClients = new HashMap(); mFilterIndexStack = new ArrayDeque(); mClientFilterIndexMap = new HashMap>(); addState(mIdle); addState(mScanStarting); - addState(mAdvertiseStarting); // Initial state is idle. setInitialState(mIdle); @@ -185,7 +134,7 @@ public class GattServiceStateMachine extends StateMachine { } /** - * {@link Idle} state is the state where there is no scanning or advertising activity. + * {@link Idle} state is the state where there is no scanning activity. */ private class Idle extends State { @Override @@ -226,47 +175,6 @@ public class GattServiceStateMachine extends StateMachine { gattClientScanNative(false); } break; - case START_ADVERTISING: - AdvertiseClient client = (AdvertiseClient) message.obj; - if (mAdvertiseClients.containsKey(client.clientIf)) { - // do something. - loge("advertising already started for client : " + client.clientIf); - try { - mService.onMultipleAdvertiseCallback(client.clientIf, - AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); - } catch (RemoteException e) { - loge("failed to start advertising", e); - } - transitionTo(mIdle); - break; - } - AdapterService adapter = AdapterService.getAdapterService(); - int numOfAdvtInstances = adapter.getNumOfAdvertisementInstancesSupported(); - if (mAdvertiseClients.size() >= numOfAdvtInstances) { - loge("too many advertisier, current size : " + mAdvertiseClients.size()); - try { - mService.onMultipleAdvertiseCallback(client.clientIf, - AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS); - } catch (RemoteException e) { - loge("failed to start advertising", e); - } - transitionTo(mIdle); - break; - } - newMessage = obtainMessage(ENABLE_ADVERTISING); - newMessage.obj = message.obj; - sendMessage(newMessage); - transitionTo(mAdvertiseStarting); - break; - case STOP_ADVERTISING: - clientIf = message.arg1; - if (mAdvertiseClients.containsKey(clientIf)) { - log("disabling client" + clientIf); - gattClientDisableAdvNative(clientIf); - mAdvertiseClients.remove(clientIf); - } - break; - default: return NOT_HANDLED; } @@ -479,146 +387,6 @@ public class GattServiceStateMachine extends StateMachine { } } - private class AdvertiseStarting extends State { - - @Override - public void enter() { - if (DBG) { - log("enter advertising state: " + getCurrentMessage().what); - } - } - - @Override - public boolean processMessage(Message message) { - log("advertising starting " + message.what); - switch (message.what) { - case START_ADVERTISING: - case STOP_ADVERTISING: - deferMessage(message); - break; - case ENABLE_ADVERTISING: - AdvertiseClient client = (AdvertiseClient) message.obj; - mAdvertiseClients.put(client.clientIf, client); - enableAdvertising(client); - sendMessageDelayed(OPERATION_TIMEOUT, client.clientIf, TIMEOUT_MILLIS); - break; - case SET_ADVERTISING_DATA: - int clientIf = message.arg1; - log("setting advertisement: " + clientIf); - client = mAdvertiseClients.get(clientIf); - setAdvertisingData(clientIf, client.advertiseData, false); - if (client.scanResponse != null) { - setAdvertisingData(clientIf, client.scanResponse, true); - } - removeMessages(OPERATION_TIMEOUT); - transitionTo(mIdle); - break; - case CANCEL_ADVERTISING: - case OPERATION_TIMEOUT: - clientIf = message.arg1; - try { - mService.onMultipleAdvertiseCallback(clientIf, - AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); - } catch (RemoteException e) { - loge("failed to start advertising", e); - } - transitionTo(mIdle); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - private void setAdvertisingData(int clientIf, AdvertiseData data, boolean isScanResponse) { - if (data == null) { - return; - } - boolean includeName = false; - boolean includeTxPower = data.getIncludeTxPowerLevel(); - int appearance = 0; - byte[] manufacturerData = data.getManufacturerSpecificData() == null ? new byte[0] - : data.getManufacturerSpecificData(); - byte[] serviceData = data.getServiceData() == null ? new byte[0] : data.getServiceData(); - - byte[] serviceUuids; - if (data.getServiceUuids() == null) { - serviceUuids = new byte[0]; - } else { - ByteBuffer advertisingUuidBytes = ByteBuffer.allocate( - data.getServiceUuids().size() * 16) - .order(ByteOrder.LITTLE_ENDIAN); - for (ParcelUuid parcelUuid : data.getServiceUuids()) { - UUID uuid = parcelUuid.getUuid(); - // Least significant bits first as the advertising uuid should be in little-endian. - advertisingUuidBytes.putLong(uuid.getLeastSignificantBits()) - .putLong(uuid.getMostSignificantBits()); - } - serviceUuids = advertisingUuidBytes.array(); - } - log("isScanResponse " + isScanResponse + " manu data " + Arrays.toString(manufacturerData)); - log("include tx power " + includeTxPower); - gattClientSetAdvDataNative(clientIf, isScanResponse, includeName, includeTxPower, - appearance, - manufacturerData, serviceData, serviceUuids); - } - - private void enableAdvertising(AdvertiseClient client) { - int clientIf = client.clientIf; - log("enabling advertisement: " + clientIf); - int minAdvertiseUnit = (int) getAdvertisingIntervalUnit(client.settings); - int maxAdvertiseUnit = minAdvertiseUnit + ADVERTISING_INTERVAL_DELTA_UNIT; - log("enabling advertising: " + clientIf + "minAdvertisingMills " + minAdvertiseUnit); - gattClientEnableAdvNative( - clientIf, - minAdvertiseUnit, maxAdvertiseUnit, - getAdvertisingEventType(client.settings), - ADVERTISING_CHANNEL_ALL, - getTxPowerLevel(client.settings)); - } - - // Convert settings tx power level to stack tx power level. - private int getTxPowerLevel(AdvertiseSettings settings) { - switch (settings.getTxPowerLevel()) { - case AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW: - return ADVERTISING_TX_POWER_MIN; - case AdvertiseSettings.ADVERTISE_TX_POWER_LOW: - return ADVERTISING_TX_POWER_LOW; - case AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM: - return ADVERTISING_TX_POWER_MID; - case AdvertiseSettings.ADVERTISE_TX_POWER_HIGH: - return ADVERTISING_TX_POWER_UPPER; - default: - // Shouldn't happen, just in case. - return ADVERTISING_TX_POWER_MID; - } - } - - // Convert advertising event type to stack advertising event type. - private int getAdvertisingEventType(AdvertiseSettings settings) { - // TODO: Check if we have scan response data to control SCANABLE - // TODO: Also check for limited discovery and set flag to LIMITED here? - if (settings.getIsConnectable()) - return ADVERTISING_EVENT_TYPE_CONNECTABLE; - return ADVERTISING_EVENT_TYPE_NON_CONNECTABLE; - } - - // Convert advertising milliseconds to advertising units(one unit is 0.625 millisecond). - private long getAdvertisingIntervalUnit(AdvertiseSettings settings) { - switch (settings.getMode()) { - case AdvertiseSettings.ADVERTISE_MODE_LOW_POWER: - return millsToUnit(ADVERTISING_INTERVAL_HIGH_MILLS); - case AdvertiseSettings.ADVERTISE_MODE_BALANCED: - return millsToUnit(ADVERTISING_INTERVAL_MEDIUM_MILLS); - case AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY: - return millsToUnit(ADVERTISING_INTERVAL_LOW_MILLS); - default: - // Shouldn't happen, just in case. - return millsToUnit(ADVERTISING_INTERVAL_HIGH_MILLS); - } - } - /** * Return batch scan result type value defined in bt stack. */ @@ -646,27 +414,9 @@ public class GattServiceStateMachine extends StateMachine { : DELIVERY_MODE_BATCH; } - private long millsToUnit(int millisecond) { - return TimeUnit.MILLISECONDS.toMicros(millisecond) / ADVERTISING_INTERVAL_MICROS_PER_UNIT; - } - // Native functions. private native void gattClientScanNative(boolean start); - private native void gattClientEnableAdvNative(int client_if, - int min_interval, int max_interval, int adv_type, int chnl_map, - int tx_power); - - private native void gattClientUpdateAdvNative(int client_if, - int min_interval, int max_interval, int adv_type, int chnl_map, - int tx_power); - - private native void gattClientSetAdvDataNative(int client_if, - boolean set_scan_rsp, boolean incl_name, boolean incl_txpower, int appearance, - byte[] manufacturer_data, byte[] service_data, byte[] service_uuid); - - private native void gattClientDisableAdvNative(int client_if); - private native void gattClientScanFilterAddNative(int client_if, int filter_type, int filter_index, int company_id, int company_id_mask, long uuid_lsb, long uuid_msb, -- 2.11.0