Loading android/app/jni/com_android_bluetooth_gatt.cpp +15 −8 Original line number Diff line number Diff line Loading @@ -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"); Loading Loading @@ -1709,6 +1709,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}, Loading @@ -1722,10 +1730,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. Loading Loading @@ -1779,6 +1783,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)); Loading android/app/src/com/android/bluetooth/Utils.java +40 −20 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; } Loading @@ -60,7 +61,7 @@ final public class Utils { 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++; } Loading Loading @@ -115,8 +116,8 @@ 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(); } Loading @@ -133,22 +134,28 @@ 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 { 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; Loading Loading @@ -197,4 +204,17 @@ final public class Utils { } 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"); } } android/app/src/com/android/bluetooth/gatt/AdvertiseClient.java +36 −2 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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); } } android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java 0 → 100644 +429 −0 Original line number Diff line number Diff line /* * 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<AdvertiseClient> 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<AdvertiseClient>(); 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<ParcelUuid> 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<ParcelUuid> registeredUuids, AdvertiseData advertiseData) { if (advertiseData == null) { return true; } List<ParcelUuid> 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); } } android/app/src/com/android/bluetooth/gatt/GattService.java +42 −61 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
android/app/jni/com_android_bluetooth_gatt.cpp +15 −8 Original line number Diff line number Diff line Loading @@ -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"); Loading Loading @@ -1709,6 +1709,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}, Loading @@ -1722,10 +1730,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. Loading Loading @@ -1779,6 +1783,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)); Loading
android/app/src/com/android/bluetooth/Utils.java +40 −20 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; } Loading @@ -60,7 +61,7 @@ final public class Utils { 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++; } Loading Loading @@ -115,8 +116,8 @@ 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(); } Loading @@ -133,22 +134,28 @@ 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 { 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; Loading Loading @@ -197,4 +204,17 @@ final public class Utils { } 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"); } }
android/app/src/com/android/bluetooth/gatt/AdvertiseClient.java +36 −2 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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); } }
android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java 0 → 100644 +429 −0 Original line number Diff line number Diff line /* * 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<AdvertiseClient> 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<AdvertiseClient>(); 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<ParcelUuid> 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<ParcelUuid> registeredUuids, AdvertiseData advertiseData) { if (advertiseData == null) { return true; } List<ParcelUuid> 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); } }
android/app/src/com/android/bluetooth/gatt/GattService.java +42 −61 File changed.Preview size limit exceeded, changes collapsed. Show changes