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