aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKolin Lu <kolinlu@google.com>2023-06-01 20:27:31 -0700
committerGitHub <noreply@github.com>2023-06-01 20:27:31 -0700
commita90f9834693f240ec8d9af0e7c45bca3f6f46313 (patch)
tree5716a8e237f87c095a4b9f69b1cbc72bf1be2da4
parentfbd882fa01fb2134303c697195ab93b739e4ee87 (diff)
parent83e457bfa4b2961b5a1aff888a48fd818e354215 (diff)
downloadmobly-bundled-snippets-a90f9834693f240ec8d9af0e7c45bca3f6f46313.tar.gz
Merge pull request #167 from ko1in1u/master
Support more features on BLE related API.
-rw-r--r--src/main/AndroidManifest.xml5
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/BluetoothLeAdvertiserSnippet.java37
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/BluetoothLeScannerSnippet.java50
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothGattClientSnippet.java218
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothGattServerSnippet.java193
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/utils/DataHolder.java38
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonDeserializer.java53
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java69
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/utils/MbsEnums.java164
-rw-r--r--src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java16
10 files changed, 830 insertions, 13 deletions
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index afec31b..adf6c64 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -5,6 +5,8 @@
<uses-feature android:name="android.hardware.telephony" android:required="false" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
@@ -12,6 +14,7 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
@@ -38,6 +41,8 @@
android:value="com.google.android.mobly.snippet.bundled.AccountSnippet,
com.google.android.mobly.snippet.bundled.AudioSnippet,
com.google.android.mobly.snippet.bundled.bluetooth.BluetoothAdapterSnippet,
+ com.google.android.mobly.snippet.bundled.bluetooth.BluetoothGattClientSnippet,
+ com.google.android.mobly.snippet.bundled.bluetooth.BluetoothGattServerSnippet,
com.google.android.mobly.snippet.bundled.bluetooth.profiles.BluetoothA2dpSnippet,
com.google.android.mobly.snippet.bundled.bluetooth.profiles.BluetoothHearingAidSnippet,
com.google.android.mobly.snippet.bundled.BluetoothLeAdvertiserSnippet,
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothLeAdvertiserSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothLeAdvertiserSnippet.java
index e161a5b..4b2c5b5 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothLeAdvertiserSnippet.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothLeAdvertiserSnippet.java
@@ -34,6 +34,7 @@ import com.google.android.mobly.snippet.event.SnippetEvent;
import com.google.android.mobly.snippet.rpc.AsyncRpc;
import com.google.android.mobly.snippet.rpc.Rpc;
import com.google.android.mobly.snippet.rpc.RpcMinSdk;
+import com.google.android.mobly.snippet.rpc.RpcOptional;
import com.google.android.mobly.snippet.util.Log;
import java.util.HashMap;
import org.json.JSONException;
@@ -76,7 +77,27 @@ public class BluetoothLeAdvertiserSnippet implements Snippet {
* }
* </pre>
*
- * @param advertiseData A JSONObject representing a {@link AdvertiseData} object. E.g.
+ * @param advertiseData A JSONObject representing a {@link AdvertiseData} object will be
+ * broadcast if the operation succeeds. E.g.
+ * <pre>
+ * {
+ * "IncludeDeviceName": (bool),
+ * # JSON list, each element representing a set of service data, which is composed of
+ * # a UUID, and an optional string.
+ * "ServiceData": [
+ * {
+ * "UUID": (A string representation of {@link ParcelUuid}),
+ * "Data": (Optional, The string representation of what you want to
+ * advertise, base64 encoded)
+ * # If you want to add a UUID without data, simply omit the "Data"
+ * # field.
+ * }
+ * ]
+ * }
+ * </pre>
+ *
+ * @param scanResponse A JSONObject representing a {@link AdvertiseData} object which will
+ * response the data to the scanning device. E.g.
* <pre>
* {
* "IncludeDeviceName": (bool),
@@ -100,7 +121,10 @@ public class BluetoothLeAdvertiserSnippet implements Snippet {
@RpcMinSdk(Build.VERSION_CODES.LOLLIPOP_MR1)
@AsyncRpc(description = "Start BLE advertising.")
public void bleStartAdvertising(
- String callbackId, JSONObject advertiseSettings, JSONObject advertiseData)
+ String callbackId,
+ JSONObject advertiseSettings,
+ JSONObject advertiseData,
+ @RpcOptional JSONObject scanResponse)
throws BluetoothLeAdvertiserSnippetException, JSONException {
if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
throw new BluetoothLeAdvertiserSnippetException(
@@ -109,7 +133,12 @@ public class BluetoothLeAdvertiserSnippet implements Snippet {
AdvertiseSettings settings = JsonDeserializer.jsonToBleAdvertiseSettings(advertiseSettings);
AdvertiseData data = JsonDeserializer.jsonToBleAdvertiseData(advertiseData);
AdvertiseCallback advertiseCallback = new DefaultAdvertiseCallback(callbackId);
- mAdvertiser.startAdvertising(settings, data, advertiseCallback);
+ if (scanResponse == null) {
+ mAdvertiser.startAdvertising(settings, data, advertiseCallback);
+ } else {
+ AdvertiseData response = JsonDeserializer.jsonToBleAdvertiseData(scanResponse);
+ mAdvertiser.startAdvertising(settings, data, response, advertiseCallback);
+ }
mAdvertiseCallbacks.put(callbackId, advertiseCallback);
}
@@ -150,6 +179,7 @@ public class BluetoothLeAdvertiserSnippet implements Snippet {
mCallbackId = callbackId;
}
+ @Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.e("Bluetooth LE advertising started with settings: " + settingsInEffect.toString());
SnippetEvent event = new SnippetEvent(mCallbackId, "onStartSuccess");
@@ -159,6 +189,7 @@ public class BluetoothLeAdvertiserSnippet implements Snippet {
sEventCache.postEvent(event);
}
+ @Override
public void onStartFailure(int errorCode) {
Log.e("Bluetooth LE advertising failed to start with error code: " + errorCode);
SnippetEvent event = new SnippetEvent(mCallbackId, "onStartFailure");
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothLeScannerSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothLeScannerSnippet.java
index 7e133d1..622556f 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothLeScannerSnippet.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/BluetoothLeScannerSnippet.java
@@ -20,10 +20,13 @@ import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
import android.os.Build;
import android.os.Bundle;
import com.google.android.mobly.snippet.Snippet;
+import com.google.android.mobly.snippet.bundled.utils.JsonDeserializer;
import com.google.android.mobly.snippet.bundled.utils.JsonSerializer;
import com.google.android.mobly.snippet.bundled.utils.MbsEnums;
import com.google.android.mobly.snippet.event.EventCache;
@@ -31,10 +34,14 @@ import com.google.android.mobly.snippet.event.SnippetEvent;
import com.google.android.mobly.snippet.rpc.AsyncRpc;
import com.google.android.mobly.snippet.rpc.Rpc;
import com.google.android.mobly.snippet.rpc.RpcMinSdk;
+import com.google.android.mobly.snippet.rpc.RpcOptional;
import com.google.android.mobly.snippet.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
/** Snippet class exposing Android APIs in WifiManager. */
@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
@@ -51,6 +58,7 @@ public class BluetoothLeScannerSnippet implements Snippet {
private final EventCache mEventCache = EventCache.getInstance();
private final HashMap<String, ScanCallback> mScanCallbacks = new HashMap<>();
private final JsonSerializer mJsonSerializer = new JsonSerializer();
+ private long bleScanStartTime = 0;
public BluetoothLeScannerSnippet() {
mScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
@@ -60,17 +68,49 @@ public class BluetoothLeScannerSnippet implements Snippet {
* Start a BLE scan.
*
* @param callbackId
+ * @param scanFilters A JSONArray representing a list of {@link ScanFilter} object for finding
+ * exact BLE devices. E.g.
+ * <pre>
+ * [
+ * {
+ * "ServiceUuid": (A string representation of {@link ParcelUuid}),
+ * },
+ * ]
+ * </pre>
+ *
+ * @param scanSettings A JSONObject representing a {@link ScanSettings} object which is the
+ * Settings for the scan. E.g.
+ * <pre>
+ * {
+ * 'ScanMode': 'SCAN_MODE_LOW_LATENCY',
+ * }
+ * </pre>
+ *
* @throws BluetoothLeScanSnippetException
*/
@RpcMinSdk(Build.VERSION_CODES.LOLLIPOP_MR1)
@AsyncRpc(description = "Start BLE scan.")
- public void bleStartScan(String callbackId) throws BluetoothLeScanSnippetException {
+ public void bleStartScan(
+ String callbackId,
+ @RpcOptional JSONArray scanFilters,
+ @RpcOptional JSONObject scanSettings)
+ throws BluetoothLeScanSnippetException, JSONException {
if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
throw new BluetoothLeScanSnippetException(
"Bluetooth is disabled, cannot start BLE scan.");
}
DefaultScanCallback callback = new DefaultScanCallback(callbackId);
- mScanner.startScan(callback);
+ if (scanFilters == null && scanSettings == null) {
+ mScanner.startScan(callback);
+ } else {
+ ArrayList<ScanFilter> filters = new ArrayList<>();
+ for (int i = 0; i < scanFilters.length(); i++) {
+ filters.add(JsonDeserializer.jsonToScanFilter(scanFilters.getJSONObject(i)));
+ }
+ ScanSettings settings = JsonDeserializer.jsonToScanSettings(scanSettings);
+ mScanner.startScan(filters, settings, callback);
+ }
+ bleScanStartTime = System.currentTimeMillis();
mScanCallbacks.put(callbackId, callback);
}
@@ -106,16 +146,21 @@ public class BluetoothLeScannerSnippet implements Snippet {
mCallbackId = callbackId;
}
+ @Override
public void onScanResult(int callbackType, ScanResult result) {
Log.i("Got Bluetooth LE scan result.");
+ long bleScanOnResultTime = System.currentTimeMillis();
SnippetEvent event = new SnippetEvent(mCallbackId, "onScanResult");
String callbackTypeString =
MbsEnums.BLE_SCAN_RESULT_CALLBACK_TYPE.getString(callbackType);
event.getData().putString("CallbackType", callbackTypeString);
event.getData().putBundle("result", mJsonSerializer.serializeBleScanResult(result));
+ event.getData()
+ .putLong("StartToResultTimeDeltaMs", bleScanOnResultTime - bleScanStartTime);
mEventCache.postEvent(event);
}
+ @Override
public void onBatchScanResults(List<ScanResult> results) {
Log.i("Got Bluetooth LE batch scan results.");
SnippetEvent event = new SnippetEvent(mCallbackId, "onBatchScanResult");
@@ -127,6 +172,7 @@ public class BluetoothLeScannerSnippet implements Snippet {
mEventCache.postEvent(event);
}
+ @Override
public void onScanFailed(int errorCode) {
Log.e("Bluetooth LE scan failed with error code: " + errorCode);
SnippetEvent event = new SnippetEvent(mCallbackId, "onScanFailed");
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothGattClientSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothGattClientSnippet.java
new file mode 100644
index 0000000..14ec348
--- /dev/null
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothGattClientSnippet.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2023 Google Inc.
+ *
+ * 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.google.android.mobly.snippet.bundled.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Base64;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.google.android.mobly.snippet.Snippet;
+import com.google.android.mobly.snippet.bundled.utils.JsonSerializer;
+import com.google.android.mobly.snippet.bundled.utils.MbsEnums;
+import com.google.android.mobly.snippet.event.EventCache;
+import com.google.android.mobly.snippet.event.SnippetEvent;
+import com.google.android.mobly.snippet.rpc.AsyncRpc;
+import com.google.android.mobly.snippet.rpc.Rpc;
+import com.google.android.mobly.snippet.rpc.RpcMinSdk;
+import com.google.android.mobly.snippet.util.Log;
+import java.util.ArrayList;
+import java.util.HashMap;
+import org.json.JSONException;
+
+/** Snippet class exposing Android APIs in BluetoothGatt. */
+public class BluetoothGattClientSnippet implements Snippet {
+ private static class BluetoothGattClientSnippetException extends Exception {
+ private static final long serialVersionUID = 1;
+
+ public BluetoothGattClientSnippetException(String msg) {
+ super(msg);
+ }
+ }
+
+ private final Context context;
+ private final EventCache eventCache;
+ private final HashMap<String, HashMap<String, BluetoothGattCharacteristic>>
+ characteristicHashMap;
+
+ private BluetoothGatt bluetoothGattClient;
+
+ private long connectionStartTime = 0;
+ private long connectionEndTime = 0;
+
+ public BluetoothGattClientSnippet() {
+ context = InstrumentationRegistry.getInstrumentation().getContext();
+ eventCache = EventCache.getInstance();
+ characteristicHashMap = new HashMap<>();
+ }
+
+ @RpcMinSdk(VERSION_CODES.LOLLIPOP)
+ @AsyncRpc(description = "Start BLE client.")
+ public void bleConnectGatt(String callbackId, String deviceAddress) throws JSONException {
+ BluetoothDevice remoteDevice =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice(deviceAddress);
+ BluetoothGattCallback gattCallback = new DefaultBluetoothGattCallback(callbackId);
+ connectionStartTime = System.currentTimeMillis();
+ bluetoothGattClient = remoteDevice.connectGatt(context, false, gattCallback);
+ Log.d("Connection start time is " + connectionStartTime);
+ connectionEndTime = 0;
+ }
+
+ @RpcMinSdk(VERSION_CODES.LOLLIPOP)
+ @Rpc(description = "Start BLE service discovery")
+ public long bleDiscoverServices() throws BluetoothGattClientSnippetException {
+ if (bluetoothGattClient == null) {
+ throw new BluetoothGattClientSnippetException("BLE client is not initialized.");
+ }
+ long discoverServicesStartTime = SystemClock.elapsedRealtimeNanos();
+ Log.d("Discover services start time is " + discoverServicesStartTime);
+ boolean result = bluetoothGattClient.discoverServices();
+ if (!result) {
+ throw new BluetoothGattClientSnippetException("Discover services returned false.");
+ }
+ return discoverServicesStartTime;
+ }
+
+ @RpcMinSdk(VERSION_CODES.LOLLIPOP)
+ @Rpc(description = "Stop BLE client.")
+ public void bleDisconnect() throws BluetoothGattClientSnippetException {
+ if (bluetoothGattClient == null) {
+ throw new BluetoothGattClientSnippetException("BLE client is not initialized.");
+ }
+ bluetoothGattClient.disconnect();
+ }
+
+ @RpcMinSdk(VERSION_CODES.LOLLIPOP)
+ @Rpc(description = "BLE read operation.")
+ public boolean bleReadOperation(String serviceUuid, String characteristicUuid)
+ throws JSONException, BluetoothGattClientSnippetException {
+ if (bluetoothGattClient == null) {
+ throw new BluetoothGattClientSnippetException("BLE client is not initialized.");
+ }
+ boolean result =
+ bluetoothGattClient.readCharacteristic(
+ characteristicHashMap.get(serviceUuid).get(characteristicUuid));
+ Log.d("Read operation returned result " + result);
+ return result;
+ }
+
+ @RpcMinSdk(VERSION_CODES.LOLLIPOP)
+ @Rpc(description = "BLE write operation.")
+ public boolean bleWriteOperation(String serviceUuid, String characteristicUuid, String data)
+ throws JSONException, BluetoothGattClientSnippetException {
+ if (bluetoothGattClient == null) {
+ throw new BluetoothGattClientSnippetException("BLE client is not initialized.");
+ }
+ BluetoothGattCharacteristic characteristic =
+ characteristicHashMap.get(serviceUuid).get(characteristicUuid);
+ characteristic.setValue(Base64.decode(data, Base64.NO_WRAP));
+ boolean result = bluetoothGattClient.writeCharacteristic(characteristic);
+ Log.d("Write operation returned result " + result);
+ return result;
+ }
+
+ private class DefaultBluetoothGattCallback extends BluetoothGattCallback {
+ private final String callbackId;
+
+ DefaultBluetoothGattCallback(String callbackId) {
+ this.callbackId = callbackId;
+ }
+
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ SnippetEvent event = new SnippetEvent(callbackId, "onConnectionStateChange");
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ connectionEndTime = System.currentTimeMillis();
+ event.getData().putLong(
+ "gattConnectionTimeMs", connectionEndTime - connectionStartTime);
+ Log.d("Connection end time is " + connectionEndTime);
+ }
+ event.getData().putString("status", MbsEnums.BLE_STATUS_TYPE.getString(status));
+ event.getData().putString("newState", MbsEnums.BLE_CONNECT_STATUS.getString(newState));
+ event.getData().putBundle("gatt", JsonSerializer.serializeBluetoothGatt(gatt));
+ eventCache.postEvent(event);
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ long discoverServicesEndTime = SystemClock.elapsedRealtimeNanos();
+ Log.d("Discover services end time is " + discoverServicesEndTime);
+ SnippetEvent event = new SnippetEvent(callbackId, "onServiceDiscovered");
+ event.getData().putString("status", MbsEnums.BLE_STATUS_TYPE.getString(status));
+ ArrayList<Bundle> services = new ArrayList<>();
+ for (BluetoothGattService service : gatt.getServices()) {
+ HashMap<String, BluetoothGattCharacteristic> characteristics = new HashMap<>();
+ for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
+ characteristics.put(characteristic.getUuid().toString(), characteristic);
+ }
+ characteristicHashMap.put(service.getUuid().toString(), characteristics);
+ services.add(JsonSerializer.serializeBluetoothGattService(service));
+ }
+ // TODO(66740428): Should not return services directly
+ event.getData().putParcelableArrayList("Services", services);
+ event.getData().putBundle("gatt", JsonSerializer.serializeBluetoothGatt(gatt));
+ event.getData().putLong("discoveryServicesEndTime", discoverServicesEndTime);
+ eventCache.postEvent(event);
+ }
+
+ @Override
+ public void onCharacteristicRead(
+ BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ SnippetEvent event = new SnippetEvent(callbackId, "onCharacteristicRead");
+ event.getData().putString("status", MbsEnums.BLE_STATUS_TYPE.getString(status));
+ // TODO(66740428): Should return the characteristic instead of value
+ event.getData()
+ .putString("Data",
+ Base64.encodeToString(characteristic.getValue(), Base64.NO_WRAP));
+ event.getData().putBundle("gatt", JsonSerializer.serializeBluetoothGatt(gatt));
+ eventCache.postEvent(event);
+ }
+
+ @Override
+ public void onCharacteristicWrite(
+ BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ SnippetEvent event = new SnippetEvent(callbackId, "onCharacteristicWrite");
+ event.getData().putString("status", MbsEnums.BLE_STATUS_TYPE.getString(status));
+ // TODO(66740428): Should return the characteristic instead of value
+ event.getData().putBundle("gatt", JsonSerializer.serializeBluetoothGatt(gatt));
+ eventCache.postEvent(event);
+ }
+
+ @Override
+ public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
+ SnippetEvent event = new SnippetEvent(callbackId, "onReliableWriteCompleted");
+ event.getData().putString("status", MbsEnums.BLE_STATUS_TYPE.getString(status));
+ event.getData().putBundle("gatt", JsonSerializer.serializeBluetoothGatt(gatt));
+ eventCache.postEvent(event);
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ if (bluetoothGattClient != null) {
+ bluetoothGattClient.close();
+ }
+ }
+}
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothGattServerSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothGattServerSnippet.java
new file mode 100644
index 0000000..29222eb
--- /dev/null
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothGattServerSnippet.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2023 Google Inc.
+ *
+ * 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.google.android.mobly.snippet.bundled.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.os.Build.VERSION_CODES;
+import android.os.DeadObjectException;
+import android.os.SystemClock;
+import android.util.Base64;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.google.android.mobly.snippet.Snippet;
+import com.google.android.mobly.snippet.bundled.utils.DataHolder;
+import com.google.android.mobly.snippet.bundled.utils.JsonDeserializer;
+import com.google.android.mobly.snippet.bundled.utils.JsonSerializer;
+import com.google.android.mobly.snippet.bundled.utils.MbsEnums;
+import com.google.android.mobly.snippet.event.EventCache;
+import com.google.android.mobly.snippet.event.SnippetEvent;
+import com.google.android.mobly.snippet.rpc.AsyncRpc;
+import com.google.android.mobly.snippet.rpc.Rpc;
+import com.google.android.mobly.snippet.rpc.RpcMinSdk;
+import com.google.android.mobly.snippet.util.Log;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** Snippet class exposing Android APIs in BluetoothGattServer. */
+public class BluetoothGattServerSnippet implements Snippet {
+ private static class BluetoothGattServerSnippetException extends Exception {
+ private static final long serialVersionUID = 1;
+
+ public BluetoothGattServerSnippetException(String msg) {
+ super(msg);
+ }
+ }
+
+ private final Context context;
+ private final BluetoothManager bluetoothManager;
+ private final DataHolder dataHolder;
+ private final EventCache eventCache;
+
+ private BluetoothGattServer bluetoothGattServer;
+
+ public BluetoothGattServerSnippet() {
+ context = InstrumentationRegistry.getInstrumentation().getContext();
+ bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
+ dataHolder = new DataHolder();
+ eventCache = EventCache.getInstance();
+ }
+
+ @RpcMinSdk(VERSION_CODES.LOLLIPOP)
+ @AsyncRpc(description = "Start BLE server.")
+ public void bleStartServer(String callbackId, JSONArray services)
+ throws JSONException, DeadObjectException {
+ BluetoothGattServerCallback gattServerCallback =
+ new DefaultBluetoothGattServerCallback(callbackId);
+ bluetoothGattServer = bluetoothManager.openGattServer(context, gattServerCallback);
+ addServiceToGattServer(services);
+ }
+
+ @RpcMinSdk(VERSION_CODES.LOLLIPOP)
+ @AsyncRpc(description = "Start BLE server with workaround.")
+ public void bleStartServerWithWorkaround(String callbackId, JSONArray services)
+ throws JSONException, DeadObjectException {
+ BluetoothGattServerCallback gattServerCallback =
+ new DefaultBluetoothGattServerCallback(callbackId);
+ boolean isGattServerStarted = false;
+ int count = 0;
+ while (!isGattServerStarted && count < 5) {
+ bluetoothGattServer = bluetoothManager.openGattServer(context, gattServerCallback);
+ if (bluetoothGattServer != null) {
+ addServiceToGattServer(services);
+ isGattServerStarted = true;
+ } else {
+ SystemClock.sleep(1000);
+ count++;
+ }
+ }
+ }
+
+ private void addServiceToGattServer(JSONArray services) throws JSONException {
+ for (int i = 0; i < services.length(); i++) {
+ JSONObject service = services.getJSONObject(i);
+ BluetoothGattService bluetoothGattService =
+ JsonDeserializer.jsonToBluetoothGattService(dataHolder, service);
+ bluetoothGattServer.addService(bluetoothGattService);
+ }
+ }
+
+ @RpcMinSdk(VERSION_CODES.LOLLIPOP)
+ @Rpc(description = "Stop BLE server.")
+ public void bleStopServer() throws BluetoothGattServerSnippetException {
+ if (bluetoothGattServer == null) {
+ throw new BluetoothGattServerSnippetException("BLE server is not initialized.");
+ }
+ bluetoothGattServer.close();
+ }
+
+ private class DefaultBluetoothGattServerCallback extends BluetoothGattServerCallback {
+ private final String callbackId;
+
+ DefaultBluetoothGattServerCallback(String callbackId) {
+ this.callbackId = callbackId;
+ }
+
+ @Override
+ public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+ SnippetEvent event = new SnippetEvent(callbackId, "onConnectionStateChange");
+ event.getData().putBundle("device", JsonSerializer.serializeBluetoothDevice(device));
+ event.getData().putString("status", MbsEnums.BLE_STATUS_TYPE.getString(status));
+ event.getData().putString("newState", MbsEnums.BLE_CONNECT_STATUS.getString(newState));
+ eventCache.postEvent(event);
+ }
+
+ @Override
+ public void onServiceAdded(int status, BluetoothGattService service) {
+ Log.d("Bluetooth Gatt Server service added with status " + status);
+ SnippetEvent event = new SnippetEvent(callbackId, "onServiceAdded");
+ event.getData().putString("status", MbsEnums.BLE_STATUS_TYPE.getString(status));
+ event.getData()
+ .putParcelable("Service",
+ JsonSerializer.serializeBluetoothGattService(service));
+ eventCache.postEvent(event);
+ }
+
+ @Override
+ public void onCharacteristicReadRequest(
+ BluetoothDevice device,
+ int requestId,
+ int offset,
+ BluetoothGattCharacteristic characteristic) {
+ Log.d("Bluetooth Gatt Server received a read request");
+ if (dataHolder.get(characteristic) != null) {
+ bluetoothGattServer.sendResponse(
+ device,
+ requestId,
+ BluetoothGatt.GATT_SUCCESS,
+ offset,
+ Base64.decode(dataHolder.get(characteristic), Base64.NO_WRAP));
+ } else {
+ bluetoothGattServer.sendResponse(
+ device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
+ }
+ }
+
+ @Override
+ public void onCharacteristicWriteRequest(
+ BluetoothDevice device,
+ int requestId,
+ BluetoothGattCharacteristic characteristic,
+ boolean preparedWrite,
+ boolean responseNeeded,
+ int offset,
+ byte[] value) {
+ Log.d("Bluetooth Gatt Server received a write request");
+ bluetoothGattServer.sendResponse(
+ device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
+ SnippetEvent event = new SnippetEvent(callbackId, "onCharacteristicWriteRequest");
+ event.getData().putString("Data", Base64.encodeToString(value, Base64.NO_WRAP));
+ eventCache.postEvent(event);
+ }
+
+ @Override
+ public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
+ Log.d("Bluetooth Gatt Server received an execute write request");
+ bluetoothGattServer.sendResponse(
+ device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
+ }
+ }
+
+ @Override
+ public void shutdown() {}
+}
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/DataHolder.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/DataHolder.java
new file mode 100644
index 0000000..021a6ba
--- /dev/null
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/DataHolder.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 Google Inc.
+ *
+ * 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.google.android.mobly.snippet.bundled.utils;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import java.util.HashMap;
+
+/** A holder to hold android objects for snippets. */
+// TODO(ko1in1u): For future extensions between Snippet classes and Utils.
+public class DataHolder {
+ private final HashMap<BluetoothGattCharacteristic, String> dataToBeRead;
+
+ public DataHolder() {
+ dataToBeRead = new HashMap<>();
+ }
+
+ public String get(BluetoothGattCharacteristic characteristic) {
+ return dataToBeRead.get(characteristic);
+ }
+
+ public void insertData(BluetoothGattCharacteristic characteristic, String string) {
+ dataToBeRead.put(characteristic, string);
+ }
+}
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonDeserializer.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonDeserializer.java
index 2f943e0..a3d5325 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonDeserializer.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonDeserializer.java
@@ -17,12 +17,17 @@
package com.google.android.mobly.snippet.bundled.utils;
import android.annotation.TargetApi;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanSettings;
import android.net.wifi.WifiConfiguration;
import android.os.Build;
import android.os.ParcelUuid;
import android.util.Base64;
+import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -101,4 +106,52 @@ public class JsonDeserializer {
}
return builder.build();
}
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static BluetoothGattService jsonToBluetoothGattService(
+ DataHolder dataHolder, JSONObject jsonObject) throws JSONException {
+ BluetoothGattService service =
+ new BluetoothGattService(
+ UUID.fromString(jsonObject.getString("UUID")),
+ MbsEnums.BLE_SERVICE_TYPE.getInt(jsonObject.getString("Type")));
+ JSONArray characteristics = jsonObject.getJSONArray("Characteristics");
+ for (int i = 0; i < characteristics.length(); i++) {
+ BluetoothGattCharacteristic characteristic =
+ jsonToBluetoothGattCharacteristic(dataHolder, characteristics.getJSONObject(i));
+ service.addCharacteristic(characteristic);
+ }
+ return service;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static BluetoothGattCharacteristic jsonToBluetoothGattCharacteristic(
+ DataHolder dataHolder, JSONObject jsonObject) throws JSONException {
+ BluetoothGattCharacteristic characteristic =
+ new BluetoothGattCharacteristic(
+ UUID.fromString(jsonObject.getString("UUID")),
+ MbsEnums.BLE_PROPERTY_TYPE.getInt(jsonObject.getString("Property")),
+ MbsEnums.BLE_PERMISSION_TYPE.getInt(jsonObject.getString("Permission")));
+ if (jsonObject.has("Data")) {
+ dataHolder.insertData(characteristic, jsonObject.getString("Data"));
+ }
+ return characteristic;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static ScanFilter jsonToScanFilter(JSONObject jsonObject) throws JSONException {
+ ScanFilter.Builder builder = new ScanFilter.Builder();
+ if (jsonObject.has("ServiceUuid")) {
+ builder.setServiceUuid(ParcelUuid.fromString(jsonObject.getString("ServiceUuid")));
+ }
+ return builder.build();
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static ScanSettings jsonToScanSettings(JSONObject jsonObject) throws JSONException {
+ ScanSettings.Builder builder = new ScanSettings.Builder();
+ if (jsonObject.has("ScanMode")) {
+ builder.setScanMode(MbsEnums.BLE_SCAN_MODE.getInt(jsonObject.getString("ScanMode")));
+ }
+ return builder.build();
+ }
}
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java
index 82e6f7c..6487501 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java
@@ -16,8 +16,13 @@
package com.google.android.mobly.snippet.bundled.utils;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.annotation.TargetApi;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.ScanRecord;
import android.net.DhcpInfo;
@@ -27,6 +32,7 @@ import android.net.wifi.WifiInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelUuid;
+import android.util.Base64;
import android.util.SparseArray;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -125,7 +131,7 @@ public class JsonSerializer {
return result;
}
- public Bundle serializeBluetoothDevice(BluetoothDevice data) {
+ public static Bundle serializeBluetoothDevice(BluetoothDevice data) {
Bundle result = new Bundle();
result.putString("Address", data.getAddress());
final String bondState =
@@ -184,6 +190,7 @@ public class JsonSerializer {
Bundle result = new Bundle();
result.putString("DeviceName", record.getDeviceName());
result.putInt("TxPowerLevel", record.getTxPowerLevel());
+ result.putParcelableArrayList("Services", serializeBleScanServices(record));
result.putBundle(
"manufacturerSpecificData", serializeBleScanManufacturerSpecificData(record));
return result;
@@ -191,6 +198,28 @@ public class JsonSerializer {
/** Serialize manufacturer specific data from ScanRecord for Bluetooth LE. */
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private ArrayList<Bundle> serializeBleScanServices(ScanRecord record) {
+ ArrayList<Bundle> result = new ArrayList<>();
+ if (record.getServiceUuids() != null) {
+ for (ParcelUuid uuid : record.getServiceUuids()) {
+ Bundle service = new Bundle();
+ service.putString("UUID", uuid.getUuid().toString());
+ if (record.getServiceData(uuid) != null) {
+ service.putString(
+ "Data",
+ new String(Base64.encode(record.getServiceData(uuid), Base64.NO_WRAP),
+ UTF_8));
+ } else {
+ service.putString("Data", "");
+ }
+ result.add(service);
+ }
+ }
+ return result;
+ }
+
+ /** Serialize manufacturer specific data from ScanRecord for Bluetooth LE. */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private Bundle serializeBleScanManufacturerSpecificData(ScanRecord record) {
Bundle result = new Bundle();
SparseArray<byte[]> sparseArray = record.getManufacturerSpecificData();
@@ -213,4 +242,42 @@ public class JsonSerializer {
result.putBoolean("IsConnectable", advertiseSettings.isConnectable());
return result;
}
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Bundle serializeBluetoothGatt(BluetoothGatt gatt) {
+ Bundle result = new Bundle();
+ ArrayList<Bundle> services = new ArrayList<>();
+ for (BluetoothGattService service : gatt.getServices()) {
+ services.add(JsonSerializer.serializeBluetoothGattService(service));
+ }
+ result.putParcelableArrayList("Services", services);
+ result.putBundle("Device", JsonSerializer.serializeBluetoothDevice(gatt.getDevice()));
+ return result;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Bundle serializeBluetoothGattService(BluetoothGattService service) {
+ Bundle result = new Bundle();
+ result.putString("UUID", service.getUuid().toString());
+ result.putString("Type", MbsEnums.BLE_SERVICE_TYPE.getString(service.getType()));
+ ArrayList<Bundle> characteristics = new ArrayList<>();
+ for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
+ characteristics.add(serializeBluetoothGattCharacteristic(characteristic));
+ }
+ result.putParcelableArrayList("Characteristics", characteristics);
+ return result;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Bundle serializeBluetoothGattCharacteristic(
+ BluetoothGattCharacteristic characteristic) {
+ Bundle result = new Bundle();
+ result.putString("UUID", characteristic.getUuid().toString());
+ result.putString(
+ "Property", MbsEnums.BLE_PROPERTY_TYPE.getString(characteristic.getProperties()));
+ result.putString(
+ "Permission",
+ MbsEnums.BLE_PERMISSION_TYPE.getString(characteristic.getPermissions()));
+ return result;
+ }
}
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/MbsEnums.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/MbsEnums.java
index 08163b4..720fad4 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/utils/MbsEnums.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/MbsEnums.java
@@ -1,9 +1,31 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * 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.google.android.mobly.snippet.bundled.utils;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanSettings;
+import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
import android.os.Build;
/** Mobly Bundled Snippets (MBS)'s {@link RpcEnum} objects representing enums in Android APIs. */
@@ -15,6 +37,27 @@ public class MbsEnums {
buildBleScanResultCallbackTypeEnum();
static final RpcEnum BLUETOOTH_DEVICE_BOND_STATE = buildBluetoothDeviceBondState();
static final RpcEnum BLUETOOTH_DEVICE_TYPE = buildBluetoothDeviceTypeEnum();
+ static final RpcEnum BLE_SERVICE_TYPE = buildServiceTypeEnum();
+ public static final RpcEnum BLE_STATUS_TYPE = buildStatusTypeEnum();
+ public static final RpcEnum BLE_CONNECT_STATUS = buildConnectStatusEnum();
+ static final RpcEnum BLE_PROPERTY_TYPE = buildPropertyTypeEnum();
+ static final RpcEnum BLE_PERMISSION_TYPE = buildPermissionTypeEnum();
+ static final RpcEnum BLE_SCAN_MODE = buildBleScanModeEnum();
+ public static final RpcEnum LOCAL_HOTSPOT_FAIL_REASON = buildLocalHotspotFailedReason();
+ public static final RpcEnum ADVERTISE_FAILURE_ERROR_CODE =
+ new RpcEnum.Builder().add("ADVERTISE_FAILED_ALREADY_STARTED",
+ AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED)
+ .add("ADVERTISE_FAILED_DATA_TOO_LARGE",
+ AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE)
+ .add(
+ "ADVERTISE_FAILED_FEATURE_UNSUPPORTED",
+ AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED)
+ .add("ADVERTISE_FAILED_INTERNAL_ERROR",
+ AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR)
+ .add(
+ "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS",
+ AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS)
+ .build();
private static RpcEnum buildBluetoothDeviceBondState() {
RpcEnum.Builder builder = new RpcEnum.Builder();
@@ -89,4 +132,125 @@ public class MbsEnums {
}
return builder.build();
}
+
+ private static RpcEnum buildServiceTypeEnum() {
+ RpcEnum.Builder builder = new RpcEnum.Builder();
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return builder.build();
+ }
+ builder.add("SERVICE_TYPE_PRIMARY", BluetoothGattService.SERVICE_TYPE_PRIMARY);
+ builder.add("SERVICE_TYPE_SECONDARY", BluetoothGattService.SERVICE_TYPE_SECONDARY);
+ return builder.build();
+ }
+
+ private static RpcEnum buildStatusTypeEnum() {
+ RpcEnum.Builder builder = new RpcEnum.Builder();
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return builder.build();
+ }
+ builder.add("GATT_SUCCESS", BluetoothGatt.GATT_SUCCESS)
+ .add("GATT_CONNECTION_CONGESTED", BluetoothGatt.GATT_CONNECTION_CONGESTED)
+ .add("GATT_FAILURE", BluetoothGatt.GATT_FAILURE)
+ .add("GATT_INSUFFICIENT_AUTHENTICATION",
+ BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION)
+ .add("GATT_INSUFFICIENT_ENCRYPTION", BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION)
+ .add("GATT_INVALID_ATTRIBUTE_LENGTH", BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH)
+ .add("GATT_INVALID_OFFSET", BluetoothGatt.GATT_INVALID_OFFSET)
+ .add("GATT_READ_NOT_PERMITTED", BluetoothGatt.GATT_READ_NOT_PERMITTED)
+ .add("GATT_REQUEST_NOT_SUPPORTED", BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED)
+ .add("GATT_WRITE_NOT_PERMITTED", BluetoothGatt.GATT_WRITE_NOT_PERMITTED)
+ .add("BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION", 0x13)
+ .add("BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION", 0x12)
+ .add("BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT", 0x22)
+ .add("BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED", 0x3e)
+ .add("UNEXPECTED_DISCONNECT_NO_ERROR_CODE", 134)
+ .add("DID_NOT_FIND_OFFLINEP2P_SERVICE", 135)
+ .add("MISSING_CHARACTERISTIC", 137)
+ .add("CONNECTION_TIMEOUT", 138)
+ .add("READ_MALFORMED_VERSION", 139)
+ .add("READ_WRITE_VERSION_NONSPECIFIC_ERROR", 140)
+ .add("GATT_0C_err", 0X0C)
+ .add("GATT_16", 0x16)
+ .add("GATT_INTERNAL_ERROR", 129)
+ .add("BLE_HCI_CONNECTION_TIMEOUT", 0x08)
+ .add("GATT_ERROR", 133);
+ return builder.build();
+ }
+
+ private static RpcEnum buildConnectStatusEnum() {
+ RpcEnum.Builder builder = new RpcEnum.Builder();
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return builder.build();
+ }
+ builder.add("STATE_CONNECTED", BluetoothProfile.STATE_CONNECTED)
+ .add("STATE_CONNECTING", BluetoothProfile.STATE_CONNECTING)
+ .add("STATE_DISCONNECTED", BluetoothProfile.STATE_DISCONNECTED)
+ .add("STATE_DISCONNECTING", BluetoothProfile.STATE_DISCONNECTING);
+ return builder.build();
+ }
+
+ private static RpcEnum buildPropertyTypeEnum() {
+ RpcEnum.Builder builder = new RpcEnum.Builder();
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return builder.build();
+ }
+ builder
+ .add("PROPERTY_NONE", 0)
+ .add("PROPERTY_BROADCAST", BluetoothGattCharacteristic.PROPERTY_BROADCAST)
+ .add("PROPERTY_EXTENDED_PROPS", BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS)
+ .add("PROPERTY_INDICATE", BluetoothGattCharacteristic.PROPERTY_INDICATE)
+ .add("PROPERTY_NOTIFY", BluetoothGattCharacteristic.PROPERTY_NOTIFY)
+ .add("PROPERTY_READ", BluetoothGattCharacteristic.PROPERTY_READ)
+ .add("PROPERTY_SIGNED_WRITE", BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE)
+ .add("PROPERTY_WRITE", BluetoothGattCharacteristic.PROPERTY_WRITE)
+ .add("PROPERTY_WRITE_NO_RESPONSE",
+ BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE);
+ return builder.build();
+ }
+
+ private static RpcEnum buildPermissionTypeEnum() {
+ RpcEnum.Builder builder = new RpcEnum.Builder();
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return builder.build();
+ }
+ builder.add("PERMISSION_NONE", 0)
+ .add("PERMISSION_READ", BluetoothGattCharacteristic.PERMISSION_READ)
+ .add("PERMISSION_READ_ENCRYPTED",
+ BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED)
+ .add("PERMISSION_READ_ENCRYPTED_MITM",
+ BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM)
+ .add("PERMISSION_WRITE", BluetoothGattCharacteristic.PERMISSION_WRITE)
+ .add("PERMISSION_WRITE_ENCRYPTED",
+ BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED)
+ .add("PERMISSION_WRITE_ENCRYPTED_MITM",
+ BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM)
+ .add("PERMISSION_WRITE_SIGNED", BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED)
+ .add("PERMISSION_WRITE_SIGNED_MITM",
+ BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM);
+ return builder.build();
+ }
+
+ private static RpcEnum buildBleScanModeEnum() {
+ RpcEnum.Builder builder = new RpcEnum.Builder();
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return builder.build();
+ }
+ builder.add("SCAN_MODE_LOW_POWER", ScanSettings.SCAN_MODE_LOW_POWER)
+ .add("SCAN_MODE_BALANCED", ScanSettings.SCAN_MODE_BALANCED)
+ .add("SCAN_MODE_LOW_LATENCY", ScanSettings.SCAN_MODE_LOW_LATENCY);
+ return builder.build();
+ }
+
+ private static RpcEnum buildLocalHotspotFailedReason() {
+ RpcEnum.Builder builder = new RpcEnum.Builder();
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ return builder.build();
+ }
+ builder.add("ERROR_TETHERING_DISALLOWED",
+ LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED)
+ .add("ERROR_INCOMPATIBLE_MODE", LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE)
+ .add("ERROR_NO_CHANNEL", LocalOnlyHotspotCallback.ERROR_NO_CHANNEL)
+ .add("ERROR_GENERIC", LocalOnlyHotspotCallback.ERROR_GENERIC);
+ return builder.build();
+ }
}
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java
index d6442a8..85b7058 100644
--- a/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java
+++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java
@@ -17,6 +17,7 @@
package com.google.android.mobly.snippet.bundled.utils;
import com.google.common.collect.ImmutableBiMap;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
/**
* A container type for handling String-Integer enum conversion in Rpc protocol.
@@ -27,20 +28,20 @@ import com.google.common.collect.ImmutableBiMap;
* <p>Once built, an RpcEnum object is immutable.
*/
public class RpcEnum {
- private final ImmutableBiMap<String, Integer> mEnums;
+ private final ImmutableBiMap<String, Integer> enums;
private RpcEnum(ImmutableBiMap.Builder<String, Integer> builder) {
- mEnums = builder.buildOrThrow();
+ enums = builder.buildOrThrow();
}
/**
* Get the int value of an enum based on its String value.
*
* @param enumString
- * @return
+ * @return int value
*/
public int getInt(String enumString) {
- Integer result = mEnums.get(enumString);
+ Integer result = enums.get(enumString);
if (result == null) {
throw new NoSuchFieldError("No int value found for: " + enumString);
}
@@ -51,12 +52,12 @@ public class RpcEnum {
* Get the String value of an enum based on its int value.
*
* @param enumInt
- * @return
+ * @return string value
*/
public String getString(int enumInt) {
- String result = mEnums.inverse().get(enumInt);
+ String result = enums.inverse().get(enumInt);
if (result == null) {
- throw new NoSuchFieldError("No String value found for: " + enumInt);
+ return String.format("UNKNOWN_VALUE[%s].", enumInt);
}
return result;
}
@@ -76,6 +77,7 @@ public class RpcEnum {
* @param enumInt
* @return
*/
+ @CanIgnoreReturnValue
public Builder add(String enumString, int enumInt) {
builder.put(enumString, enumInt);
return this;