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