• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 HIMSA II K/S - www.himsa.com.
3  * Represented by EHIMA - www.ehima.com
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.bluetooth.mcp;
19 
20 import static android.bluetooth.BluetoothDevice.METADATA_GMCS_CCCD;
21 import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED;
22 import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED;
23 import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_NOTIFY;
24 import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_READ;
25 import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE;
26 import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE;
27 
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.bluetooth.BluetoothAdapter;
31 import android.bluetooth.BluetoothDevice;
32 import android.bluetooth.BluetoothGatt;
33 import android.bluetooth.BluetoothGattCharacteristic;
34 import android.bluetooth.BluetoothGattDescriptor;
35 import android.bluetooth.BluetoothGattServer;
36 import android.bluetooth.BluetoothGattServerCallback;
37 import android.bluetooth.BluetoothGattService;
38 import android.bluetooth.BluetoothManager;
39 import android.bluetooth.BluetoothProfile;
40 import android.bluetooth.IBluetoothManager;
41 import android.bluetooth.IBluetoothStateChangeCallback;
42 import android.content.Context;
43 import android.os.Handler;
44 import android.os.Looper;
45 import android.os.ParcelUuid;
46 import android.os.RemoteException;
47 import android.util.Log;
48 import android.util.Pair;
49 
50 import com.android.bluetooth.Utils;
51 import com.android.bluetooth.a2dp.A2dpService;
52 import com.android.bluetooth.btservice.AdapterService;
53 import com.android.bluetooth.hearingaid.HearingAidService;
54 import com.android.bluetooth.le_audio.LeAudioService;
55 import com.android.internal.annotations.VisibleForTesting;
56 
57 import java.nio.ByteBuffer;
58 import java.nio.ByteOrder;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Objects;
65 import java.util.UUID;
66 
67 /**
68  * This implements Media Control Service object which is given back to the app who registers a new
69  * MCS instance through the MCS Service Manager. It has no higher level logic to control the media
70  * player itself, thus can be used either as an MCS or a single-instance GMCS. It implements only
71  * the GATT Service logic, allowing the higher level layer to control the service state and react to
72  * bluetooth peer device requests through the method calls and callback mechanism.
73  *
74  * Implemented according to Media Control Service v1.0 specification.
75  */
76 public class MediaControlGattService implements MediaControlGattServiceInterface {
77     private static final String TAG = "MediaControlGattService";
78     private static final boolean DBG = Log.isLoggable(TAG, Log.INFO);
79     private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
80 
81     /* MCS assigned UUIDs */
82     public static final UUID UUID_PLAYER_NAME =
83             UUID.fromString("00002b93-0000-1000-8000-00805f9b34fb");
84     public static final UUID UUID_PLAYER_ICON_OBJ_ID =
85             UUID.fromString("00002b94-0000-1000-8000-00805f9b34fb");
86     public static final UUID UUID_PLAYER_ICON_URL =
87             UUID.fromString("00002b95-0000-1000-8000-00805f9b34fb");
88     public static final UUID UUID_TRACK_CHANGED =
89             UUID.fromString("00002b96-0000-1000-8000-00805f9b34fb");
90     public static final UUID UUID_TRACK_TITLE =
91             UUID.fromString("00002b97-0000-1000-8000-00805f9b34fb");
92     public static final UUID UUID_TRACK_DURATION =
93             UUID.fromString("00002b98-0000-1000-8000-00805f9b34fb");
94     public static final UUID UUID_TRACK_POSITION =
95             UUID.fromString("00002b99-0000-1000-8000-00805f9b34fb");
96     public static final UUID UUID_PLAYBACK_SPEED =
97             UUID.fromString("00002b9a-0000-1000-8000-00805f9b34fb");
98     public static final UUID UUID_SEEKING_SPEED =
99             UUID.fromString("00002b9b-0000-1000-8000-00805f9b34fb");
100     public static final UUID UUID_CURRENT_TRACK_SEGMENT_OBJ_ID =
101             UUID.fromString("00002b9c-0000-1000-8000-00805f9b34fb");
102     public static final UUID UUID_CURRENT_TRACK_OBJ_ID =
103             UUID.fromString("00002b9d-0000-1000-8000-00805f9b34fb");
104     public static final UUID UUID_NEXT_TRACK_OBJ_ID =
105             UUID.fromString("00002b9e-0000-1000-8000-00805f9b34fb");
106     public static final UUID UUID_CURRENT_GROUP_OBJ_ID =
107             UUID.fromString("00002b9f-0000-1000-8000-00805f9b34fb");
108     public static final UUID UUID_PARENT_GROUP_OBJ_ID =
109             UUID.fromString("00002ba0-0000-1000-8000-00805f9b34fb");
110     public static final UUID UUID_PLAYING_ORDER =
111             UUID.fromString("00002ba1-0000-1000-8000-00805f9b34fb");
112     public static final UUID UUID_PLAYING_ORDER_SUPPORTED =
113             UUID.fromString("00002ba2-0000-1000-8000-00805f9b34fb");
114     public static final UUID UUID_MEDIA_STATE =
115             UUID.fromString("00002ba3-0000-1000-8000-00805f9b34fb");
116     public static final UUID UUID_MEDIA_CONTROL_POINT =
117             UUID.fromString("00002ba4-0000-1000-8000-00805f9b34fb");
118     public static final UUID UUID_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED =
119             UUID.fromString("00002ba5-0000-1000-8000-00805f9b34fb");
120     public static final UUID UUID_SEARCH_RESULT_OBJ_ID =
121             UUID.fromString("00002ba6-0000-1000-8000-00805f9b34fb");
122     public static final UUID UUID_SEARCH_CONTROL_POINT =
123             UUID.fromString("00002ba7-0000-1000-8000-00805f9b34fb");
124     public static final UUID UUID_CONTENT_CONTROL_ID =
125             UUID.fromString("00002bba-0000-1000-8000-00805f9b34fb");
126 
127     private static final byte SEARCH_CONTROL_POINT_RESULT_SUCCESS = 0x01;
128     private static final byte SEARCH_CONTROL_POINT_RESULT_FAILURE = 0x02;
129 
130     private static final float PLAY_SPEED_MIN = 0.25f;
131     private static final float PLAY_SPEED_MAX = 3.957f;
132 
133     private static final int INTERVAL_UNAVAILABLE = 0xFFFFFFFF;
134 
135     private final int mCcid;
136     private HashMap<String, HashMap<UUID, Short>> mCccDescriptorValues;
137     private long mFeatures;
138     private Context mContext;
139     private MediaControlServiceCallbacks mCallbacks;
140     private BluetoothGattServerProxy mBluetoothGattServer;
141     private BluetoothGattService mGattService = null;
142     private Handler mHandler = new Handler(Looper.getMainLooper());
143     private Map<Integer, BluetoothGattCharacteristic> mCharacteristics = new HashMap<>();
144     private MediaState mCurrentMediaState = MediaState.INACTIVE;
145     private Map<BluetoothDevice, List<GattOpContext>> mPendingGattOperations = new HashMap<>();
146     private McpService mMcpService;
147     private LeAudioService mLeAudioService;
148     private AdapterService mAdapterService;
149 
mcsUuidToString(UUID uuid)150     private static String mcsUuidToString(UUID uuid) {
151         if (uuid.equals(UUID_PLAYER_NAME)) {
152             return "PLAYER_NAME";
153         } else if (uuid.equals(UUID_PLAYER_ICON_OBJ_ID)) {
154             return "PLAYER_ICON_OBJ_ID";
155         } else if (uuid.equals(UUID_PLAYER_ICON_URL)) {
156             return "PLAYER_ICON_URL";
157         } else if (uuid.equals(UUID_TRACK_CHANGED)) {
158             return "TRACK_CHANGED";
159         } else if (uuid.equals(UUID_TRACK_TITLE)) {
160             return "TRACK_TITLE";
161         } else if (uuid.equals(UUID_TRACK_DURATION)) {
162             return "TRACK_DURATION";
163         } else if (uuid.equals(UUID_TRACK_POSITION)) {
164             return "TRACK_POSITION";
165         } else if (uuid.equals(UUID_PLAYBACK_SPEED)) {
166             return "PLAYBACK_SPEED";
167         } else if (uuid.equals(UUID_SEEKING_SPEED)) {
168             return "SEEKING_SPEED";
169         } else if (uuid.equals(UUID_CURRENT_TRACK_SEGMENT_OBJ_ID)) {
170             return "CURRENT_TRACK_SEGMENT_OBJ_ID";
171         } else if (uuid.equals(UUID_CURRENT_TRACK_OBJ_ID)) {
172             return "CURRENT_TRACK_OBJ_ID";
173         } else if (uuid.equals(UUID_NEXT_TRACK_OBJ_ID)) {
174             return "NEXT_TRACK_OBJ_ID";
175         } else if (uuid.equals(UUID_CURRENT_GROUP_OBJ_ID)) {
176             return "CURRENT_GROUP_OBJ_ID";
177         } else if (uuid.equals(UUID_PARENT_GROUP_OBJ_ID)) {
178             return "PARENT_GROUP_OBJ_ID";
179         } else if (uuid.equals(UUID_PLAYING_ORDER)) {
180             return "PLAYING_ORDER";
181         } else if (uuid.equals(UUID_PLAYING_ORDER_SUPPORTED)) {
182             return "PLAYING_ORDER_SUPPORTED";
183         } else if (uuid.equals(UUID_MEDIA_STATE)) {
184             return "MEDIA_STATE";
185         } else if (uuid.equals(UUID_MEDIA_CONTROL_POINT)) {
186             return "MEDIA_CONTROL_POINT";
187         } else if (uuid.equals(UUID_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED)) {
188             return "MEDIA_CONTROL_POINT_OPCODES_SUPPORTED";
189         } else if (uuid.equals(UUID_SEARCH_RESULT_OBJ_ID)) {
190             return "SEARCH_RESULT_OBJ_ID";
191         } else if (uuid.equals(UUID_SEARCH_CONTROL_POINT)) {
192             return "SEARCH_CONTROL_POINT";
193         } else if (uuid.equals(UUID_CONTENT_CONTROL_ID)) {
194             return "CONTENT_CONTROL_ID";
195         } else {
196             return "UNKNOWN(" + uuid + ")";
197         }
198     }
199 
200     private static class GattOpContext {
201         public enum Operation {
202             READ_CHARACTERISTIC,
203             WRITE_CHARACTERISTIC,
204             READ_DESCRIPTOR,
205             WRITE_DESCRIPTOR,
206         }
207 
GattOpContext(Operation operation, int requestId, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value)208         GattOpContext(Operation operation, int requestId,
209                 BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor,
210                 boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
211             mOperation = operation;
212             mRequestId = requestId;
213             mCharacteristic = characteristic;
214             mDescriptor = descriptor;
215             mPreparedWrite = preparedWrite;
216             mResponseNeeded = responseNeeded;
217             mOffset = offset;
218             mValue = value;
219         }
220 
GattOpContext(Operation operation, int requestId, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor)221         GattOpContext(Operation operation, int requestId,
222                 BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor) {
223             mOperation = operation;
224             mRequestId = requestId;
225             mCharacteristic = characteristic;
226             mDescriptor = descriptor;
227             mPreparedWrite = false;
228             mResponseNeeded = false;
229             mOffset = 0;
230             mValue = null;
231         }
232 
233         public Operation mOperation;
234         public int mRequestId;
235         public BluetoothGattCharacteristic mCharacteristic;
236         public BluetoothGattDescriptor mDescriptor;
237         public boolean mPreparedWrite;
238         public boolean mResponseNeeded;
239         public int mOffset;
240         public byte[] mValue;
241     }
242 
243     private final Map<UUID, CharacteristicWriteHandler> mCharWriteCallback = Map.of(
244             UUID_TRACK_POSITION,
245             (device, requestId, characteristic, preparedWrite, responseNeeded, offset, value) -> {
246                 if (VDBG) {
247                     Log.d(TAG, "TRACK_POSITION write request");
248                 }
249                 int status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
250                 if (value.length == 4) {
251                     status = BluetoothGatt.GATT_SUCCESS;
252                     ByteBuffer bb = ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN);
253                     handleTrackPositionRequest(bb.getInt());
254                 }
255                 if (responseNeeded) {
256                     mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
257                 }
258             },
259             UUID_PLAYBACK_SPEED,
260             (device, requestId, characteristic, preparedWrite, responseNeeded, offset, value) -> {
261                 if (VDBG) {
262                     Log.d(TAG, "PLAYBACK_SPEED write request");
263                 }
264                 int status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
265                 if (value.length == 1) {
266                     status = BluetoothGatt.GATT_SUCCESS;
267 
268                     Integer intVal = characteristic.getIntValue(
269                             BluetoothGattCharacteristic.FORMAT_SINT8, 0);
270                     // Don't bother player with the same value
271                     if (intVal == value[0]) {
272                         notifyCharacteristic(characteristic, null);
273                     } else {
274                         handlePlaybackSpeedRequest(value[0]);
275                     }
276                 }
277                 if (responseNeeded) {
278                     mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
279                 }
280             },
281             UUID_CURRENT_TRACK_OBJ_ID,
282             (device, requestId, characteristic, preparedWrite, responseNeeded, offset, value) -> {
283                 if (VDBG) {
284                     Log.d(TAG, "CURRENT_TRACK_OBJ_ID write request");
285                 }
286                 int status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
287                 if (value.length == 6) {
288                     status = BluetoothGatt.GATT_SUCCESS;
289                     handleObjectIdRequest(
290                             ObjectIds.CURRENT_TRACK_OBJ_ID, byteArray2ObjId(value));
291                 }
292                 if (responseNeeded) {
293                     mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
294                 }
295             },
296             UUID_NEXT_TRACK_OBJ_ID,
297             (device, requestId, characteristic, preparedWrite, responseNeeded, offset, value) -> {
298                 if (VDBG) {
299                     Log.d(TAG, "NEXT_TRACK_OBJ_ID write request");
300                 }
301                 int status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
302                 if (value.length == 6) {
303                     status = BluetoothGatt.GATT_SUCCESS;
304                     handleObjectIdRequest(ObjectIds.NEXT_TRACK_OBJ_ID, byteArray2ObjId(value));
305                 }
306                 if (responseNeeded) {
307                     mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
308                 }
309             },
310             UUID_CURRENT_GROUP_OBJ_ID,
311             (device, requestId, characteristic, preparedWrite, responseNeeded, offset, value) -> {
312                 if (VDBG) {
313                     Log.d(TAG, "CURRENT_GROUP_OBJ_ID write request");
314                 }
315                 int status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
316                 if (value.length == 6) {
317                     status = BluetoothGatt.GATT_SUCCESS;
318                     handleObjectIdRequest(
319                             ObjectIds.CURRENT_GROUP_OBJ_ID, byteArray2ObjId(value));
320                 }
321                 if (responseNeeded) {
322                     mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
323                 }
324             },
325             UUID_PLAYING_ORDER,
326             (device, requestId, characteristic, preparedWrite, responseNeeded, offset, value) -> {
327                 if (VDBG) {
328                     Log.d(TAG, "PLAYING_ORDER write request");
329                 }
330                 int status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
331                 Integer currentPlayingOrder = null;
332 
333                 if (characteristic.getValue() != null) {
334                     currentPlayingOrder = characteristic.getIntValue(
335                             BluetoothGattCharacteristic.FORMAT_UINT8, 0);
336                 }
337 
338                 if (value.length == 1
339                         && (currentPlayingOrder == null || currentPlayingOrder != value[0])) {
340                     status = BluetoothGatt.GATT_SUCCESS;
341                     BluetoothGattCharacteristic supportedPlayingOrderChar =
342                             mCharacteristics.get(CharId.PLAYING_ORDER_SUPPORTED);
343                     Integer supportedPlayingOrder =
344                             supportedPlayingOrderChar.getIntValue(
345                                     BluetoothGattCharacteristic.FORMAT_UINT16, 0);
346 
347                     if ((supportedPlayingOrder & (1 << (value[0] - 1))) != 0) {
348                         handlePlayingOrderRequest(value[0]);
349                     }
350                 }
351                 if (responseNeeded) {
352                     mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
353                 }
354             },
355             UUID_MEDIA_CONTROL_POINT,
356             (device, requestId, characteristic, preparedWrite, responseNeeded, offset, value) -> {
357                 if (VDBG) {
358                     Log.d(TAG, "MEDIA_CONTROL_POINT write request");
359                 }
360                 int status = handleMediaControlPointRequest(device, value);
361                 if (responseNeeded) {
362                     mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
363                 }
364             },
365             UUID_SEARCH_CONTROL_POINT,
366             (device, requestId, characteristic, preparedWrite, responseNeeded, offset, value) -> {
367                 if (VDBG) {
368                     Log.d(TAG, "SEARCH_CONTROL_POINT write request");
369                 }
370                 // TODO: There is no Object Trasfer Service implementation.
371                 if (responseNeeded) {
372                     mBluetoothGattServer.sendResponse(device, requestId, 0, offset, value);
373                 }
374             });
375 
millisecondsToMcsInterval(long interval)376     private long millisecondsToMcsInterval(long interval) {
377         /* MCS presents time in 0.01s intervals */
378         return interval / 10;
379     }
380 
mcsIntervalToMilliseconds(long interval)381     private long mcsIntervalToMilliseconds(long interval) {
382         /* MCS presents time in 0.01s intervals */
383         return interval * 10L;
384     }
385 
getDeviceAuthorization(BluetoothDevice device)386     private int getDeviceAuthorization(BluetoothDevice device) {
387         return mMcpService.getDeviceAuthorization(device);
388     }
389 
onUnauthorizedGattOperation(BluetoothDevice device, GattOpContext op)390     private void onUnauthorizedGattOperation(BluetoothDevice device, GattOpContext op) {
391         if (VDBG) {
392             Log.d(TAG, "onUnauthorizedGattOperation device: " + device);
393         }
394 
395         synchronized (mPendingGattOperations) {
396             List<GattOpContext> operations = mPendingGattOperations.get(device);
397             if (operations == null) {
398                 operations = new ArrayList<>();
399                 mPendingGattOperations.put(device, operations);
400             }
401 
402             operations.add(op);
403             // Send authorization request for each device only for it's first GATT request
404             if (operations.size() == 1) {
405                 mMcpService.onDeviceUnauthorized(device);
406             }
407         }
408     }
409 
onAuthorizedGattOperation(BluetoothDevice device, GattOpContext op)410     private void onAuthorizedGattOperation(BluetoothDevice device, GattOpContext op) {
411         int status = BluetoothGatt.GATT_SUCCESS;
412 
413         if (VDBG) {
414             Log.d(TAG, "onAuthorizedGattOperation device: " + device);
415         }
416 
417         switch (op.mOperation) {
418             case READ_CHARACTERISTIC:
419                 // Always ask for the latest position
420                 if (op.mCharacteristic.getUuid().equals(
421                         mCharacteristics.get(CharId.TRACK_POSITION).getUuid())) {
422                     long positionMs = TRACK_POSITION_UNAVAILABLE;
423                     positionMs = mCallbacks.onGetCurrentTrackPosition();
424                     final int position = (positionMs != TRACK_POSITION_UNAVAILABLE)
425                             ? new Long(millisecondsToMcsInterval(positionMs)).intValue()
426                             : INTERVAL_UNAVAILABLE;
427 
428                     ByteBuffer bb =
429                             ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN);
430                     bb.putInt(position);
431 
432                     mBluetoothGattServer.sendResponse(device, op.mRequestId,
433                             BluetoothGatt.GATT_SUCCESS, op.mOffset,
434                             Arrays.copyOfRange(bb.array(), op.mOffset, Integer.BYTES));
435                     return;
436                 }
437 
438                 if (op.mCharacteristic.getValue() != null) {
439                     mBluetoothGattServer.sendResponse(device, op.mRequestId,
440                             BluetoothGatt.GATT_SUCCESS, op.mOffset,
441                             Arrays.copyOfRange(op.mCharacteristic.getValue(), op.mOffset,
442                                     op.mCharacteristic.getValue().length));
443                 } else {
444                     Log.e(TAG,
445                             "Missing characteristic value for char: "
446                                     + op.mCharacteristic.getUuid());
447                     mBluetoothGattServer.sendResponse(device, op.mRequestId,
448                             BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH, op.mOffset, new byte[]{});
449                 }
450                 break;
451 
452             case WRITE_CHARACTERISTIC:
453                 if (op.mPreparedWrite) {
454                     status = BluetoothGatt.GATT_FAILURE;
455                 } else if (op.mOffset > 0) {
456                     status = BluetoothGatt.GATT_INVALID_OFFSET;
457                 } else {
458                     CharacteristicWriteHandler handler =
459                             mCharWriteCallback.get(op.mCharacteristic.getUuid());
460                     handler.onCharacteristicWriteRequest(
461                             device, op.mRequestId, op.mCharacteristic, op.mPreparedWrite,
462                             op.mResponseNeeded, op.mOffset, op.mValue);
463                     break;
464                 }
465 
466                 if (op.mResponseNeeded) {
467                     mBluetoothGattServer.sendResponse(
468                             device, op.mRequestId, status, op.mOffset, op.mValue);
469                 }
470                 break;
471 
472             case READ_DESCRIPTOR:
473                 if (op.mOffset > 1) {
474                     mBluetoothGattServer.sendResponse(device, op.mRequestId,
475                             BluetoothGatt.GATT_INVALID_OFFSET, op.mOffset, null);
476                     break;
477                 }
478 
479                 byte[] value = getCccBytes(device, op.mDescriptor.getCharacteristic().getUuid());
480                 if (value == null) {
481                     mBluetoothGattServer.sendResponse(
482                             device, op.mRequestId, BluetoothGatt.GATT_FAILURE, op.mOffset, null);
483                     break;
484                 }
485 
486                 value = Arrays.copyOfRange(value, op.mOffset, value.length);
487                 mBluetoothGattServer.sendResponse(
488                         device, op.mRequestId, BluetoothGatt.GATT_SUCCESS, op.mOffset, value);
489                 break;
490 
491             case WRITE_DESCRIPTOR:
492                 if (op.mPreparedWrite) {
493                     status = BluetoothGatt.GATT_FAILURE;
494                 } else if (op.mOffset > 0) {
495                     status = BluetoothGatt.GATT_INVALID_OFFSET;
496                 } else {
497                     status = BluetoothGatt.GATT_SUCCESS;
498                     setCcc(device, op.mDescriptor.getCharacteristic().getUuid(), op.mOffset,
499                             op.mValue, true);
500                 }
501 
502                 if (op.mResponseNeeded) {
503                     mBluetoothGattServer.sendResponse(
504                             device, op.mRequestId, status, op.mOffset, op.mValue);
505                 }
506                 break;
507 
508             default:
509                 break;
510         }
511     }
512 
onRejectedAuthorizationGattOperation(BluetoothDevice device, GattOpContext op)513     private void onRejectedAuthorizationGattOperation(BluetoothDevice device, GattOpContext op) {
514         Log.w(TAG, "onRejectedAuthorizationGattOperation device: " + device);
515 
516         switch (op.mOperation) {
517             case READ_CHARACTERISTIC:
518             case READ_DESCRIPTOR:
519                 mBluetoothGattServer.sendResponse(device, op.mRequestId,
520                         BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION, op.mOffset, null);
521                 break;
522             case WRITE_CHARACTERISTIC:
523                 if (op.mResponseNeeded) {
524                     mBluetoothGattServer.sendResponse(device, op.mRequestId,
525                             BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION, op.mOffset, null);
526                 } else {
527                     // In case of control point operations we can send an application error code
528                     if (op.mCharacteristic.getUuid().equals(UUID_MEDIA_CONTROL_POINT)) {
529                         setMediaControlRequestResult(
530                                 new Request(op.mValue[0], 0),
531                                 Request.Results.COMMAND_CANNOT_BE_COMPLETED);
532                     } else if (op.mCharacteristic.getUuid().equals(UUID_SEARCH_CONTROL_POINT)) {
533                         setSearchRequestResult(null, SearchRequest.Results.FAILURE, 0);
534                     }
535                 }
536                 break;
537             case WRITE_DESCRIPTOR:
538                 if (op.mResponseNeeded) {
539                     mBluetoothGattServer.sendResponse(device, op.mRequestId,
540                             BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION, op.mOffset, null);
541                 }
542                 break;
543 
544             default:
545                 break;
546         }
547     }
548 
ClearUnauthorizedGattOperations(BluetoothDevice device)549     private void ClearUnauthorizedGattOperations(BluetoothDevice device) {
550         if (VDBG) {
551             Log.d(TAG, "ClearUnauthorizedGattOperations device: " + device);
552         }
553 
554         synchronized (mPendingGattOperations) {
555             mPendingGattOperations.remove(device);
556         }
557     }
558 
ProcessPendingGattOperations(BluetoothDevice device)559     private void ProcessPendingGattOperations(BluetoothDevice device) {
560         if (VDBG) {
561             Log.d(TAG, "ProcessPendingGattOperations device: " + device);
562         }
563 
564         synchronized (mPendingGattOperations) {
565             if (mPendingGattOperations.containsKey(device)) {
566                 if (getDeviceAuthorization(device) == BluetoothDevice.ACCESS_ALLOWED) {
567                     for (GattOpContext op : mPendingGattOperations.get(device)) {
568                         onAuthorizedGattOperation(device, op);
569                     }
570                 } else {
571                     for (GattOpContext op : mPendingGattOperations.get(device)) {
572                         onRejectedAuthorizationGattOperation(device, op);
573                     }
574                 }
575                 ClearUnauthorizedGattOperations(device);
576             }
577         }
578     }
579 
restoreCccValuesForStoredDevices()580     private void restoreCccValuesForStoredDevices() {
581         for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
582             byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);
583 
584             if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
585                 return;
586             }
587 
588             List<ParcelUuid> uuidList = Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd));
589 
590             /* Restore CCCD values for device */
591             for (ParcelUuid uuid : uuidList) {
592                 setCcc(device, uuid.getUuid(), 0,
593                         BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE, false);
594             }
595         }
596     }
597 
598     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
599             new IBluetoothStateChangeCallback.Stub() {
600                 public void onBluetoothStateChange(boolean up) {
601                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
602                     if (up) {
603                         restoreCccValuesForStoredDevices();
604                     }
605                 }
606             };
607 
608     @VisibleForTesting
609     final BluetoothGattServerCallback mServerCallback = new BluetoothGattServerCallback() {
610         @Override
611         public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
612             super.onConnectionStateChange(device, status, newState);
613             if (VDBG) {
614                 Log.d(TAG, "BluetoothGattServerCallback: onConnectionStateChange");
615             }
616             if (newState == BluetoothProfile.STATE_DISCONNECTED) {
617                 ClearUnauthorizedGattOperations(device);
618             }
619         }
620 
621         @Override
622         public void onServiceAdded(int status, BluetoothGattService service) {
623             super.onServiceAdded(status, service);
624             if (VDBG) {
625                 Log.d(TAG, "BluetoothGattServerCallback: onServiceAdded");
626             }
627 
628             if (mCallbacks != null) {
629                 mCallbacks.onServiceInstanceRegistered((status != BluetoothGatt.GATT_SUCCESS)
630                                 ? ServiceStatus.UNKNOWN_ERROR
631                                 : ServiceStatus.OK,
632                         MediaControlGattService.this);
633             }
634 
635             mCharacteristics.get(CharId.CONTENT_CONTROL_ID)
636                     .setValue(mCcid, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
637             restoreCccValuesForStoredDevices();
638             setInitialCharacteristicValuesAndNotify();
639             initialStateRequest();
640         }
641 
642         @Override
643         public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
644                 BluetoothGattCharacteristic characteristic) {
645             super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
646             if (VDBG) {
647                 Log.d(TAG, "BluetoothGattServerCallback: onCharacteristicReadRequest offset= "
648                         + offset + " entire value= " + Arrays.toString(characteristic.getValue()));
649             }
650 
651             if ((characteristic.getProperties() & PROPERTY_READ) == 0) {
652                 mBluetoothGattServer.sendResponse(device, requestId,
653                         BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, null);
654                 return;
655             }
656 
657             GattOpContext op = new GattOpContext(
658                     GattOpContext.Operation.READ_CHARACTERISTIC, requestId, characteristic, null);
659             switch (getDeviceAuthorization(device)) {
660                 case BluetoothDevice.ACCESS_REJECTED:
661                     onRejectedAuthorizationGattOperation(device, op);
662                     break;
663                 case BluetoothDevice.ACCESS_UNKNOWN:
664                     onUnauthorizedGattOperation(device, op);
665                     break;
666                 default:
667                     onAuthorizedGattOperation(device, op);
668                     break;
669             }
670         }
671 
672         @Override
673         public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
674                 BluetoothGattCharacteristic characteristic, boolean preparedWrite,
675                 boolean responseNeeded, int offset, byte[] value) {
676             super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite,
677                     responseNeeded, offset, value);
678             if (VDBG) {
679                 Log.d(TAG,
680                         "BluetoothGattServerCallback: "
681                                 + "onCharacteristicWriteRequest");
682             }
683 
684             if ((characteristic.getProperties() & PROPERTY_WRITE)
685                     == 0) {
686                 mBluetoothGattServer.sendResponse(
687                         device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, value);
688                 return;
689             }
690 
691             GattOpContext op = new GattOpContext(GattOpContext.Operation.WRITE_CHARACTERISTIC,
692                     requestId, characteristic, null, preparedWrite, responseNeeded, offset, value);
693             switch (getDeviceAuthorization(device)) {
694                 case BluetoothDevice.ACCESS_REJECTED:
695                     onRejectedAuthorizationGattOperation(device, op);
696                     break;
697                 case BluetoothDevice.ACCESS_UNKNOWN:
698                     onUnauthorizedGattOperation(device, op);
699                     break;
700                 default:
701                     onAuthorizedGattOperation(device, op);
702                     break;
703             }
704         }
705 
706         @Override
707         public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset,
708                 BluetoothGattDescriptor descriptor) {
709             super.onDescriptorReadRequest(device, requestId, offset, descriptor);
710             if (VDBG) {
711                 Log.d(TAG,
712                         "BluetoothGattServerCallback: "
713                                 + "onDescriptorReadRequest");
714             }
715 
716             if ((descriptor.getPermissions() & BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED)
717                     == 0) {
718                 mBluetoothGattServer.sendResponse(
719                         device, requestId, BluetoothGatt.GATT_READ_NOT_PERMITTED, offset, null);
720                 return;
721             }
722 
723             GattOpContext op = new GattOpContext(
724                     GattOpContext.Operation.READ_DESCRIPTOR, requestId, null, descriptor);
725             switch (getDeviceAuthorization(device)) {
726                 case BluetoothDevice.ACCESS_REJECTED:
727                     onRejectedAuthorizationGattOperation(device, op);
728                     break;
729                 case BluetoothDevice.ACCESS_UNKNOWN:
730                     onUnauthorizedGattOperation(device, op);
731                     break;
732                 default:
733                     onAuthorizedGattOperation(device, op);
734                     break;
735             }
736         }
737 
738         @Override
739         public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
740                 BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded,
741                 int offset, byte[] value) {
742             super.onDescriptorWriteRequest(
743                     device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
744             if (VDBG) {
745                 Log.d(TAG,
746                         "BluetoothGattServerCallback: "
747                                 + "onDescriptorWriteRequest");
748             }
749 
750             if ((descriptor.getPermissions() & BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED)
751                     == 0) {
752                 mBluetoothGattServer.sendResponse(
753                         device, requestId, BluetoothGatt.GATT_WRITE_NOT_PERMITTED, offset, value);
754                 return;
755             }
756 
757             GattOpContext op = new GattOpContext(GattOpContext.Operation.WRITE_DESCRIPTOR,
758                     requestId, null, descriptor, preparedWrite, responseNeeded, offset, value);
759             switch (getDeviceAuthorization(device)) {
760                 case BluetoothDevice.ACCESS_REJECTED:
761                     onRejectedAuthorizationGattOperation(device, op);
762                     break;
763                 case BluetoothDevice.ACCESS_UNKNOWN:
764                     onUnauthorizedGattOperation(device, op);
765                     break;
766                 default:
767                     onAuthorizedGattOperation(device, op);
768                     break;
769             }
770         }
771     };
772 
initialStateRequest()773     private void initialStateRequest() {
774         List<PlayerStateField> field_list = new ArrayList<>();
775 
776         if (isFeatureSupported(ServiceFeature.MEDIA_STATE)) {
777             field_list.add(PlayerStateField.PLAYBACK_STATE);
778         }
779 
780         if (isFeatureSupported(ServiceFeature.PLAYER_ICON_URL)) {
781             field_list.add(PlayerStateField.ICON_URL);
782         }
783 
784         if (isFeatureSupported(ServiceFeature.PLAYER_ICON_OBJ_ID)) {
785             field_list.add(PlayerStateField.ICON_OBJ_ID);
786         }
787 
788         if (isFeatureSupported(ServiceFeature.PLAYER_NAME)) {
789             field_list.add(PlayerStateField.PLAYER_NAME);
790         }
791 
792         if (isFeatureSupported(ServiceFeature.PLAYING_ORDER_SUPPORTED)) {
793             field_list.add(PlayerStateField.PLAYING_ORDER_SUPPORTED);
794         }
795 
796         mCallbacks.onPlayerStateRequest(field_list.stream().toArray(PlayerStateField[]::new));
797     }
798 
setInitialCharacteristicValues(boolean notify)799     private void setInitialCharacteristicValues(boolean notify) {
800         updateMediaStateChar(mCurrentMediaState.getValue());
801         updatePlayerNameChar("", notify);
802         updatePlayerIconUrlChar("");
803 
804         // Object IDs will have a length of 0;
805         updateObjectID(ObjectIds.PLAYER_ICON_OBJ_ID, -1, notify);
806         updateObjectID(ObjectIds.CURRENT_TRACK_SEGMENT_OBJ_ID, -1, notify);
807         updateObjectID(ObjectIds.CURRENT_TRACK_OBJ_ID, -1, notify);
808         updateObjectID(ObjectIds.NEXT_TRACK_OBJ_ID, -1, notify);
809         updateObjectID(ObjectIds.CURRENT_GROUP_OBJ_ID, -1, notify);
810         updateObjectID(ObjectIds.PARENT_GROUP_OBJ_ID, -1, notify);
811         updateObjectID(ObjectIds.SEARCH_RESULT_OBJ_ID, -1, notify);
812         updateTrackTitleChar("", notify);
813         updateTrackDurationChar(TRACK_DURATION_UNAVAILABLE, notify);
814         updateTrackPositionChar(TRACK_POSITION_UNAVAILABLE, notify);
815         updatePlaybackSpeedChar(1, notify);
816         updateSeekingSpeedChar(1, notify);
817         updatePlayingOrderSupportedChar(SupportedPlayingOrder.SINGLE_ONCE);
818         updatePlayingOrderChar(PlayingOrder.SINGLE_ONCE, notify);
819         updateSupportedOpcodesChar(Request.SupportedOpcodes.NONE, notify);
820     }
821 
setInitialCharacteristicValues()822     private void setInitialCharacteristicValues() {
823         setInitialCharacteristicValues(false);
824     }
825 
setInitialCharacteristicValuesAndNotify()826     private void setInitialCharacteristicValuesAndNotify() {
827         setInitialCharacteristicValues(true);
828     }
829 
830     /**
831      * A proxy class that facilitates testing of the McpService class.
832      *
833      * This is necessary due to the "final" attribute of the BluetoothGattServer class. In order to
834      * test the correct functioning of the McpService class, the final class must be put into a
835      * container that can be mocked correctly.
836      */
837     public class BluetoothGattServerProxy {
838         private BluetoothGattServer mBluetoothGattServer;
839         private BluetoothManager mBluetoothManager;
840 
BluetoothGattServerProxy(BluetoothGattServer gatt, BluetoothManager manager)841         public BluetoothGattServerProxy(BluetoothGattServer gatt, BluetoothManager manager) {
842             mBluetoothManager = manager;
843             mBluetoothGattServer = gatt;
844         }
845 
addService(BluetoothGattService service)846         public boolean addService(BluetoothGattService service) {
847             return mBluetoothGattServer.addService(service);
848         }
849 
removeService(BluetoothGattService service)850         public boolean removeService(BluetoothGattService service) {
851             return mBluetoothGattServer.removeService(service);
852         }
853 
close()854         public void close() {
855             mBluetoothGattServer.close();
856         }
857 
sendResponse( BluetoothDevice device, int requestId, int status, int offset, byte[] value)858         public boolean sendResponse(
859                 BluetoothDevice device, int requestId, int status, int offset, byte[] value) {
860             return mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
861         }
862 
notifyCharacteristicChanged(BluetoothDevice device, BluetoothGattCharacteristic characteristic, boolean confirm)863         public boolean notifyCharacteristicChanged(BluetoothDevice device,
864                 BluetoothGattCharacteristic characteristic, boolean confirm) {
865             return mBluetoothGattServer.notifyCharacteristicChanged(
866                     device, characteristic, confirm);
867         }
868 
getConnectedDevices()869         public List<BluetoothDevice> getConnectedDevices() {
870             return mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER);
871         }
872     }
873 
MediaControlGattService(McpService mcpService, @NonNull MediaControlServiceCallbacks callbacks, int ccid)874     protected MediaControlGattService(McpService mcpService,
875             @NonNull MediaControlServiceCallbacks callbacks, int ccid) {
876         mContext = mcpService;
877         mCallbacks = callbacks;
878         mCcid = ccid;
879 
880         mMcpService = mcpService;
881         mAdapterService =  Objects.requireNonNull(AdapterService.getAdapterService(),
882                 "AdapterService shouldn't be null when creating MediaControlCattService");
883 
884         IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
885         if (mgr != null) {
886             try {
887                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
888             } catch (RemoteException e) {
889                 throw e.rethrowFromSystemServer();
890             }
891         }
892     }
893 
init(UUID scvUuid)894     protected boolean init(UUID scvUuid) {
895         mCccDescriptorValues = new HashMap<>();
896 
897         mFeatures = mCallbacks.onGetFeatureFlags();
898 
899         // Verify the minimum required set of supported player features
900         if ((mFeatures & ServiceFeature.ALL_MANDATORY_SERVICE_FEATURES)
901                 != ServiceFeature.ALL_MANDATORY_SERVICE_FEATURES) {
902             mCallbacks.onServiceInstanceRegistered(ServiceStatus.INVALID_FEATURE_FLAGS, null);
903             return false;
904         }
905 
906         // Init attribute database
907         return initGattService(scvUuid);
908     }
909 
handleObjectIdRequest(int objField, long objId)910     private void handleObjectIdRequest(int objField, long objId) {
911         mCallbacks.onSetObjectIdRequest(objField, objId);
912     }
913 
handlePlayingOrderRequest(int order)914     private void handlePlayingOrderRequest(int order) {
915         mCallbacks.onPlayingOrderSetRequest(order);
916     }
917 
handlePlaybackSpeedRequest(int speed)918     private void handlePlaybackSpeedRequest(int speed) {
919         float floatingSpeed = (float) Math.pow(2, speed / 64);
920         mCallbacks.onPlaybackSpeedSetRequest(floatingSpeed);
921     }
922 
handleTrackPositionRequest(long position)923     private void handleTrackPositionRequest(long position) {
924         final long positionMs = (position != INTERVAL_UNAVAILABLE)
925                 ? mcsIntervalToMilliseconds(position)
926                 : TRACK_POSITION_UNAVAILABLE;
927 
928         mCallbacks.onTrackPositionSetRequest(positionMs);
929     }
930 
getMediaControlPointRequestPayloadLength(int opcode)931     private static int getMediaControlPointRequestPayloadLength(int opcode) {
932         switch (opcode) {
933             case Request.Opcodes.MOVE_RELATIVE:
934             case Request.Opcodes.GOTO_SEGMENT:
935             case Request.Opcodes.GOTO_TRACK:
936             case Request.Opcodes.GOTO_GROUP:
937                 return 4;
938             default:
939                 return 0;
940         }
941     }
942 
943     @VisibleForTesting
handleMediaControlPointRequest(BluetoothDevice device, byte[] value)944     int handleMediaControlPointRequest(BluetoothDevice device, byte[] value) {
945         final int payloadOffset = 1;
946         final int opcode = value[0];
947 
948         // Test for RFU bits and currently supported opcodes
949         if (!isOpcodeSupported(opcode)) {
950             Log.i(TAG, "handleMediaControlPointRequest: " + Request.Opcodes.toString(opcode)
951                      + " not supported");
952             mHandler.post(() -> {
953                 setMediaControlRequestResult(new Request(opcode, 0),
954                         Request.Results.OPCODE_NOT_SUPPORTED);
955             });
956             return BluetoothGatt.GATT_SUCCESS;
957         }
958 
959         if (getMediaControlPointRequestPayloadLength(opcode) != (value.length - payloadOffset)) {
960             Log.w(TAG, "handleMediaControlPointRequest: " + Request.Opcodes.toString(opcode)
961                     + " bad payload length");
962             return BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
963         }
964 
965         // Only some requests have payload
966         int intVal = 0;
967         if (opcode == Request.Opcodes.MOVE_RELATIVE
968                 || opcode == Request.Opcodes.GOTO_SEGMENT
969                 || opcode == Request.Opcodes.GOTO_TRACK
970                 || opcode == Request.Opcodes.GOTO_GROUP) {
971             intVal = ByteBuffer.wrap(value, payloadOffset, value.length - payloadOffset)
972                     .order(ByteOrder.LITTLE_ENDIAN)
973                     .getInt();
974 
975             // If the argument is time interval, convert to milliseconds time domain
976             if (opcode == Request.Opcodes.MOVE_RELATIVE) {
977                 intVal = new Long(mcsIntervalToMilliseconds(intVal)).intValue();
978             }
979         }
980 
981         Request req = new Request(opcode, intVal);
982 
983         if (DBG) {
984             Log.d(TAG, "handleMediaControlPointRequest: sending " + Request.Opcodes.toString(opcode)
985                     + " request up");
986         }
987 
988         // TODO: Activate/deactivate devices with ActiveDeviceManager
989         if (req.getOpcode() == Request.Opcodes.PLAY) {
990             if (mAdapterService.getActiveDevices(BluetoothProfile.A2DP).size() > 0) {
991                 A2dpService.getA2dpService().removeActiveDevice(false);
992             }
993             if (mAdapterService.getActiveDevices(BluetoothProfile.HEARING_AID).size() > 0) {
994                 HearingAidService.getHearingAidService().removeActiveDevice(false);
995             }
996             if (mLeAudioService == null) {
997                 mLeAudioService = LeAudioService.getLeAudioService();
998             }
999             mLeAudioService.setActiveDevice(device);
1000         }
1001         mCallbacks.onMediaControlRequest(req);
1002 
1003         return BluetoothGatt.GATT_SUCCESS;
1004     }
1005 
setCallbacks(MediaControlServiceCallbacks callbacks)1006     public void setCallbacks(MediaControlServiceCallbacks callbacks) {
1007         mCallbacks = callbacks;
1008     }
1009 
1010     @VisibleForTesting
setServiceManagerForTesting(McpService manager)1011     protected void setServiceManagerForTesting(McpService manager) {
1012         mMcpService = manager;
1013     }
1014 
1015     @VisibleForTesting
setBluetoothGattServerForTesting(BluetoothGattServerProxy proxy)1016     void setBluetoothGattServerForTesting(BluetoothGattServerProxy proxy) {
1017         mBluetoothGattServer = proxy;
1018     }
1019 
1020     @VisibleForTesting
setLeAudioServiceForTesting(LeAudioService leAudioService)1021     void setLeAudioServiceForTesting(LeAudioService leAudioService) {
1022         mLeAudioService = leAudioService;
1023     }
1024 
initGattService(UUID serviceUuid)1025     private boolean initGattService(UUID serviceUuid) {
1026         if (DBG) {
1027             Log.d(TAG, "initGattService uuid: " + serviceUuid);
1028         }
1029 
1030         if (mBluetoothGattServer == null) {
1031             BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
1032             BluetoothGattServer server = manager.openGattServer(mContext, mServerCallback);
1033             if (server == null) {
1034                 Log.e(TAG, "Failed to start BluetoothGattServer for MCP");
1035                 //TODO: This now effectively makes MCP unusable, but fixes tests
1036                 // Handle this error more gracefully, verify BluetoothInstrumentationTests
1037                 // are passing after fix is applied
1038                 return false;
1039             }
1040             mBluetoothGattServer = new BluetoothGattServerProxy(server, manager);
1041         }
1042 
1043         mGattService =
1044                 new BluetoothGattService(serviceUuid, BluetoothGattService.SERVICE_TYPE_PRIMARY);
1045 
1046         for (Pair<UUID, CharacteristicData> entry : getUuidCharacteristicList()) {
1047             CharacteristicData desc = entry.second;
1048             UUID uuid = entry.first;
1049             if (VDBG) {
1050                 Log.d(TAG, "Checking uuid: " + uuid);
1051             }
1052             if ((mFeatures & desc.featureFlag) != 0) {
1053                 int notifyProp = (((mFeatures & desc.ntfFeatureFlag) != 0)
1054                         ? PROPERTY_NOTIFY
1055                         : 0);
1056 
1057                 BluetoothGattCharacteristic myChar = new BluetoothGattCharacteristic(
1058                         uuid, desc.properties | notifyProp, desc.permissions);
1059 
1060                 // Add CCC descriptor if notification is supported
1061                 if ((myChar.getProperties() & PROPERTY_NOTIFY) != 0) {
1062                     BluetoothGattDescriptor cccDesc = new BluetoothGattDescriptor(UUID_CCCD,
1063                             BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED
1064                                     | BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED);
1065                     if (VDBG) {
1066                         Log.d(TAG, "Adding descriptor: " + cccDesc);
1067                     }
1068                     myChar.addDescriptor(cccDesc);
1069                 }
1070 
1071                 if (VDBG) {
1072                     Log.d(TAG, "Adding char: " + myChar);
1073                 }
1074                 mCharacteristics.put(desc.id, myChar);
1075                 mGattService.addCharacteristic(myChar);
1076             }
1077         }
1078         if (VDBG) {
1079             Log.d(TAG, "Adding service: " + mGattService);
1080         }
1081         return mBluetoothGattServer.addService(mGattService);
1082     }
1083 
removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device)1084     private void removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device) {
1085         List<ParcelUuid> uuidList;
1086         byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);
1087 
1088         if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
1089             uuidList = new ArrayList<ParcelUuid>();
1090         } else {
1091             uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd)));
1092 
1093             if (!uuidList.contains(charUuid)) {
1094                 Log.d(TAG, "Characteristic CCCD can't be removed (not cached): "
1095                         + charUuid.toString());
1096                 return;
1097             }
1098         }
1099 
1100         uuidList.remove(charUuid);
1101 
1102         if (!device.setMetadata(METADATA_GMCS_CCCD,
1103                 Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
1104             Log.e(TAG, "Can't set CCCD for GMCS characteristic UUID: " + charUuid.toString()
1105                     + ", (remove)");
1106         }
1107     }
1108 
addUuidToMetadata(ParcelUuid charUuid, BluetoothDevice device)1109     private void addUuidToMetadata(ParcelUuid charUuid, BluetoothDevice device) {
1110         List<ParcelUuid> uuidList;
1111         byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);
1112 
1113         if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
1114             uuidList = new ArrayList<ParcelUuid>();
1115         } else {
1116             uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd)));
1117 
1118             if (uuidList.contains(charUuid)) {
1119                 Log.d(TAG, "Characteristic CCCD already added: " + charUuid.toString());
1120                 return;
1121             }
1122         }
1123 
1124         uuidList.add(charUuid);
1125 
1126         if (!device.setMetadata(METADATA_GMCS_CCCD,
1127                 Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
1128             Log.e(TAG, "Can't set CCCD for GMCS characteristic UUID: " + charUuid.toString()
1129                     + ", (add)");
1130         }
1131     }
1132 
1133     @VisibleForTesting
setCcc(BluetoothDevice device, UUID charUuid, int offset, byte[] value, boolean store)1134     void setCcc(BluetoothDevice device, UUID charUuid, int offset, byte[] value, boolean store) {
1135         HashMap<UUID, Short> characteristicCcc = mCccDescriptorValues.get(device.getAddress());
1136         if (characteristicCcc == null) {
1137             characteristicCcc = new HashMap<>();
1138             mCccDescriptorValues.put(device.getAddress(), characteristicCcc);
1139         }
1140 
1141         characteristicCcc.put(charUuid,
1142                 ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getShort());
1143 
1144         if (!store) {
1145             return;
1146         }
1147 
1148         if (Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
1149             addUuidToMetadata(new ParcelUuid(charUuid), device);
1150         } else if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
1151             removeUuidFromMetadata(new ParcelUuid(charUuid), device);
1152         } else {
1153             Log.e(TAG, "Not handled CCC value: " + Arrays.toString(value));
1154         }
1155     }
1156 
getCccBytes(BluetoothDevice device, UUID charUuid)1157     private byte[] getCccBytes(BluetoothDevice device, UUID charUuid) {
1158         Map<UUID, Short> characteristicCcc = mCccDescriptorValues.get(device.getAddress());
1159         if (characteristicCcc != null) {
1160             ByteBuffer bb = ByteBuffer.allocate(Short.BYTES).order(ByteOrder.LITTLE_ENDIAN);
1161             Short ccc = characteristicCcc.get(charUuid);
1162             if (ccc != null) {
1163                 bb.putShort(characteristicCcc.get(charUuid));
1164                 return bb.array();
1165             }
1166         }
1167         return BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
1168     }
1169 
1170     @Override
updatePlaybackState(MediaState state)1171     public void updatePlaybackState(MediaState state) {
1172         if (DBG) {
1173             Log.d(TAG, "updatePlaybackState");
1174         }
1175 
1176         if ((state.getValue() <= MediaState.STATE_MAX.getValue())
1177                 && (state.getValue() >= MediaState.STATE_MIN.getValue())) {
1178             updateMediaStateChar(state.getValue());
1179         }
1180     }
1181 
1182     @VisibleForTesting
getMediaStateChar()1183     int getMediaStateChar() {
1184         if (!isFeatureSupported(ServiceFeature.MEDIA_STATE)) return MediaState.INACTIVE.getValue();
1185 
1186         BluetoothGattCharacteristic stateChar =
1187                 mCharacteristics.get(CharId.MEDIA_STATE);
1188 
1189         if (stateChar.getValue() != null) {
1190             return stateChar.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
1191         }
1192 
1193         return MediaState.INACTIVE.getValue();
1194     }
1195 
1196     @VisibleForTesting
updateMediaStateChar(int state)1197     void updateMediaStateChar(int state) {
1198         if (DBG) {
1199             Log.d(TAG, "updateMediaStateChar");
1200         }
1201 
1202         if (!isFeatureSupported(ServiceFeature.MEDIA_STATE)) return;
1203 
1204         if (DBG) {
1205             Log.d(TAG, "updateMediaStateChar setting to state= " + state);
1206         }
1207 
1208         BluetoothGattCharacteristic stateChar =
1209                 mCharacteristics.get(CharId.MEDIA_STATE);
1210         stateChar.setValue(state, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
1211         notifyCharacteristic(stateChar, null);
1212     }
1213 
updateObjectID(int objectIdField, long objectIdValue, boolean notify)1214     private void updateObjectID(int objectIdField, long objectIdValue, boolean notify) {
1215         if (DBG) {
1216             Log.d(TAG, "updateObjectID");
1217         }
1218         int feature = ObjectIds.GetMatchingServiceFeature(objectIdField);
1219 
1220         if (!isFeatureSupported(feature)) return;
1221 
1222         updateObjectIdChar(mCharacteristics.get(CharId.FromFeature(feature)),
1223                 objectIdValue, null, notify);
1224     }
1225 
1226     @Override
updateObjectID(int objectIdField, long objectIdValue)1227     public void updateObjectID(int objectIdField, long objectIdValue) {
1228         updateObjectID(objectIdField, objectIdValue, true);
1229     }
1230 
1231     @Override
setMediaControlRequestResult(Request request, Request.Results resultStatus)1232     public void setMediaControlRequestResult(Request request,
1233             Request.Results resultStatus) {
1234         if (DBG) {
1235             Log.d(TAG, "setMediaControlRequestResult");
1236         }
1237 
1238         if (getMediaStateChar() == MediaState.INACTIVE.getValue()) {
1239             resultStatus = Request.Results.MEDIA_PLAYER_INACTIVE;
1240         }
1241 
1242         ByteBuffer bb = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
1243         bb.put((byte) request.getOpcode());
1244         bb.put((byte) resultStatus.getValue());
1245 
1246         BluetoothGattCharacteristic characteristic =
1247                 mCharacteristics.get(CharId.MEDIA_CONTROL_POINT);
1248         characteristic.setValue(bb.array());
1249         notifyCharacteristic(characteristic, null);
1250     }
1251 
1252     @Override
setSearchRequestResult(SearchRequest request, SearchRequest.Results resultStatus, long resultObjectId)1253     public void setSearchRequestResult(SearchRequest request,
1254             SearchRequest.Results resultStatus, long resultObjectId) {
1255         if (DBG) {
1256             Log.d(TAG, "setSearchRequestResult");
1257         }
1258 
1259         // TODO: There is no Object Trasfer Service implementation.
1260         BluetoothGattCharacteristic characteristic =
1261                 mCharacteristics.get(CharId.SEARCH_CONTROL_POINT);
1262         characteristic.setValue(new byte[]{SEARCH_CONTROL_POINT_RESULT_FAILURE});
1263         notifyCharacteristic(characteristic, null);
1264     }
1265 
1266     @Override
updatePlayerState(Map stateFields)1267     public void updatePlayerState(Map stateFields) {
1268         if (stateFields.isEmpty()) {
1269             return;
1270         }
1271 
1272         if (stateFields.containsKey(PlayerStateField.PLAYBACK_STATE)) {
1273             MediaState playbackState =
1274                     (MediaState) stateFields.get(PlayerStateField.PLAYBACK_STATE);
1275             if (DBG) {
1276                 Log.d(TAG,
1277                         "updatePlayerState: playbackState= "
1278                                 + stateFields.get(PlayerStateField.PLAYBACK_STATE));
1279             }
1280 
1281             if (playbackState == MediaState.INACTIVE) {
1282                 setInitialCharacteristicValues();
1283             }
1284         }
1285         final boolean doNotifyValueChange = true;
1286 
1287         // Additional fields that may be requested by the service to complete the new state info
1288         List<PlayerStateField> reqFieldList = null;
1289 
1290         if (stateFields.containsKey(PlayerStateField.PLAYBACK_SPEED)) {
1291             updatePlaybackSpeedChar(
1292                     (float) stateFields.get(PlayerStateField.PLAYBACK_SPEED), doNotifyValueChange);
1293         }
1294 
1295         if (stateFields.containsKey(PlayerStateField.PLAYING_ORDER_SUPPORTED)) {
1296             updatePlayingOrderSupportedChar(
1297                     (Integer) stateFields.get(PlayerStateField.PLAYING_ORDER_SUPPORTED));
1298         }
1299 
1300         if (stateFields.containsKey(PlayerStateField.PLAYING_ORDER)) {
1301             updatePlayingOrderChar((PlayingOrder) stateFields.get(PlayerStateField.PLAYING_ORDER),
1302                     doNotifyValueChange);
1303         }
1304 
1305         if (stateFields.containsKey(PlayerStateField.TRACK_POSITION)) {
1306             updateTrackPositionChar(
1307                     (long) stateFields.get(PlayerStateField.TRACK_POSITION), doNotifyValueChange);
1308         }
1309 
1310         if (stateFields.containsKey(PlayerStateField.PLAYER_NAME)) {
1311             String name = (String) stateFields.get(PlayerStateField.PLAYER_NAME);
1312             if ((getPlayerNameChar() != null) && (name.compareTo(getPlayerNameChar()) != 0)) {
1313                 updatePlayerNameChar(name, doNotifyValueChange);
1314 
1315                 // Most likely the player has changed - request critical info fields
1316                 reqFieldList = new ArrayList<>();
1317                 reqFieldList.add(PlayerStateField.PLAYBACK_STATE);
1318                 reqFieldList.add(PlayerStateField.TRACK_DURATION);
1319 
1320                 if (isFeatureSupported(ServiceFeature.MEDIA_CONTROL_POINT_OPCODES_SUPPORTED)) {
1321                     reqFieldList.add(PlayerStateField.OPCODES_SUPPORTED);
1322                 }
1323                 if (isFeatureSupported(ServiceFeature.PLAYING_ORDER_SUPPORTED)) {
1324                     reqFieldList.add(PlayerStateField.PLAYING_ORDER_SUPPORTED);
1325                 }
1326                 if (isFeatureSupported(ServiceFeature.PLAYING_ORDER)) {
1327                     reqFieldList.add(PlayerStateField.PLAYING_ORDER);
1328                 }
1329                 if (isFeatureSupported(ServiceFeature.PLAYER_ICON_OBJ_ID)) {
1330                     reqFieldList.add(PlayerStateField.ICON_OBJ_ID);
1331                 }
1332                 if (isFeatureSupported(ServiceFeature.PLAYER_ICON_URL)) {
1333                     reqFieldList.add(PlayerStateField.ICON_URL);
1334                 }
1335             }
1336         }
1337 
1338         if (stateFields.containsKey(PlayerStateField.ICON_URL)) {
1339             updatePlayerIconUrlChar((String) stateFields.get(PlayerStateField.ICON_URL));
1340         }
1341 
1342         if (stateFields.containsKey(PlayerStateField.ICON_OBJ_ID)) {
1343             updateIconObjIdChar((Long) stateFields.get(PlayerStateField.ICON_OBJ_ID));
1344         }
1345 
1346         if (stateFields.containsKey(PlayerStateField.OPCODES_SUPPORTED)) {
1347             updateSupportedOpcodesChar(
1348                     (Integer) stateFields.get(PlayerStateField.OPCODES_SUPPORTED),
1349                     doNotifyValueChange);
1350         }
1351 
1352         // Notify track change if any of these have changed
1353         boolean notifyTrackChange = false;
1354         if (stateFields.containsKey(PlayerStateField.TRACK_TITLE)) {
1355             String newTitle = (String) stateFields.get(PlayerStateField.TRACK_TITLE);
1356 
1357             if (getTrackTitleChar().compareTo(newTitle) != 0) {
1358                 updateTrackTitleChar((String) stateFields.get(PlayerStateField.TRACK_TITLE),
1359                         doNotifyValueChange);
1360                 notifyTrackChange = true;
1361             }
1362         }
1363 
1364         if (stateFields.containsKey(PlayerStateField.TRACK_DURATION)) {
1365             long newTrackDuration = (long) (stateFields.get(PlayerStateField.TRACK_DURATION));
1366             if (getTrackDurationChar() != newTrackDuration) {
1367                 updateTrackDurationChar(newTrackDuration, doNotifyValueChange);
1368                 notifyTrackChange = true;
1369             }
1370         }
1371 
1372         if (stateFields.containsKey(PlayerStateField.PLAYBACK_STATE)) {
1373             mCurrentMediaState =
1374                     (MediaState) stateFields.get(PlayerStateField.PLAYBACK_STATE);
1375         }
1376 
1377         int mediaState = getMediaStateChar();
1378         if (mediaState != mCurrentMediaState.getValue()) {
1379             updateMediaStateChar(mCurrentMediaState.getValue());
1380         }
1381 
1382         if (stateFields.containsKey(PlayerStateField.SEEKING_SPEED)) {
1383             int playbackState = getMediaStateChar();
1384             // Seeking speed should be 1.0f (char. value of 0) when not in seeking state.
1385             // [Ref. Media Control Service v1.0, sec. 3.9]
1386             if (playbackState == MediaState.SEEKING.getValue()) {
1387                 updateSeekingSpeedChar((float) stateFields.get(PlayerStateField.SEEKING_SPEED),
1388                         doNotifyValueChange);
1389             } else {
1390                 updateSeekingSpeedChar(1.0f, doNotifyValueChange);
1391             }
1392         }
1393 
1394         // Notify track change as the last step of all track change related characteristic changes.
1395         // [Ref. Media Control Service v1.0, sec. 3.4.1]
1396         if (notifyTrackChange) {
1397             if (isFeatureSupported(ServiceFeature.TRACK_CHANGED)) {
1398                 BluetoothGattCharacteristic myChar =
1399                         mCharacteristics.get(CharId.TRACK_CHANGED);
1400                 myChar.setValue(new byte[]{});
1401                 notifyCharacteristic(myChar, null);
1402             }
1403         }
1404 
1405         if (reqFieldList != null) {
1406             // Don't ask for those that we just got.
1407             reqFieldList.removeAll(stateFields.keySet());
1408 
1409             if (!reqFieldList.isEmpty()) {
1410                 mCallbacks.onPlayerStateRequest(
1411                         reqFieldList.stream().toArray(PlayerStateField[]::new));
1412             }
1413         }
1414     }
1415 
1416     @Override
getContentControlId()1417     public int getContentControlId() {
1418         return mCcid;
1419     }
1420 
1421     @Override
onDeviceAuthorizationSet(BluetoothDevice device)1422     public void onDeviceAuthorizationSet(BluetoothDevice device) {
1423         ProcessPendingGattOperations(device);
1424     }
1425 
1426     @Override
destroy()1427     public void destroy() {
1428         if (DBG) {
1429             Log.d(TAG, "Destroy");
1430         }
1431 
1432         if (mBluetoothGattServer == null) {
1433             return;
1434         }
1435 
1436         if (mBluetoothGattServer.removeService(mGattService)) {
1437             if (mCallbacks != null) {
1438                 mCallbacks.onServiceInstanceUnregistered(ServiceStatus.OK);
1439             }
1440         }
1441 
1442         mBluetoothGattServer.close();
1443     }
1444 
1445     @VisibleForTesting
updatePlayingOrderChar(PlayingOrder order, boolean notify)1446     void updatePlayingOrderChar(PlayingOrder order, boolean notify) {
1447         if (VDBG) {
1448             Log.d(TAG, "updatePlayingOrderChar: " + order);
1449         }
1450         if (!isFeatureSupported(ServiceFeature.PLAYING_ORDER)) return;
1451 
1452         BluetoothGattCharacteristic orderChar = mCharacteristics.get(CharId.PLAYING_ORDER);
1453         Integer playingOrder = null;
1454 
1455         if (orderChar.getValue() != null) {
1456             playingOrder = orderChar.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
1457         }
1458 
1459         if ((playingOrder == null) || (playingOrder != order.getValue())) {
1460             orderChar.setValue(order.getValue(), BluetoothGattCharacteristic.FORMAT_UINT8, 0);
1461             if (notify && isFeatureSupported(ServiceFeature.PLAYING_ORDER_NOTIFY)) {
1462                 notifyCharacteristic(orderChar, null);
1463             }
1464         }
1465     }
1466 
notifyCharacteristic(@onNull BluetoothGattCharacteristic characteristic, @Nullable BluetoothDevice originDevice)1467     private void notifyCharacteristic(@NonNull BluetoothGattCharacteristic characteristic,
1468             @Nullable BluetoothDevice originDevice) {
1469         for (BluetoothDevice device : mBluetoothGattServer.getConnectedDevices()) {
1470             // Skip the origin device who changed the characteristic
1471             if (device.equals(originDevice)) {
1472                 continue;
1473             }
1474 
1475             HashMap<UUID, Short> charCccMap = mCccDescriptorValues.get(device.getAddress());
1476             if (charCccMap == null) continue;
1477 
1478             byte[] ccc = getCccBytes(device, characteristic.getUuid());
1479             if (VDBG) {
1480                 Log.d(TAG, "notifyCharacteristic char= " + characteristic.getUuid().toString()
1481                         + " cccVal= "
1482                         + ByteBuffer.wrap(ccc).order(ByteOrder.LITTLE_ENDIAN).getShort());
1483             }
1484 
1485             if (!Arrays.equals(ccc, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) continue;
1486 
1487             if (VDBG) Log.d(TAG, "notifyCharacteristic sending notification");
1488 
1489             mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false);
1490         }
1491     }
1492 
SpeedFloatToCharacteristicIntValue(float speed)1493     private static int SpeedFloatToCharacteristicIntValue(float speed) {
1494         /* The spec. defined valid speed range is <0.25, 3.957> as float input, resulting in
1495          * <-128, 127> output integer range. */
1496         if (speed < 0) {
1497             speed = -speed;
1498         }
1499         if (speed < PLAY_SPEED_MIN) {
1500             speed = PLAY_SPEED_MIN;
1501         } else if (speed > PLAY_SPEED_MAX) {
1502             speed = PLAY_SPEED_MAX;
1503         }
1504 
1505         return new Float(64 * Math.log(speed) / Math.log(2)).intValue();
1506     }
1507 
CharacteristicSpeedIntValueToSpeedFloat(Integer speed)1508     private static float CharacteristicSpeedIntValueToSpeedFloat(Integer speed) {
1509         return new Float(Math.pow(2, (speed.floatValue() / 64.0f)));
1510     }
1511 
1512     @VisibleForTesting
getSeekingSpeedChar()1513     Float getSeekingSpeedChar() {
1514         Float speed = null;
1515 
1516         if (isFeatureSupported(ServiceFeature.SEEKING_SPEED)) {
1517             BluetoothGattCharacteristic characteristic =
1518                     mCharacteristics.get(CharId.SEEKING_SPEED);
1519             if (characteristic.getValue() != null) {
1520                 Integer intVal =
1521                         characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT8, 0);
1522                 speed = CharacteristicSpeedIntValueToSpeedFloat(intVal);
1523             }
1524         }
1525 
1526         return speed;
1527     }
1528 
1529     @VisibleForTesting
updateSeekingSpeedChar(float speed, boolean notify)1530     void updateSeekingSpeedChar(float speed, boolean notify) {
1531         if (VDBG) {
1532             Log.d(TAG, "updateSeekingSpeedChar: " + speed);
1533         }
1534         if (isFeatureSupported(ServiceFeature.SEEKING_SPEED)) {
1535             if ((getSeekingSpeedChar() == null) || (getSeekingSpeedChar() != speed)) {
1536                 BluetoothGattCharacteristic characteristic =
1537                         mCharacteristics.get(CharId.SEEKING_SPEED);
1538                 int intSpeed = SpeedFloatToCharacteristicIntValue(speed);
1539                 characteristic.setValue(intSpeed, BluetoothGattCharacteristic.FORMAT_SINT8, 0);
1540                 if (notify && isFeatureSupported(ServiceFeature.SEEKING_SPEED_NOTIFY)) {
1541                     notifyCharacteristic(characteristic, null);
1542                 }
1543             }
1544         }
1545     }
1546 
1547     @VisibleForTesting
getPlaybackSpeedChar()1548     Float getPlaybackSpeedChar() {
1549         Float speed = null;
1550 
1551         if (!isFeatureSupported(ServiceFeature.PLAYBACK_SPEED)) return null;
1552 
1553         BluetoothGattCharacteristic characteristic = mCharacteristics.get(CharId.PLAYBACK_SPEED);
1554         if (characteristic.getValue() != null) {
1555             Integer intVal =
1556                     characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT8, 0);
1557             speed = CharacteristicSpeedIntValueToSpeedFloat(intVal);
1558         }
1559 
1560         return speed;
1561     }
1562 
1563     @VisibleForTesting
updatePlaybackSpeedChar(float speed, boolean notify)1564     void updatePlaybackSpeedChar(float speed, boolean notify) {
1565         if (VDBG) {
1566             Log.d(TAG, "updatePlaybackSpeedChar: " + speed);
1567         }
1568 
1569         if (!isFeatureSupported(ServiceFeature.PLAYBACK_SPEED)) return;
1570 
1571         // Reject if no changes were made
1572         if ((getPlaybackSpeedChar() == null) || (getPlaybackSpeedChar() != speed)) {
1573             BluetoothGattCharacteristic characteristic =
1574                     mCharacteristics.get(CharId.PLAYBACK_SPEED);
1575             int intSpeed = SpeedFloatToCharacteristicIntValue(speed);
1576             characteristic.setValue(intSpeed, BluetoothGattCharacteristic.FORMAT_SINT8, 0);
1577             if (notify && isFeatureSupported(ServiceFeature.PLAYBACK_SPEED_NOTIFY)) {
1578                 notifyCharacteristic(characteristic, null);
1579             }
1580         }
1581     }
1582 
1583     @VisibleForTesting
updateTrackPositionChar(long positionMs, boolean forceNotify)1584     void updateTrackPositionChar(long positionMs, boolean forceNotify) {
1585         if (VDBG) {
1586             Log.d(TAG, "updateTrackPositionChar: " + positionMs);
1587         }
1588         if (!isFeatureSupported(ServiceFeature.TRACK_POSITION)) return;
1589 
1590         final int position = (positionMs != TRACK_POSITION_UNAVAILABLE)
1591                 ? new Long(millisecondsToMcsInterval(positionMs)).intValue()
1592                 : INTERVAL_UNAVAILABLE;
1593 
1594         BluetoothGattCharacteristic characteristic =
1595                 mCharacteristics.get(CharId.TRACK_POSITION);
1596         characteristic.setValue(position, BluetoothGattCharacteristic.FORMAT_SINT32, 0);
1597 
1598         if (isFeatureSupported(ServiceFeature.TRACK_POSITION_NOTIFY)) {
1599             // Position should be notified only while seeking (frequency is implementation
1600             // specific), on pause, or position change, but not during the playback.
1601             if ((getMediaStateChar() == MediaState.PAUSED.getValue())
1602                     || (getMediaStateChar() == MediaState.SEEKING.getValue())
1603                     || forceNotify) {
1604                 notifyCharacteristic(characteristic, null);
1605             }
1606         }
1607     }
1608 
getTrackDurationChar()1609     private long getTrackDurationChar() {
1610         if (!isFeatureSupported(ServiceFeature.TRACK_DURATION)) return TRACK_DURATION_UNAVAILABLE;
1611 
1612         BluetoothGattCharacteristic characteristic = mCharacteristics.get(CharId.TRACK_DURATION);
1613         if (characteristic.getValue() != null) {
1614             int duration =
1615                     characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT32, 0);
1616             return (duration != INTERVAL_UNAVAILABLE) ? mcsIntervalToMilliseconds(duration)
1617                     : TRACK_DURATION_UNAVAILABLE;
1618         }
1619         return TRACK_DURATION_UNAVAILABLE;
1620     }
1621 
1622     @VisibleForTesting
updateTrackDurationChar(long durationMs, boolean notify)1623     void updateTrackDurationChar(long durationMs, boolean notify) {
1624         if (VDBG) {
1625             Log.d(TAG, "updateTrackDurationChar: " + durationMs);
1626         }
1627         if (isFeatureSupported(ServiceFeature.TRACK_DURATION)) {
1628             final int duration = (durationMs != TRACK_DURATION_UNAVAILABLE)
1629                     ? new Long(millisecondsToMcsInterval(durationMs)).intValue()
1630                     : INTERVAL_UNAVAILABLE;
1631 
1632             BluetoothGattCharacteristic characteristic =
1633                     mCharacteristics.get(CharId.TRACK_DURATION);
1634             characteristic.setValue(duration, BluetoothGattCharacteristic.FORMAT_SINT32, 0);
1635             if (notify && isFeatureSupported(ServiceFeature.TRACK_DURATION_NOTIFY)) {
1636                 notifyCharacteristic(characteristic, null);
1637             }
1638         }
1639     }
1640 
getTrackTitleChar()1641     private String getTrackTitleChar() {
1642         if (isFeatureSupported(ServiceFeature.TRACK_TITLE)) {
1643             BluetoothGattCharacteristic characteristic =
1644                     mCharacteristics.get(CharId.TRACK_TITLE);
1645             if (characteristic.getValue() != null) {
1646                 return characteristic.getStringValue(0);
1647             }
1648         }
1649 
1650         return "";
1651     }
1652 
1653     @VisibleForTesting
updateTrackTitleChar(String title, boolean notify)1654     void updateTrackTitleChar(String title, boolean notify) {
1655         if (VDBG) {
1656             Log.d(TAG, "updateTrackTitleChar: " + title);
1657         }
1658         if (isFeatureSupported(ServiceFeature.TRACK_TITLE)) {
1659             BluetoothGattCharacteristic characteristic =
1660                     mCharacteristics.get(CharId.TRACK_TITLE);
1661             characteristic.setValue(title);
1662             if (notify && isFeatureSupported(ServiceFeature.TRACK_TITLE_NOTIFY)) {
1663                 notifyCharacteristic(characteristic, null);
1664             }
1665         }
1666     }
1667 
1668     @VisibleForTesting
updateSupportedOpcodesChar(int opcodes, boolean notify)1669     void updateSupportedOpcodesChar(int opcodes, boolean notify) {
1670         if (VDBG) {
1671             Log.d(TAG, "updateSupportedOpcodesChar: " + opcodes);
1672         }
1673 
1674         if (!isFeatureSupported(ServiceFeature.MEDIA_CONTROL_POINT_OPCODES_SUPPORTED)) return;
1675 
1676         BluetoothGattCharacteristic characteristic = mCharacteristics.get(
1677                 CharId.MEDIA_CONTROL_POINT_OPCODES_SUPPORTED);
1678         // Do nothing if nothing has changed
1679         if (characteristic.getValue() != null
1680                 && characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0) == opcodes) {
1681             return;
1682         }
1683 
1684         if (VDBG) {
1685             Log.d(TAG, "updateSupportedOpcodesChar setting char");
1686         }
1687 
1688         characteristic.setValue(opcodes, BluetoothGattCharacteristic.FORMAT_UINT32, 0);
1689         if (notify
1690                 && isFeatureSupported(
1691                 ServiceFeature.MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_NOTIFY)) {
1692             notifyCharacteristic(characteristic, null);
1693         }
1694 
1695     }
1696 
1697     @VisibleForTesting
updatePlayingOrderSupportedChar(int supportedOrder)1698     void updatePlayingOrderSupportedChar(int supportedOrder) {
1699         if (VDBG) {
1700             Log.d(TAG, "updatePlayingOrderSupportedChar: " + supportedOrder);
1701         }
1702         if (isFeatureSupported(ServiceFeature.PLAYING_ORDER_SUPPORTED)) {
1703             mCharacteristics.get(CharId.PLAYING_ORDER_SUPPORTED)
1704                     .setValue(supportedOrder, BluetoothGattCharacteristic.FORMAT_UINT16, 0);
1705         }
1706     }
1707 
updateIconObjIdChar(Long objId)1708     private void updateIconObjIdChar(Long objId) {
1709         if (isFeatureSupported(ServiceFeature.PLAYER_ICON_OBJ_ID)) {
1710             updateObjectIdChar(mCharacteristics.get(CharId.PLAYER_ICON_OBJ_ID), objId,
1711                     null, true);
1712         }
1713     }
1714 
1715     @VisibleForTesting
byteArray2ObjId(byte[] buffer)1716     public long byteArray2ObjId(byte[] buffer) {
1717         ByteBuffer bb = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN);
1718         bb.put(buffer, 0, 6);
1719         // Move position to beginnng after putting data to buffer
1720         bb.position(0);
1721         return bb.getLong();
1722     }
1723 
1724     @VisibleForTesting
objId2ByteArray(long objId)1725     public byte[] objId2ByteArray(long objId) {
1726         if (objId < 0) {
1727             return new byte[0];
1728         }
1729 
1730         ByteBuffer bb = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN);
1731         bb.putInt((int) objId);
1732         bb.putShort((short) (objId >> Integer.SIZE));
1733 
1734         return bb.array();
1735     }
1736 
updateObjectIdChar(BluetoothGattCharacteristic characteristic, long objId, BluetoothDevice originDevice, boolean notify)1737     private void updateObjectIdChar(BluetoothGattCharacteristic characteristic, long objId,
1738             BluetoothDevice originDevice, boolean notify) {
1739         characteristic.setValue(objId2ByteArray(objId));
1740         if ((characteristic.getProperties() & PROPERTY_NOTIFY) != 0) {
1741             // Notify all clients but not the originDevice
1742             if (notify) {
1743                 notifyCharacteristic(characteristic, originDevice);
1744             }
1745         }
1746     }
1747 
updatePlayerIconUrlChar(String url)1748     private void updatePlayerIconUrlChar(String url) {
1749         if (VDBG) {
1750             Log.d(TAG, "updatePlayerIconUrlChar: " + url);
1751         }
1752         if (isFeatureSupported(ServiceFeature.PLAYER_ICON_URL)) {
1753             mCharacteristics.get(CharId.PLAYER_ICON_URL).setValue(url);
1754         }
1755     }
1756 
getPlayerNameChar()1757     private String getPlayerNameChar() {
1758         if (!isFeatureSupported(ServiceFeature.PLAYER_NAME)) return null;
1759 
1760         BluetoothGattCharacteristic characteristic = mCharacteristics.get(CharId.PLAYER_NAME);
1761         if (characteristic.getValue() != null) {
1762             return characteristic.getStringValue(0);
1763         }
1764         return null;
1765     }
1766 
1767     @VisibleForTesting
updatePlayerNameChar(String name, boolean notify)1768     void updatePlayerNameChar(String name, boolean notify) {
1769         if (VDBG) {
1770             Log.d(TAG, "updatePlayerNameChar: " + name);
1771         }
1772 
1773         if (!isFeatureSupported(ServiceFeature.PLAYER_NAME)) return;
1774 
1775         BluetoothGattCharacteristic characteristic =
1776                 mCharacteristics.get(CharId.PLAYER_NAME);
1777         characteristic.setValue(name);
1778         if (notify && isFeatureSupported(ServiceFeature.PLAYER_NAME_NOTIFY)) {
1779             notifyCharacteristic(characteristic, null);
1780         }
1781     }
1782 
isFeatureSupported(long featureBit)1783     private boolean isFeatureSupported(long featureBit) {
1784         if (DBG) {
1785             Log.w(TAG, "Feature " + ServiceFeature.toString(featureBit) + " support: "
1786                     + ((mFeatures & featureBit) != 0));
1787         }
1788         return (mFeatures & featureBit) != 0;
1789     }
1790 
1791     @VisibleForTesting
isOpcodeSupported(int opcode)1792     boolean isOpcodeSupported(int opcode) {
1793         if (opcode < Request.Opcodes.PLAY || opcode > Request.Opcodes.GOTO_GROUP) {
1794             return false;
1795         }
1796 
1797         if (!isFeatureSupported(ServiceFeature.MEDIA_CONTROL_POINT_OPCODES_SUPPORTED)) {
1798             return false;
1799         }
1800 
1801         Integer opcodeSupportBit = Request.OpcodeToOpcodeSupport.get(opcode);
1802         if (opcodeSupportBit == null) return false;
1803 
1804         return (mCharacteristics.get(CharId.MEDIA_CONTROL_POINT_OPCODES_SUPPORTED)
1805                     .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0)
1806                 & opcodeSupportBit) == opcodeSupportBit;
1807     }
1808 
1809     private interface CharacteristicWriteHandler {
onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value)1810         void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
1811                 BluetoothGattCharacteristic characteristic, boolean preparedWrite,
1812                 boolean responseNeeded, int offset, byte[] value);
1813     }
1814 
1815     private static final class CharacteristicData {
1816         public final int id;
1817         public final int properties;
1818         public final int permissions;
1819         public final long featureFlag;
1820         public final long ntfFeatureFlag;
1821 
CharacteristicData( int id, long featureFlag, long ntfFeatureFlag, int properties, int permissions)1822         private CharacteristicData(
1823                 int id, long featureFlag, long ntfFeatureFlag, int properties, int permissions) {
1824             this.id = id;
1825             this.featureFlag = featureFlag;
1826             this.ntfFeatureFlag = ntfFeatureFlag;
1827             this.properties = properties;
1828             this.permissions = permissions;
1829         }
1830     }
1831 
1832     private final static class CharId {
1833         public static final int PLAYER_NAME =
1834                 Long.numberOfTrailingZeros(ServiceFeature.PLAYER_NAME);
1835         public static final int PLAYER_ICON_OBJ_ID =
1836                 Long.numberOfTrailingZeros(ServiceFeature.PLAYER_ICON_OBJ_ID);
1837         public static final int PLAYER_ICON_URL =
1838                 Long.numberOfTrailingZeros(ServiceFeature.PLAYER_ICON_URL);
1839         public static final int TRACK_CHANGED =
1840                 Long.numberOfTrailingZeros(ServiceFeature.TRACK_CHANGED);
1841         public static final int TRACK_TITLE =
1842                 Long.numberOfTrailingZeros(ServiceFeature.TRACK_TITLE);
1843         public static final int TRACK_DURATION =
1844                 Long.numberOfTrailingZeros(ServiceFeature.TRACK_DURATION);
1845         public static final int TRACK_POSITION =
1846                 Long.numberOfTrailingZeros(ServiceFeature.TRACK_POSITION);
1847         public static final int PLAYBACK_SPEED =
1848                 Long.numberOfTrailingZeros(ServiceFeature.PLAYBACK_SPEED);
1849         public static final int SEEKING_SPEED =
1850                 Long.numberOfTrailingZeros(ServiceFeature.SEEKING_SPEED);
1851         public static final int CURRENT_TRACK_SEGMENT_OBJ_ID =
1852                 Long.numberOfTrailingZeros(ServiceFeature.CURRENT_TRACK_SEGMENT_OBJ_ID);
1853         public static final int CURRENT_TRACK_OBJ_ID =
1854                 Long.numberOfTrailingZeros(ServiceFeature.CURRENT_TRACK_OBJ_ID);
1855         public static final int NEXT_TRACK_OBJ_ID =
1856                 Long.numberOfTrailingZeros(ServiceFeature.NEXT_TRACK_OBJ_ID);
1857         public static final int CURRENT_GROUP_OBJ_ID =
1858                 Long.numberOfTrailingZeros(ServiceFeature.CURRENT_GROUP_OBJ_ID);
1859         public static final int PARENT_GROUP_OBJ_ID =
1860                 Long.numberOfTrailingZeros(ServiceFeature.PARENT_GROUP_OBJ_ID);
1861         public static final int PLAYING_ORDER =
1862                 Long.numberOfTrailingZeros(ServiceFeature.PLAYING_ORDER);
1863         public static final int PLAYING_ORDER_SUPPORTED =
1864                 Long.numberOfTrailingZeros(ServiceFeature.PLAYING_ORDER_SUPPORTED);
1865         public static final int MEDIA_STATE =
1866                 Long.numberOfTrailingZeros(ServiceFeature.MEDIA_STATE);
1867         public static final int MEDIA_CONTROL_POINT =
1868                 Long.numberOfTrailingZeros(ServiceFeature.MEDIA_CONTROL_POINT);
1869         public static final int MEDIA_CONTROL_POINT_OPCODES_SUPPORTED =
1870                 Long.numberOfTrailingZeros(ServiceFeature.MEDIA_CONTROL_POINT_OPCODES_SUPPORTED);
1871         public static final int SEARCH_RESULT_OBJ_ID =
1872                 Long.numberOfTrailingZeros(ServiceFeature.SEARCH_RESULT_OBJ_ID);
1873         public static final int SEARCH_CONTROL_POINT =
1874                 Long.numberOfTrailingZeros(ServiceFeature.SEARCH_CONTROL_POINT);
1875         public static final int CONTENT_CONTROL_ID =
1876                 Long.numberOfTrailingZeros(ServiceFeature.CONTENT_CONTROL_ID);
1877 
FromFeature(long feature)1878         public static int FromFeature(long feature) {
1879             return Long.numberOfTrailingZeros(feature);
1880         }
1881     }
1882 
1883     private static final UUID UUID_CCCD = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
1884 
1885     /* All characteristic attributes (UUIDs, properties, permissions and flags needed to enable
1886      * them) This is set according to the Media Control Service Specification.
1887      */
getUuidCharacteristicList()1888     private static List<Pair<UUID, CharacteristicData>> getUuidCharacteristicList() {
1889         List<Pair<UUID, CharacteristicData>> characteristics = new ArrayList<>();
1890         characteristics.add(new Pair<>(UUID_PLAYER_NAME,
1891                 new CharacteristicData(CharId.PLAYER_NAME, ServiceFeature.PLAYER_NAME,
1892                         ServiceFeature.PLAYER_NAME_NOTIFY, PROPERTY_READ,
1893                         PERMISSION_READ_ENCRYPTED)));
1894         characteristics.add(new Pair<>(UUID_PLAYER_ICON_OBJ_ID,
1895                 new CharacteristicData(CharId.PLAYER_ICON_OBJ_ID, ServiceFeature.PLAYER_ICON_OBJ_ID,
1896                         // Notifications unsupported
1897                         0, PROPERTY_READ, PERMISSION_READ_ENCRYPTED)));
1898         characteristics.add(new Pair<>(UUID_PLAYER_ICON_URL,
1899                 new CharacteristicData(CharId.PLAYER_ICON_URL, ServiceFeature.PLAYER_ICON_URL,
1900                         // Notifications unsupported
1901                         0, PROPERTY_READ, PERMISSION_READ_ENCRYPTED)));
1902         characteristics.add(new Pair<>(UUID_TRACK_CHANGED,
1903                 new CharacteristicData(CharId.TRACK_CHANGED, ServiceFeature.TRACK_CHANGED,
1904                         // Mandatory notification if char. exists.
1905                         ServiceFeature.TRACK_CHANGED, PROPERTY_NOTIFY, 0)));
1906         characteristics.add(new Pair<>(UUID_TRACK_TITLE,
1907                 new CharacteristicData(CharId.TRACK_TITLE, ServiceFeature.TRACK_TITLE,
1908                         ServiceFeature.TRACK_TITLE_NOTIFY, PROPERTY_READ,
1909                         PERMISSION_READ_ENCRYPTED)));
1910         characteristics.add(new Pair<>(UUID_TRACK_DURATION,
1911                 new CharacteristicData(CharId.TRACK_DURATION, ServiceFeature.TRACK_DURATION,
1912                         ServiceFeature.TRACK_DURATION_NOTIFY, PROPERTY_READ,
1913                         PERMISSION_READ_ENCRYPTED)));
1914         characteristics.add(new Pair<>(UUID_TRACK_POSITION,
1915                 new CharacteristicData(CharId.TRACK_POSITION, ServiceFeature.TRACK_POSITION,
1916                         ServiceFeature.TRACK_POSITION_NOTIFY,
1917                         PROPERTY_READ | PROPERTY_WRITE | PROPERTY_WRITE_NO_RESPONSE,
1918                         PERMISSION_READ_ENCRYPTED | PERMISSION_WRITE_ENCRYPTED)));
1919         characteristics.add(new Pair<>(UUID_PLAYBACK_SPEED,
1920                 new CharacteristicData(CharId.PLAYBACK_SPEED, ServiceFeature.PLAYBACK_SPEED,
1921                         ServiceFeature.PLAYBACK_SPEED_NOTIFY,
1922                         PROPERTY_READ | PROPERTY_WRITE | PROPERTY_WRITE_NO_RESPONSE,
1923                         PERMISSION_READ_ENCRYPTED | PERMISSION_WRITE_ENCRYPTED)));
1924         characteristics.add(new Pair<>(UUID_SEEKING_SPEED,
1925                 new CharacteristicData(CharId.SEEKING_SPEED, ServiceFeature.SEEKING_SPEED,
1926                         ServiceFeature.SEEKING_SPEED_NOTIFY, PROPERTY_READ,
1927                         PERMISSION_READ_ENCRYPTED)));
1928         characteristics.add(new Pair<>(UUID_CURRENT_TRACK_SEGMENT_OBJ_ID,
1929                 new CharacteristicData(CharId.CURRENT_TRACK_SEGMENT_OBJ_ID,
1930                         ServiceFeature.CURRENT_TRACK_SEGMENT_OBJ_ID,
1931                         // Notifications unsupported
1932                         0, PROPERTY_READ, PERMISSION_READ_ENCRYPTED)));
1933         characteristics.add(new Pair<>(UUID_CURRENT_TRACK_OBJ_ID,
1934                 new CharacteristicData(CharId.CURRENT_TRACK_OBJ_ID,
1935                         ServiceFeature.CURRENT_TRACK_OBJ_ID,
1936                         ServiceFeature.CURRENT_TRACK_OBJ_ID_NOTIFY,
1937                         PROPERTY_READ | PROPERTY_WRITE | PROPERTY_WRITE_NO_RESPONSE,
1938                         PERMISSION_READ_ENCRYPTED | PERMISSION_WRITE_ENCRYPTED)));
1939         characteristics.add(new Pair<>(UUID_NEXT_TRACK_OBJ_ID,
1940                 new CharacteristicData(CharId.NEXT_TRACK_OBJ_ID, ServiceFeature.NEXT_TRACK_OBJ_ID,
1941                         ServiceFeature.NEXT_TRACK_OBJ_ID_NOTIFY,
1942                         PROPERTY_READ | PROPERTY_WRITE | PROPERTY_WRITE_NO_RESPONSE,
1943                         PERMISSION_READ_ENCRYPTED | PERMISSION_WRITE_ENCRYPTED)));
1944         characteristics.add(new Pair<>(UUID_CURRENT_GROUP_OBJ_ID,
1945                 new CharacteristicData(CharId.CURRENT_GROUP_OBJ_ID,
1946                         ServiceFeature.CURRENT_GROUP_OBJ_ID,
1947                         ServiceFeature.CURRENT_GROUP_OBJ_ID_NOTIFY,
1948                         PROPERTY_READ | PROPERTY_WRITE | PROPERTY_WRITE_NO_RESPONSE,
1949                         PERMISSION_READ_ENCRYPTED | PERMISSION_WRITE_ENCRYPTED)));
1950         characteristics.add(new Pair<>(UUID_PARENT_GROUP_OBJ_ID,
1951                 new CharacteristicData(CharId.PARENT_GROUP_OBJ_ID,
1952                         ServiceFeature.PARENT_GROUP_OBJ_ID,
1953                         ServiceFeature.PARENT_GROUP_OBJ_ID_NOTIFY, PROPERTY_READ,
1954                         PERMISSION_READ_ENCRYPTED)));
1955         characteristics.add(new Pair<>(UUID_PLAYING_ORDER,
1956                 new CharacteristicData(CharId.PLAYING_ORDER, ServiceFeature.PLAYING_ORDER,
1957                         ServiceFeature.PLAYING_ORDER_NOTIFY,
1958                         PROPERTY_READ | PROPERTY_WRITE | PROPERTY_WRITE_NO_RESPONSE,
1959                         PERMISSION_READ_ENCRYPTED | PERMISSION_WRITE_ENCRYPTED)));
1960         characteristics.add(new Pair<>(UUID_PLAYING_ORDER_SUPPORTED,
1961                 new CharacteristicData(CharId.PLAYING_ORDER_SUPPORTED,
1962                         ServiceFeature.PLAYING_ORDER_SUPPORTED,
1963                         // Notifications unsupported
1964                         0, PROPERTY_READ, PERMISSION_READ_ENCRYPTED)));
1965         characteristics.add(new Pair<>(UUID_MEDIA_STATE,
1966                 new CharacteristicData(CharId.MEDIA_STATE, ServiceFeature.MEDIA_STATE,
1967                         // Mandatory notification if char. exists.
1968                         ServiceFeature.MEDIA_STATE, PROPERTY_READ | PROPERTY_NOTIFY,
1969                         PERMISSION_READ_ENCRYPTED)));
1970         characteristics.add(new Pair<>(UUID_MEDIA_CONTROL_POINT,
1971                 new CharacteristicData(CharId.MEDIA_CONTROL_POINT,
1972                         ServiceFeature.MEDIA_CONTROL_POINT,
1973                         // Mandatory notification if char. exists.
1974                         ServiceFeature.MEDIA_CONTROL_POINT,
1975                         PROPERTY_WRITE | PROPERTY_WRITE_NO_RESPONSE | PROPERTY_NOTIFY,
1976                         PERMISSION_WRITE_ENCRYPTED)));
1977         characteristics.add(new Pair<>(UUID_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED,
1978                 new CharacteristicData(CharId.MEDIA_CONTROL_POINT_OPCODES_SUPPORTED,
1979                         ServiceFeature.MEDIA_CONTROL_POINT_OPCODES_SUPPORTED,
1980                         ServiceFeature.MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_NOTIFY, PROPERTY_READ,
1981                         PERMISSION_READ_ENCRYPTED)));
1982         characteristics.add(new Pair<>(UUID_SEARCH_RESULT_OBJ_ID,
1983                 new CharacteristicData(CharId.SEARCH_RESULT_OBJ_ID,
1984                         ServiceFeature.SEARCH_RESULT_OBJ_ID,
1985                         // Mandatory notification if char. exists.
1986                         ServiceFeature.SEARCH_RESULT_OBJ_ID, PROPERTY_READ | PROPERTY_NOTIFY,
1987                         PERMISSION_READ_ENCRYPTED)));
1988         characteristics.add(new Pair<>(UUID_SEARCH_CONTROL_POINT,
1989                 new CharacteristicData(CharId.SEARCH_CONTROL_POINT,
1990                         ServiceFeature.SEARCH_CONTROL_POINT,
1991                         // Mandatory notification if char. exists.
1992                         ServiceFeature.SEARCH_CONTROL_POINT,
1993                         PROPERTY_WRITE | PROPERTY_WRITE_NO_RESPONSE | PROPERTY_NOTIFY,
1994                         PERMISSION_WRITE_ENCRYPTED)));
1995         characteristics.add(new Pair<>(UUID_CONTENT_CONTROL_ID,
1996                 new CharacteristicData(CharId.CONTENT_CONTROL_ID, ServiceFeature.CONTENT_CONTROL_ID,
1997                         // Notifications unsupported
1998                         0, PROPERTY_READ, PERMISSION_READ_ENCRYPTED)));
1999         return characteristics;
2000     }
2001 
dump(StringBuilder sb)2002     public void dump(StringBuilder sb) {
2003         sb.append("\tMediaControlService instance:");
2004         sb.append("\n\t\tCcid = " + mCcid);
2005         sb.append("\n\t\tFeatures:" + ServiceFeature.featuresToString(mFeatures, "\n\t\t\t"));
2006 
2007         BluetoothGattCharacteristic characteristic =
2008                 mCharacteristics.get(CharId.PLAYER_NAME);
2009         if (characteristic == null) {
2010             sb.append("\n\t\tPlayer name: <No Player>");
2011         } else {
2012             sb.append("\n\t\tPlayer name: " + characteristic.getStringValue(0));
2013         }
2014 
2015         sb.append("\n\t\tCurrentPlaybackState = " + mCurrentMediaState);
2016         for (Map.Entry<String, HashMap<UUID, Short>> deviceEntry
2017                 : mCccDescriptorValues.entrySet()) {
2018             sb.append("\n\t\tCCC states for device: " + "xx:xx:xx:xx:"
2019                     + deviceEntry.getKey().substring(12));
2020             for (Map.Entry<UUID, Short> entry : deviceEntry.getValue().entrySet()) {
2021                 sb.append("\n\t\t\tCharacteristic: " + mcsUuidToString(entry.getKey()) + ", value: "
2022                         + Utils.cccIntToStr(entry.getValue()));
2023             }
2024         }
2025     }
2026 }
2027