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