1 /* 2 * Copyright (C) 2023 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.google.android.mobly.snippet.bundled.bluetooth; 18 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothGatt; 21 import android.bluetooth.BluetoothGattCharacteristic; 22 import android.bluetooth.BluetoothGattServer; 23 import android.bluetooth.BluetoothGattServerCallback; 24 import android.bluetooth.BluetoothGattService; 25 import android.bluetooth.BluetoothManager; 26 import android.content.Context; 27 import android.os.Build.VERSION_CODES; 28 import android.os.DeadObjectException; 29 import android.os.SystemClock; 30 import android.util.Base64; 31 import androidx.test.platform.app.InstrumentationRegistry; 32 import com.google.android.mobly.snippet.Snippet; 33 import com.google.android.mobly.snippet.bundled.utils.DataHolder; 34 import com.google.android.mobly.snippet.bundled.utils.JsonDeserializer; 35 import com.google.android.mobly.snippet.bundled.utils.JsonSerializer; 36 import com.google.android.mobly.snippet.bundled.utils.MbsEnums; 37 import com.google.android.mobly.snippet.event.EventCache; 38 import com.google.android.mobly.snippet.event.SnippetEvent; 39 import com.google.android.mobly.snippet.rpc.AsyncRpc; 40 import com.google.android.mobly.snippet.rpc.Rpc; 41 import com.google.android.mobly.snippet.rpc.RpcMinSdk; 42 import com.google.android.mobly.snippet.util.Log; 43 import org.json.JSONArray; 44 import org.json.JSONException; 45 import org.json.JSONObject; 46 47 /** Snippet class exposing Android APIs in BluetoothGattServer. */ 48 public class BluetoothGattServerSnippet implements Snippet { 49 private static class BluetoothGattServerSnippetException extends Exception { 50 private static final long serialVersionUID = 1; 51 BluetoothGattServerSnippetException(String msg)52 public BluetoothGattServerSnippetException(String msg) { 53 super(msg); 54 } 55 } 56 57 private final Context context; 58 private final BluetoothManager bluetoothManager; 59 private final DataHolder dataHolder; 60 private final EventCache eventCache; 61 62 private BluetoothGattServer bluetoothGattServer; 63 BluetoothGattServerSnippet()64 public BluetoothGattServerSnippet() { 65 context = InstrumentationRegistry.getInstrumentation().getContext(); 66 bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); 67 dataHolder = new DataHolder(); 68 eventCache = EventCache.getInstance(); 69 } 70 71 @RpcMinSdk(VERSION_CODES.LOLLIPOP) 72 @AsyncRpc(description = "Start BLE server.") bleStartServer(String callbackId, JSONArray services)73 public void bleStartServer(String callbackId, JSONArray services) 74 throws JSONException, DeadObjectException { 75 BluetoothGattServerCallback gattServerCallback = 76 new DefaultBluetoothGattServerCallback(callbackId); 77 bluetoothGattServer = bluetoothManager.openGattServer(context, gattServerCallback); 78 addServiceToGattServer(services); 79 } 80 81 @RpcMinSdk(VERSION_CODES.LOLLIPOP) 82 @AsyncRpc(description = "Start BLE server with workaround.") bleStartServerWithWorkaround(String callbackId, JSONArray services)83 public void bleStartServerWithWorkaround(String callbackId, JSONArray services) 84 throws JSONException, DeadObjectException { 85 BluetoothGattServerCallback gattServerCallback = 86 new DefaultBluetoothGattServerCallback(callbackId); 87 boolean isGattServerStarted = false; 88 int count = 0; 89 while (!isGattServerStarted && count < 5) { 90 bluetoothGattServer = bluetoothManager.openGattServer(context, gattServerCallback); 91 if (bluetoothGattServer != null) { 92 addServiceToGattServer(services); 93 isGattServerStarted = true; 94 } else { 95 SystemClock.sleep(1000); 96 count++; 97 } 98 } 99 } 100 addServiceToGattServer(JSONArray services)101 private void addServiceToGattServer(JSONArray services) throws JSONException { 102 for (int i = 0; i < services.length(); i++) { 103 JSONObject service = services.getJSONObject(i); 104 BluetoothGattService bluetoothGattService = 105 JsonDeserializer.jsonToBluetoothGattService(dataHolder, service); 106 bluetoothGattServer.addService(bluetoothGattService); 107 } 108 } 109 110 @RpcMinSdk(VERSION_CODES.LOLLIPOP) 111 @Rpc(description = "Stop BLE server.") bleStopServer()112 public void bleStopServer() throws BluetoothGattServerSnippetException { 113 if (bluetoothGattServer == null) { 114 throw new BluetoothGattServerSnippetException("BLE server is not initialized."); 115 } 116 bluetoothGattServer.close(); 117 } 118 119 private class DefaultBluetoothGattServerCallback extends BluetoothGattServerCallback { 120 private final String callbackId; 121 DefaultBluetoothGattServerCallback(String callbackId)122 DefaultBluetoothGattServerCallback(String callbackId) { 123 this.callbackId = callbackId; 124 } 125 126 @Override onConnectionStateChange(BluetoothDevice device, int status, int newState)127 public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { 128 SnippetEvent event = new SnippetEvent(callbackId, "onConnectionStateChange"); 129 event.getData().putBundle("device", JsonSerializer.serializeBluetoothDevice(device)); 130 event.getData().putString("status", MbsEnums.BLE_STATUS_TYPE.getString(status)); 131 event.getData().putString("newState", MbsEnums.BLE_CONNECT_STATUS.getString(newState)); 132 eventCache.postEvent(event); 133 } 134 135 @Override onServiceAdded(int status, BluetoothGattService service)136 public void onServiceAdded(int status, BluetoothGattService service) { 137 Log.d("Bluetooth Gatt Server service added with status " + status); 138 SnippetEvent event = new SnippetEvent(callbackId, "onServiceAdded"); 139 event.getData().putString("status", MbsEnums.BLE_STATUS_TYPE.getString(status)); 140 event.getData() 141 .putParcelable("Service", 142 JsonSerializer.serializeBluetoothGattService(service)); 143 eventCache.postEvent(event); 144 } 145 146 @Override onCharacteristicReadRequest( BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic)147 public void onCharacteristicReadRequest( 148 BluetoothDevice device, 149 int requestId, 150 int offset, 151 BluetoothGattCharacteristic characteristic) { 152 Log.d("Bluetooth Gatt Server received a read request"); 153 if (dataHolder.get(characteristic) != null) { 154 bluetoothGattServer.sendResponse( 155 device, 156 requestId, 157 BluetoothGatt.GATT_SUCCESS, 158 offset, 159 Base64.decode(dataHolder.get(characteristic), Base64.NO_WRAP)); 160 } else { 161 bluetoothGattServer.sendResponse( 162 device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null); 163 } 164 } 165 166 @Override onCharacteristicWriteRequest( BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value)167 public void onCharacteristicWriteRequest( 168 BluetoothDevice device, 169 int requestId, 170 BluetoothGattCharacteristic characteristic, 171 boolean preparedWrite, 172 boolean responseNeeded, 173 int offset, 174 byte[] value) { 175 Log.d("Bluetooth Gatt Server received a write request"); 176 bluetoothGattServer.sendResponse( 177 device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null); 178 SnippetEvent event = new SnippetEvent(callbackId, "onCharacteristicWriteRequest"); 179 event.getData().putString("Data", Base64.encodeToString(value, Base64.NO_WRAP)); 180 eventCache.postEvent(event); 181 } 182 183 @Override onExecuteWrite(BluetoothDevice device, int requestId, boolean execute)184 public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) { 185 Log.d("Bluetooth Gatt Server received an execute write request"); 186 bluetoothGattServer.sendResponse( 187 device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null); 188 } 189 } 190 191 @Override shutdown()192 public void shutdown() {} 193 } 194