1 /* 2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.le_audio; 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.bluetooth.BluetoothProfile; 27 import android.bluetooth.BluetoothUuid; 28 import android.content.Context; 29 import android.util.Log; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 33 import java.util.Arrays; 34 import java.util.List; 35 import java.util.UUID; 36 37 /** 38 * A GATT server for Telephony and Media Audio Profile (TMAP) 39 */ 40 @VisibleForTesting 41 public class LeAudioTmapGattServer { 42 private static final boolean DBG = true; 43 private static final String TAG = "LeAudioTmapGattServer"; 44 45 /* Telephony and Media Audio Profile Role Characteristic UUID */ 46 @VisibleForTesting 47 public static final UUID UUID_TMAP_ROLE = 48 UUID.fromString("00002B51-0000-1000-8000-00805f9b34fb"); 49 50 /* TMAP Role: Call Gateway */ 51 public static final int TMAP_ROLE_FLAG_CG = 1; 52 /* TMAP Role: Call Terminal */ 53 public static final int TMAP_ROLE_FLAG_CT = 1 << 1; 54 /* TMAP Role: Unicast Media Sender */ 55 public static final int TMAP_ROLE_FLAG_UMS = 1 << 2; 56 /* TMAP Role: Unicast Media Receiver */ 57 public static final int TMAP_ROLE_FLAG_UMR = 1 << 3; 58 /* TMAP Role: Broadcast Media Sender */ 59 public static final int TMAP_ROLE_FLAG_BMS = 1 << 4; 60 /* TMAP Role: Broadcast Media Receiver */ 61 public static final int TMAP_ROLE_FLAG_BMR = 1 << 5; 62 63 private final BluetoothGattServerProxy mBluetoothGattServer; 64 LeAudioTmapGattServer(BluetoothGattServerProxy gattServer)65 /*package*/ LeAudioTmapGattServer(BluetoothGattServerProxy gattServer) { 66 mBluetoothGattServer = gattServer; 67 } 68 69 /** 70 * Init TMAP server 71 * @param roleMask bit mask of supported roles. 72 */ 73 @VisibleForTesting start(int roleMask)74 public void start(int roleMask) { 75 if (DBG) { 76 Log.d(TAG, "start(roleMask:" + roleMask + ")"); 77 } 78 79 if (!mBluetoothGattServer.open(mBluetoothGattServerCallback)) { 80 throw new IllegalStateException("Could not open Gatt server"); 81 } 82 83 BluetoothGattService service = 84 new BluetoothGattService(BluetoothUuid.TMAP.getUuid(), 85 BluetoothGattService.SERVICE_TYPE_PRIMARY); 86 87 BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic( 88 UUID_TMAP_ROLE, 89 BluetoothGattCharacteristic.PROPERTY_READ, 90 BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED); 91 92 characteristic.setValue(roleMask, BluetoothGattCharacteristic.FORMAT_UINT16, 0); 93 service.addCharacteristic(characteristic); 94 95 if (!mBluetoothGattServer.addService(service)) { 96 throw new IllegalStateException("Failed to add service for TMAP"); 97 } 98 } 99 100 /** 101 * Stop TMAP server 102 */ 103 @VisibleForTesting stop()104 public void stop() { 105 if (DBG) { 106 Log.d(TAG, "stop()"); 107 } 108 if (mBluetoothGattServer == null) { 109 Log.w(TAG, "mBluetoothGattServer should not be null when stop() is called"); 110 return; 111 } 112 mBluetoothGattServer.close(); 113 } 114 115 /** 116 * Callback to handle incoming requests to the GATT server. 117 * All read/write requests for characteristics and descriptors are handled here. 118 */ 119 private final BluetoothGattServerCallback mBluetoothGattServerCallback = 120 new BluetoothGattServerCallback() { 121 @Override 122 public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, 123 BluetoothGattCharacteristic characteristic) { 124 byte[] value = characteristic.getValue(); 125 if (DBG) { 126 Log.d(TAG, "value " + Arrays.toString(value)); 127 } 128 if (value != null) { 129 Log.e(TAG, "value null"); 130 value = Arrays.copyOfRange(value, offset, value.length); 131 } 132 mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 133 offset, value); 134 } 135 }; 136 137 /** 138 * A proxy class that facilitates testing. 139 * 140 * This is necessary due to the "final" attribute of the BluetoothGattServer class. 141 */ 142 public static class BluetoothGattServerProxy { 143 private final Context mContext; 144 private final BluetoothManager mBluetoothManager; 145 146 private BluetoothGattServer mBluetoothGattServer; 147 148 /** 149 * Create a new GATT server proxy object 150 * @param context context to use 151 */ BluetoothGattServerProxy(Context context)152 public BluetoothGattServerProxy(Context context) { 153 mContext = context; 154 mBluetoothManager = context.getSystemService(BluetoothManager.class); 155 } 156 157 /** 158 * Open with GATT server callback 159 * @param callback callback to invoke 160 * @return true on success 161 */ open(BluetoothGattServerCallback callback)162 public boolean open(BluetoothGattServerCallback callback) { 163 mBluetoothGattServer = mBluetoothManager.openGattServer(mContext, callback); 164 return mBluetoothGattServer != null; 165 } 166 167 /** 168 * Close the GATT server, should be called as soon as the server is not needed 169 */ close()170 public void close() { 171 if (mBluetoothGattServer == null) { 172 Log.w(TAG, "BluetoothGattServerProxy.close() called without open()"); 173 return; 174 } 175 mBluetoothGattServer.close(); 176 mBluetoothGattServer = null; 177 } 178 179 /** 180 * Add a GATT service 181 * @param service added service 182 * @return true on success 183 */ addService(BluetoothGattService service)184 public boolean addService(BluetoothGattService service) { 185 return mBluetoothGattServer.addService(service); 186 } 187 188 /** 189 * Send GATT response to remote 190 * @param device remote device 191 * @param requestId request id 192 * @param status status of response 193 * @param offset offset of the value 194 * @param value value content 195 * @return true on success 196 */ sendResponse( BluetoothDevice device, int requestId, int status, int offset, byte[] value)197 public boolean sendResponse( 198 BluetoothDevice device, int requestId, int status, int offset, byte[] value) { 199 return mBluetoothGattServer.sendResponse(device, requestId, status, offset, value); 200 } 201 202 /** 203 * Gatt a list of devices connected to this GATT server 204 * @return list of connected devices at this moment 205 */ getConnectedDevices()206 public List<BluetoothDevice> getConnectedDevices() { 207 return mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER); 208 } 209 } 210 } 211