1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.avrcpcontroller; 18 19 import android.annotation.RequiresPermission; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothAvrcpPlayerSettings; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothProfile; 24 import android.bluetooth.IBluetoothAvrcpController; 25 import android.content.AttributionSource; 26 import android.content.Intent; 27 import android.media.AudioManager; 28 import android.support.v4.media.MediaBrowserCompat.MediaItem; 29 import android.support.v4.media.session.PlaybackStateCompat; 30 import android.sysprop.BluetoothProperties; 31 import android.util.Log; 32 33 import com.android.bluetooth.BluetoothPrefs; 34 import com.android.bluetooth.R; 35 import com.android.bluetooth.Utils; 36 import com.android.bluetooth.a2dpsink.A2dpSinkService; 37 import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService.BrowseResult; 38 import com.android.bluetooth.btservice.AdapterService; 39 import com.android.bluetooth.btservice.ProfileService; 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.modules.utils.SynchronousResultReceiver; 42 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.UUID; 48 import java.util.concurrent.ConcurrentHashMap; 49 50 /** 51 * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application. 52 */ 53 public class AvrcpControllerService extends ProfileService { 54 static final String TAG = "AvrcpControllerService"; 55 static final int MAXIMUM_CONNECTED_DEVICES = 5; 56 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 57 static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); 58 59 /** 60 * Owned Components 61 */ 62 private static final String ON_ERROR_SETTINGS_ACTIVITY = 63 BluetoothPrefs.class.getCanonicalName(); 64 private static final String COVER_ART_PROVIDER = AvrcpCoverArtProvider.class.getCanonicalName(); 65 66 /* 67 * Play State Values from JNI 68 */ 69 private static final byte JNI_PLAY_STATUS_STOPPED = 0x00; 70 private static final byte JNI_PLAY_STATUS_PLAYING = 0x01; 71 private static final byte JNI_PLAY_STATUS_PAUSED = 0x02; 72 private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03; 73 @VisibleForTesting 74 static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04; 75 private static final byte JNI_PLAY_STATUS_ERROR = -1; 76 77 /* Folder/Media Item scopes. 78 * Keep in sync with AVRCP 1.6 sec. 6.10.1 79 */ 80 public static final byte BROWSE_SCOPE_PLAYER_LIST = 0x00; 81 public static final byte BROWSE_SCOPE_VFS = 0x01; 82 public static final byte BROWSE_SCOPE_SEARCH = 0x02; 83 public static final byte BROWSE_SCOPE_NOW_PLAYING = 0x03; 84 85 /* Folder navigation directions 86 * This is borrowed from AVRCP 1.6 spec and must be kept with same values 87 */ 88 public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00; 89 public static final byte FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01; 90 91 /* 92 * KeyCoded for Pass Through Commands 93 */ 94 public static final int PASS_THRU_CMD_ID_PLAY = 0x44; 95 public static final int PASS_THRU_CMD_ID_PAUSE = 0x46; 96 public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41; 97 public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42; 98 public static final int PASS_THRU_CMD_ID_STOP = 0x45; 99 public static final int PASS_THRU_CMD_ID_FF = 0x49; 100 public static final int PASS_THRU_CMD_ID_REWIND = 0x48; 101 public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B; 102 public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C; 103 104 /* Key State Variables */ 105 public static final int KEY_STATE_PRESSED = 0; 106 public static final int KEY_STATE_RELEASED = 1; 107 108 /* Active Device State Variables */ 109 public static final int DEVICE_STATE_INACTIVE = 0; 110 public static final int DEVICE_STATE_ACTIVE = 1; 111 112 static BrowseTree sBrowseTree; 113 private static AvrcpControllerService sService; 114 115 private AdapterService mAdapterService; 116 117 protected Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap = 118 new ConcurrentHashMap<>(1); 119 private BluetoothDevice mActiveDevice = null; 120 private final Object mActiveDeviceLock = new Object(); 121 122 private boolean mCoverArtEnabled = false; 123 protected AvrcpCoverArtManager mCoverArtManager; 124 125 private class ImageDownloadCallback implements AvrcpCoverArtManager.Callback { 126 @Override onImageDownloadComplete(BluetoothDevice device, AvrcpCoverArtManager.DownloadEvent event)127 public void onImageDownloadComplete(BluetoothDevice device, 128 AvrcpCoverArtManager.DownloadEvent event) { 129 if (DBG) { 130 Log.d(TAG, "Image downloaded [device: " + device + ", uuid: " + event.getUuid() 131 + ", uri: " + event.getUri()); 132 } 133 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 134 if (stateMachine == null) { 135 Log.e(TAG, "No state machine found for device " + device); 136 mCoverArtManager.removeImage(device, event.getUuid()); 137 return; 138 } 139 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_IMAGE_DOWNLOADED, 140 event); 141 } 142 } 143 144 static { classInitNative()145 classInitNative(); 146 } 147 isEnabled()148 public static boolean isEnabled() { 149 return BluetoothProperties.isProfileAvrcpControllerEnabled().orElse(false); 150 } 151 152 @Override start()153 protected synchronized boolean start() { 154 initNative(); 155 setComponentAvailable(ON_ERROR_SETTINGS_ACTIVITY, true); 156 mAdapterService = AdapterService.getAdapterService(); 157 mCoverArtEnabled = getResources().getBoolean(R.bool.avrcp_controller_enable_cover_art); 158 if (mCoverArtEnabled) { 159 setComponentAvailable(COVER_ART_PROVIDER, true); 160 mCoverArtManager = new AvrcpCoverArtManager(this, new ImageDownloadCallback()); 161 } 162 sBrowseTree = new BrowseTree(null); 163 sService = this; 164 165 // Start the media browser service. 166 Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class); 167 startService(startIntent); 168 setActiveDevice(null); 169 return true; 170 } 171 172 @Override stop()173 protected synchronized boolean stop() { 174 setActiveDevice(null); 175 Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class); 176 stopService(stopIntent); 177 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 178 stateMachine.quitNow(); 179 } 180 mDeviceStateMap.clear(); 181 182 sService = null; 183 sBrowseTree = null; 184 if (mCoverArtManager != null) { 185 mCoverArtManager.cleanup(); 186 mCoverArtManager = null; 187 setComponentAvailable(COVER_ART_PROVIDER, false); 188 } 189 setComponentAvailable(ON_ERROR_SETTINGS_ACTIVITY, false); 190 return true; 191 } 192 getAvrcpControllerService()193 public static AvrcpControllerService getAvrcpControllerService() { 194 return sService; 195 } 196 197 /** 198 * Get the current active device 199 */ getActiveDevice()200 public BluetoothDevice getActiveDevice() { 201 synchronized (mActiveDeviceLock) { 202 return mActiveDevice; 203 } 204 } 205 206 /** 207 * Set the current active device, notify devices of activity status 208 */ 209 @VisibleForTesting setActiveDevice(BluetoothDevice device)210 boolean setActiveDevice(BluetoothDevice device) { 211 A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); 212 if (a2dpSinkService == null) { 213 return false; 214 } 215 216 BluetoothDevice currentActiveDevice = getActiveDevice(); 217 if ((device == null && currentActiveDevice == null) 218 || (device != null && device.equals(currentActiveDevice))) { 219 return true; 220 } 221 222 // Try and update the active device 223 synchronized (mActiveDeviceLock) { 224 if (a2dpSinkService.setActiveDevice(device)) { 225 mActiveDevice = device; 226 227 // Pause the old active device 228 if (currentActiveDevice != null) { 229 AvrcpControllerStateMachine oldStateMachine = 230 getStateMachine(currentActiveDevice); 231 if (oldStateMachine != null) { 232 oldStateMachine.setDeviceState(DEVICE_STATE_INACTIVE); 233 } 234 } 235 236 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 237 if (stateMachine != null) { 238 stateMachine.setDeviceState(DEVICE_STATE_ACTIVE); 239 } else { 240 BluetoothMediaBrowserService.reset(); 241 } 242 return true; 243 } 244 } 245 return false; 246 } 247 toPlaybackStateFromJni(int fromJni)248 private int toPlaybackStateFromJni(int fromJni) { 249 int playbackState = PlaybackStateCompat.STATE_NONE; 250 switch (fromJni) { 251 case JNI_PLAY_STATUS_STOPPED: 252 playbackState = PlaybackStateCompat.STATE_STOPPED; 253 break; 254 case JNI_PLAY_STATUS_PLAYING: 255 playbackState = PlaybackStateCompat.STATE_PLAYING; 256 break; 257 case JNI_PLAY_STATUS_PAUSED: 258 playbackState = PlaybackStateCompat.STATE_PAUSED; 259 break; 260 case JNI_PLAY_STATUS_FWD_SEEK: 261 playbackState = PlaybackStateCompat.STATE_FAST_FORWARDING; 262 break; 263 case JNI_PLAY_STATUS_REV_SEEK: 264 playbackState = PlaybackStateCompat.STATE_REWINDING; 265 break; 266 default: 267 playbackState = PlaybackStateCompat.STATE_NONE; 268 } 269 return playbackState; 270 } 271 newStateMachine(BluetoothDevice device)272 protected AvrcpControllerStateMachine newStateMachine(BluetoothDevice device) { 273 return new AvrcpControllerStateMachine(device, this); 274 } 275 getCurrentMetadataIfNoCoverArt(BluetoothDevice device)276 protected void getCurrentMetadataIfNoCoverArt(BluetoothDevice device) { 277 if (device == null) return; 278 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 279 if (stateMachine == null) return; 280 AvrcpItem track = stateMachine.getCurrentTrack(); 281 if (track != null && track.getCoverArtLocation() == null) { 282 getCurrentMetadataNative(Utils.getByteAddress(device)); 283 } 284 } 285 286 @VisibleForTesting refreshContents(BrowseTree.BrowseNode node)287 void refreshContents(BrowseTree.BrowseNode node) { 288 BluetoothDevice device = node.getDevice(); 289 if (device == null) { 290 return; 291 } 292 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 293 if (stateMachine != null) { 294 stateMachine.requestContents(node); 295 } 296 } 297 playItem(String parentMediaId)298 void playItem(String parentMediaId) { 299 if (DBG) Log.d(TAG, "playItem(" + parentMediaId + ")"); 300 // Check if the requestedNode is a player rather than a song 301 BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId); 302 if (requestedNode == null) { 303 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 304 // Check each state machine for the song and then play it 305 requestedNode = stateMachine.findNode(parentMediaId); 306 if (requestedNode != null) { 307 if (DBG) Log.d(TAG, "Found a node"); 308 BluetoothDevice device = stateMachine.getDevice(); 309 if (device != null) { 310 setActiveDevice(device); 311 } 312 stateMachine.playItem(requestedNode); 313 break; 314 } 315 } 316 } 317 } 318 319 /*Java API*/ 320 321 /** 322 * Get a List of MediaItems that are children of the specified media Id 323 * 324 * @param parentMediaId The player or folder to get the contents of 325 * @return List of Children if available, an empty list if there are none, or null if a search 326 * must be performed. 327 */ getContents(String parentMediaId)328 public synchronized BrowseResult getContents(String parentMediaId) { 329 if (DBG) Log.d(TAG, "getContents(" + parentMediaId + ")"); 330 331 BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId); 332 if (requestedNode == null) { 333 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 334 requestedNode = stateMachine.findNode(parentMediaId); 335 if (requestedNode != null) { 336 Log.d(TAG, "Found a node"); 337 break; 338 } 339 } 340 } 341 // If we don't find a node in the tree then do not have any way to browse for the contents. 342 // Return an empty list instead. 343 if (requestedNode == null) { 344 if (DBG) Log.d(TAG, "Didn't find a node"); 345 return new BrowseResult(new ArrayList(0), BrowseResult.ERROR_MEDIA_ID_INVALID); 346 } 347 if (parentMediaId.equals(BrowseTree.ROOT) && requestedNode.getChildrenCount() == 0) { 348 return new BrowseResult(null, BrowseResult.NO_DEVICE_CONNECTED); 349 } 350 // If we found a node and it belongs to a device then go ahead and make it active 351 BluetoothDevice device = requestedNode.getDevice(); 352 if (device != null) { 353 setActiveDevice(device); 354 } 355 356 List<MediaItem> contents = requestedNode.getContents(); 357 358 if (DBG) Log.d(TAG, "Returning contents"); 359 if (!requestedNode.isCached()) { 360 if (DBG) Log.d(TAG, "node is not cached"); 361 refreshContents(requestedNode); 362 /* Ongoing downloads can have partial results and we want to make sure they get sent 363 * to the client. If a download gets kicked off as a result of this request, the 364 * contents will be null until the first results arrive. 365 */ 366 return new BrowseResult(contents, BrowseResult.DOWNLOAD_PENDING); 367 } 368 return new BrowseResult(contents, BrowseResult.SUCCESS); 369 } 370 371 372 @Override initBinder()373 protected IProfileServiceBinder initBinder() { 374 return new AvrcpControllerServiceBinder(this); 375 } 376 377 //Binder object: Must be static class or memory leak may occur 378 @VisibleForTesting 379 static class AvrcpControllerServiceBinder extends IBluetoothAvrcpController.Stub 380 implements IProfileServiceBinder { 381 private AvrcpControllerService mService; 382 383 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)384 private AvrcpControllerService getService(AttributionSource source) { 385 if (Utils.isInstrumentationTestMode()) { 386 return mService; 387 } 388 if (!Utils.checkServiceAvailable(mService, TAG) 389 || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG) 390 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 391 return null; 392 } 393 return mService; 394 } 395 AvrcpControllerServiceBinder(AvrcpControllerService service)396 AvrcpControllerServiceBinder(AvrcpControllerService service) { 397 mService = service; 398 } 399 400 @Override cleanup()401 public void cleanup() { 402 mService = null; 403 } 404 405 @Override getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)406 public void getConnectedDevices(AttributionSource source, 407 SynchronousResultReceiver receiver) { 408 try { 409 AvrcpControllerService service = getService(source); 410 List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(0); 411 if (service != null) { 412 defaultValue = service.getConnectedDevices(); 413 } 414 receiver.send(defaultValue); 415 } catch (RuntimeException e) { 416 receiver.propagateException(e); 417 } 418 } 419 420 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)421 public void getDevicesMatchingConnectionStates(int[] states, 422 AttributionSource source, SynchronousResultReceiver receiver) { 423 try { 424 AvrcpControllerService service = getService(source); 425 List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(0); 426 if (service != null) { 427 defaultValue = service.getDevicesMatchingConnectionStates(states); 428 } 429 receiver.send(defaultValue); 430 } catch (RuntimeException e) { 431 receiver.propagateException(e); 432 } 433 } 434 435 @Override getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)436 public void getConnectionState(BluetoothDevice device, AttributionSource source, 437 SynchronousResultReceiver receiver) { 438 try { 439 AvrcpControllerService service = getService(source); 440 int defaultValue = BluetoothProfile.STATE_DISCONNECTED; 441 if (service != null) { 442 defaultValue = service.getConnectionState(device); 443 } 444 receiver.send(defaultValue); 445 } catch (RuntimeException e) { 446 receiver.propagateException(e); 447 } 448 } 449 450 @Override sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState, AttributionSource source, SynchronousResultReceiver receiver)451 public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState, 452 AttributionSource source, SynchronousResultReceiver receiver) { 453 try { 454 AvrcpControllerService service = getService(source); 455 Log.w(TAG, "sendGroupNavigationCmd not implemented"); 456 receiver.send(null); 457 } catch (RuntimeException e) { 458 receiver.propagateException(e); 459 } 460 } 461 462 @Override setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings, AttributionSource source, SynchronousResultReceiver receiver)463 public void setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings, 464 AttributionSource source, SynchronousResultReceiver receiver) { 465 try { 466 AvrcpControllerService service = getService(source); 467 Log.w(TAG, "setPlayerApplicationSetting not implemented"); 468 receiver.send(null); 469 } catch (RuntimeException e) { 470 receiver.propagateException(e); 471 } 472 } 473 474 @Override getPlayerSettings(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)475 public void getPlayerSettings(BluetoothDevice device, 476 AttributionSource source, SynchronousResultReceiver receiver) { 477 try { 478 AvrcpControllerService service = getService(source); 479 Log.w(TAG, "getPlayerSettings not implemented"); 480 receiver.send(null); 481 } catch (RuntimeException e) { 482 receiver.propagateException(e); 483 } 484 } 485 } 486 487 488 /* JNI API*/ 489 // Called by JNI when a passthrough key was received. 490 @VisibleForTesting handlePassthroughRsp(int id, int keyState, byte[] address)491 void handlePassthroughRsp(int id, int keyState, byte[] address) { 492 if (DBG) { 493 Log.d(TAG, "passthrough response received as: key: " + id 494 + " state: " + keyState + "address:" + Arrays.toString(address)); 495 } 496 } 497 498 @VisibleForTesting handleGroupNavigationRsp(int id, int keyState)499 void handleGroupNavigationRsp(int id, int keyState) { 500 if (DBG) { 501 Log.d(TAG, "group navigation response received as: key: " + id + " state: " 502 + keyState); 503 } 504 } 505 506 // Called by JNI when a device has connected or disconnected. 507 @VisibleForTesting onConnectionStateChanged(boolean remoteControlConnected, boolean browsingConnected, byte[] address)508 synchronized void onConnectionStateChanged(boolean remoteControlConnected, 509 boolean browsingConnected, byte[] address) { 510 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 511 if (DBG) { 512 Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " " 513 + browsingConnected + device); 514 } 515 516 StackEvent event = 517 StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected); 518 AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device); 519 if (remoteControlConnected || browsingConnected) { 520 stateMachine.connect(event); 521 // The first device to connect gets to be the active device 522 if (getActiveDevice() == null) { 523 setActiveDevice(device); 524 } 525 } else { 526 stateMachine.disconnect(); 527 if (device.equals(getActiveDevice())) { 528 setActiveDevice(null); 529 } 530 } 531 } 532 533 // Called by JNI to notify Avrcp of features supported by the Remote device. 534 @VisibleForTesting getRcFeatures(byte[] address, int features)535 void getRcFeatures(byte[] address, int features) { 536 /* Do Nothing. */ 537 } 538 539 // Called by JNI to notify Avrcp of a remote device's Cover Art PSM 540 @VisibleForTesting getRcPsm(byte[] address, int psm)541 void getRcPsm(byte[] address, int psm) { 542 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 543 if (DBG) Log.d(TAG, "getRcPsm(device=" + device + ", psm=" + psm + ")"); 544 AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device); 545 if (stateMachine != null) { 546 stateMachine.sendMessage( 547 AvrcpControllerStateMachine.MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM, psm); 548 } 549 } 550 551 // Called by JNI 552 @VisibleForTesting setPlayerAppSettingRsp(byte[] address, byte accepted)553 void setPlayerAppSettingRsp(byte[] address, byte accepted) { 554 /* Do Nothing. */ 555 } 556 557 // Called by JNI when remote wants to receive absolute volume notifications. 558 @VisibleForTesting handleRegisterNotificationAbsVol(byte[] address, byte label)559 synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) { 560 if (DBG) { 561 Log.d(TAG, "handleRegisterNotificationAbsVol"); 562 } 563 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 564 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 565 if (stateMachine != null) { 566 stateMachine.sendMessage( 567 AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION); 568 } 569 } 570 571 // Called by JNI when remote wants to set absolute volume. 572 @VisibleForTesting handleSetAbsVolume(byte[] address, byte absVol, byte label)573 synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) { 574 if (DBG) { 575 Log.d(TAG, "handleSetAbsVolume "); 576 } 577 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 578 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 579 if (stateMachine != null) { 580 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, 581 absVol); 582 } 583 } 584 585 /** 586 * Notify AVRCP Controller of an audio focus state change so we can make requests of the active 587 * player to stop and start playing. 588 */ onAudioFocusStateChanged(int state)589 public void onAudioFocusStateChanged(int state) { 590 if (DBG) { 591 Log.d(TAG, "onAudioFocusStateChanged(state=" + state + ")"); 592 } 593 594 // Make sure the active device isn't changed while we're processing the event so play/pause 595 // commands get routed to the correct device 596 synchronized (mActiveDeviceLock) { 597 switch (state) { 598 case AudioManager.AUDIOFOCUS_GAIN: 599 BluetoothMediaBrowserService.setActive(true); 600 break; 601 case AudioManager.AUDIOFOCUS_LOSS: 602 BluetoothMediaBrowserService.setActive(false); 603 break; 604 } 605 BluetoothDevice device = getActiveDevice(); 606 if (device == null) { 607 Log.w(TAG, "No active device set, ignore focus change"); 608 return; 609 } 610 611 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 612 if (stateMachine == null) { 613 Log.w(TAG, "No state machine for active device."); 614 return; 615 } 616 stateMachine.sendMessage(AvrcpControllerStateMachine.AUDIO_FOCUS_STATE_CHANGE, state); 617 } 618 } 619 620 // Called by JNI when a track changes and local AvrcpController is registered for updates. 621 @VisibleForTesting onTrackChanged(byte[] address, byte numAttributes, int[] attributes, String[] attribVals)622 synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes, 623 String[] attribVals) { 624 if (DBG) { 625 Log.d(TAG, "onTrackChanged"); 626 } 627 628 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 629 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 630 if (stateMachine != null) { 631 AvrcpItem.Builder aib = new AvrcpItem.Builder(); 632 aib.fromAvrcpAttributeArray(attributes, attribVals); 633 aib.setDevice(device); 634 aib.setItemType(AvrcpItem.TYPE_MEDIA); 635 aib.setUuid(UUID.randomUUID().toString()); 636 AvrcpItem item = aib.build(); 637 if (mCoverArtManager != null) { 638 String handle = item.getCoverArtHandle(); 639 if (handle != null) { 640 item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle)); 641 } 642 } 643 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, 644 item); 645 } 646 } 647 648 // Called by JNI periodically based upon timer to update play position 649 @VisibleForTesting onPlayPositionChanged(byte[] address, int songLen, int currSongPosition)650 synchronized void onPlayPositionChanged(byte[] address, int songLen, 651 int currSongPosition) { 652 if (DBG) { 653 Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition); 654 } 655 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 656 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 657 if (stateMachine != null) { 658 stateMachine.sendMessage( 659 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED, 660 songLen, currSongPosition); 661 } 662 } 663 664 // Called by JNI on changes of play status 665 @VisibleForTesting onPlayStatusChanged(byte[] address, byte playStatus)666 synchronized void onPlayStatusChanged(byte[] address, byte playStatus) { 667 if (DBG) { 668 Log.d(TAG, "onPlayStatusChanged " + playStatus); 669 } 670 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 671 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 672 if (stateMachine != null) { 673 stateMachine.sendMessage( 674 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, 675 toPlaybackStateFromJni(playStatus)); 676 } 677 } 678 679 // Called by JNI to report remote Player's capabilities 680 @VisibleForTesting handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen)681 synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, 682 int rspLen) { 683 if (DBG) { 684 Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen); 685 } 686 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 687 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 688 if (stateMachine != null) { 689 PlayerApplicationSettings supportedSettings = 690 PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp); 691 stateMachine.sendMessage( 692 AvrcpControllerStateMachine.MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS, 693 supportedSettings); 694 } 695 } 696 697 @VisibleForTesting onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen)698 synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, 699 int rspLen) { 700 if (DBG) { 701 Log.d(TAG, "onPlayerAppSettingChanged "); 702 } 703 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 704 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 705 if (stateMachine != null) { 706 707 PlayerApplicationSettings currentSettings = 708 PlayerApplicationSettings.makeSettings(playerAttribRsp); 709 stateMachine.sendMessage( 710 AvrcpControllerStateMachine.MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS, 711 currentSettings); 712 } 713 } 714 715 @VisibleForTesting onAvailablePlayerChanged(byte[] address)716 void onAvailablePlayerChanged(byte[] address) { 717 if (DBG) { 718 Log.d(TAG," onAvailablePlayerChanged"); 719 } 720 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 721 722 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 723 if (stateMachine != null) { 724 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED); 725 } 726 } 727 728 // Browsing related JNI callbacks. handleGetFolderItemsRsp(byte[] address, int status, AvrcpItem[] items)729 void handleGetFolderItemsRsp(byte[] address, int status, AvrcpItem[] items) { 730 if (DBG) { 731 Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items " 732 + items.length + " items."); 733 } 734 735 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 736 List<AvrcpItem> itemsList = new ArrayList<>(); 737 for (AvrcpItem item : items) { 738 if (VDBG) Log.d(TAG, item.toString()); 739 if (mCoverArtManager != null) { 740 String handle = item.getCoverArtHandle(); 741 if (handle != null) { 742 item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle)); 743 } 744 } 745 itemsList.add(item); 746 } 747 748 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 749 if (stateMachine != null) { 750 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, 751 itemsList); 752 } 753 } 754 handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items)755 void handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items) { 756 if (DBG) { 757 Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items."); 758 } 759 760 List<AvrcpPlayer> itemsList = new ArrayList<>(); 761 for (AvrcpPlayer item : items) { 762 if (VDBG) Log.d(TAG, "bt player item: " + item); 763 itemsList.add(item); 764 } 765 766 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 767 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 768 if (stateMachine != null) { 769 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS, 770 itemsList); 771 } 772 } 773 774 // JNI Helper functions to convert native objects to java. createFromNativeMediaItem(byte[] address, long uid, int type, String name, int[] attrIds, String[] attrVals)775 AvrcpItem createFromNativeMediaItem(byte[] address, long uid, int type, String name, 776 int[] attrIds, String[] attrVals) { 777 if (VDBG) { 778 Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type: " + type + " name: " + name 779 + " attrids: " + Arrays.toString(attrIds) 780 + " attrVals: " + Arrays.toString(attrVals)); 781 } 782 783 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 784 AvrcpItem.Builder aib = new AvrcpItem.Builder().fromAvrcpAttributeArray(attrIds, attrVals); 785 aib.setDevice(device); 786 aib.setItemType(AvrcpItem.TYPE_MEDIA); 787 aib.setType(type); 788 aib.setUid(uid); 789 aib.setUuid(UUID.randomUUID().toString()); 790 aib.setPlayable(true); 791 AvrcpItem item = aib.build(); 792 return item; 793 } 794 createFromNativeFolderItem(byte[] address, long uid, int type, String name, int playable)795 AvrcpItem createFromNativeFolderItem(byte[] address, long uid, int type, String name, 796 int playable) { 797 if (VDBG) { 798 Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + " name " 799 + name + " playable " + playable); 800 } 801 802 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 803 AvrcpItem.Builder aib = new AvrcpItem.Builder(); 804 aib.setDevice(device); 805 aib.setItemType(AvrcpItem.TYPE_FOLDER); 806 aib.setType(type); 807 aib.setUid(uid); 808 aib.setUuid(UUID.randomUUID().toString()); 809 aib.setDisplayableName(name); 810 aib.setPlayable(playable == 0x01); 811 aib.setBrowsable(true); 812 return aib.build(); 813 } 814 createFromNativePlayerItem(byte[] address, int id, String name, byte[] transportFlags, int playStatus, int playerType)815 AvrcpPlayer createFromNativePlayerItem(byte[] address, int id, String name, 816 byte[] transportFlags, int playStatus, int playerType) { 817 if (VDBG) { 818 Log.d(TAG, "createFromNativePlayerItem name: " + name 819 + " transportFlags " + Arrays.toString(transportFlags) 820 + " play status " + playStatus 821 + " player type " + playerType); 822 } 823 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 824 AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder(); 825 apb.setDevice(device); 826 apb.setPlayerId(id); 827 apb.setPlayerType(playerType); 828 apb.setSupportedFeatures(transportFlags); 829 apb.setName(name); 830 apb.setPlayStatus(toPlaybackStateFromJni(playStatus)); 831 return apb.build(); 832 } 833 834 @VisibleForTesting handleChangeFolderRsp(byte[] address, int count)835 void handleChangeFolderRsp(byte[] address, int count) { 836 if (DBG) { 837 Log.d(TAG, "handleChangeFolderRsp count: " + count); 838 } 839 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 840 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 841 if (stateMachine != null) { 842 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, 843 count); 844 } 845 } 846 847 @VisibleForTesting handleSetBrowsedPlayerRsp(byte[] address, int items, int depth)848 void handleSetBrowsedPlayerRsp(byte[] address, int items, int depth) { 849 if (DBG) { 850 Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth); 851 } 852 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 853 854 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 855 if (stateMachine != null) { 856 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, 857 items, depth); 858 } 859 } 860 861 @VisibleForTesting handleSetAddressedPlayerRsp(byte[] address, int status)862 void handleSetAddressedPlayerRsp(byte[] address, int status) { 863 if (DBG) { 864 Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status); 865 } 866 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 867 868 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 869 if (stateMachine != null) { 870 stateMachine.sendMessage( 871 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER); 872 } 873 } 874 875 @VisibleForTesting handleAddressedPlayerChanged(byte[] address, int id)876 void handleAddressedPlayerChanged(byte[] address, int id) { 877 if (DBG) { 878 Log.d(TAG, "handleAddressedPlayerChanged id: " + id); 879 } 880 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 881 882 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 883 if (stateMachine != null) { 884 stateMachine.sendMessage( 885 AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id); 886 } 887 } 888 889 @VisibleForTesting handleNowPlayingContentChanged(byte[] address)890 void handleNowPlayingContentChanged(byte[] address) { 891 if (DBG) { 892 Log.d(TAG, "handleNowPlayingContentChanged"); 893 } 894 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 895 896 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 897 if (stateMachine != null) { 898 stateMachine.nowPlayingContentChanged(); 899 } 900 } 901 902 /* Generic Profile Code */ 903 904 /** 905 * Disconnect the given Bluetooth device. 906 * 907 * @return true if disconnect is successful, false otherwise. 908 */ disconnect(BluetoothDevice device)909 public synchronized boolean disconnect(BluetoothDevice device) { 910 if (DBG) { 911 StringBuilder sb = new StringBuilder(); 912 dump(sb); 913 Log.d(TAG, "MAP disconnect device: " + device 914 + ", InstanceMap start state: " + sb.toString()); 915 } 916 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 917 // a map state machine instance doesn't exist. maybe it is already gone? 918 if (stateMachine == null) { 919 return false; 920 } 921 int connectionState = stateMachine.getState(); 922 if (connectionState != BluetoothProfile.STATE_CONNECTED 923 && connectionState != BluetoothProfile.STATE_CONNECTING) { 924 return false; 925 } 926 stateMachine.disconnect(); 927 if (DBG) { 928 StringBuilder sb = new StringBuilder(); 929 dump(sb); 930 Log.d(TAG, "MAP disconnect device: " + device 931 + ", InstanceMap start state: " + sb.toString()); 932 } 933 return true; 934 } 935 936 /** 937 * Remove state machine from device map once it is no longer needed. 938 */ removeStateMachine(AvrcpControllerStateMachine stateMachine)939 public void removeStateMachine(AvrcpControllerStateMachine stateMachine) { 940 BluetoothDevice device = stateMachine.getDevice(); 941 if (device.equals(getActiveDevice())) { 942 setActiveDevice(null); 943 } 944 mDeviceStateMap.remove(stateMachine.getDevice()); 945 } 946 getConnectedDevices()947 public List<BluetoothDevice> getConnectedDevices() { 948 return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED}); 949 } 950 getStateMachine(BluetoothDevice device)951 protected AvrcpControllerStateMachine getStateMachine(BluetoothDevice device) { 952 if (device == null) { 953 return null; 954 } 955 return mDeviceStateMap.get(device); 956 } 957 getOrCreateStateMachine(BluetoothDevice device)958 protected AvrcpControllerStateMachine getOrCreateStateMachine(BluetoothDevice device) { 959 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 960 if (stateMachine == null) { 961 stateMachine = newStateMachine(device); 962 mDeviceStateMap.put(device, stateMachine); 963 stateMachine.start(); 964 } 965 return stateMachine; 966 } 967 getCoverArtManager()968 protected AvrcpCoverArtManager getCoverArtManager() { 969 return mCoverArtManager; 970 } 971 getDevicesMatchingConnectionStates(int[] states)972 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 973 if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states)); 974 List<BluetoothDevice> deviceList = new ArrayList<>(); 975 BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 976 int connectionState; 977 for (BluetoothDevice device : bondedDevices) { 978 connectionState = getConnectionState(device); 979 if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState); 980 for (int i = 0; i < states.length; i++) { 981 if (connectionState == states[i]) { 982 deviceList.add(device); 983 } 984 } 985 } 986 if (DBG) Log.d(TAG, deviceList.toString()); 987 Log.d(TAG, "GetDevicesDone"); 988 return deviceList; 989 } 990 getConnectionState(BluetoothDevice device)991 synchronized int getConnectionState(BluetoothDevice device) { 992 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 993 return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED 994 : stateMachine.getState(); 995 } 996 997 @Override dump(StringBuilder sb)998 public void dump(StringBuilder sb) { 999 super.dump(sb); 1000 ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size()); 1001 ProfileService.println(sb, "Active Device = " + mActiveDevice); 1002 1003 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 1004 ProfileService.println(sb, 1005 "==== StateMachine for " + stateMachine.getDevice() + " ===="); 1006 stateMachine.dump(sb); 1007 } 1008 sb.append("\n sBrowseTree: " + sBrowseTree.toString()); 1009 1010 sb.append("\n Cover Artwork Enabled: " + (mCoverArtEnabled ? "True" : "False")); 1011 if (mCoverArtManager != null) { 1012 sb.append("\n " + mCoverArtManager.toString()); 1013 } 1014 1015 sb.append("\n " + BluetoothMediaBrowserService.dump() + "\n"); 1016 } 1017 1018 /*JNI*/ classInitNative()1019 private static native void classInitNative(); 1020 initNative()1021 private native void initNative(); 1022 cleanupNative()1023 private native void cleanupNative(); 1024 1025 /** 1026 * Send button press commands to addressed device 1027 * 1028 * @param keyCode key code as defined in AVRCP specification 1029 * @param keyState 0 = key pressed, 1 = key released 1030 * @return command was sent 1031 */ sendPassThroughCommandNative(byte[] address, int keyCode, int keyState)1032 public native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState); 1033 1034 /** 1035 * Send group navigation commands 1036 * 1037 * @param keyCode next/previous 1038 * @param keyState state 1039 * @return command was sent 1040 */ sendGroupNavigationCommandNative(byte[] address, int keyCode, int keyState)1041 public native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode, 1042 int keyState); 1043 1044 /** 1045 * Change player specific settings such as shuffle 1046 * 1047 * @param numAttrib number of settings being sent 1048 * @param attribIds list of settings to be changed 1049 * @param attribVal list of settings values 1050 */ setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib, byte[] attribIds, byte[] attribVal)1051 public native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib, 1052 byte[] attribIds, byte[] attribVal); 1053 1054 /** 1055 * Send response to set absolute volume 1056 * 1057 * @param absVol new volume 1058 * @param label label 1059 */ sendAbsVolRspNative(byte[] address, int absVol, int label)1060 public native void sendAbsVolRspNative(byte[] address, int absVol, int label); 1061 1062 /** 1063 * Register for any volume level changes 1064 * 1065 * @param rspType type of response 1066 * @param absVol current volume 1067 * @param label label 1068 */ sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol, int label)1069 public native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol, 1070 int label); 1071 1072 /** 1073 * Fetch the current track's metadata 1074 * 1075 * This method is specifically meant to allow us to fetch image handles that may not have been 1076 * sent to us yet, prior to having a BIP client connection. See the AVRCP 1.6+ specification, 1077 * section 4.1.7, for more details. 1078 */ getCurrentMetadataNative(byte[] address)1079 public native void getCurrentMetadataNative(byte[] address); 1080 1081 /** 1082 * Fetch the playback state 1083 */ getPlaybackStateNative(byte[] address)1084 public native void getPlaybackStateNative(byte[] address); 1085 1086 /** 1087 * Fetch the current now playing list 1088 * 1089 * @param start first index to retrieve 1090 * @param end last index to retrieve 1091 */ getNowPlayingListNative(byte[] address, int start, int end)1092 public native void getNowPlayingListNative(byte[] address, int start, int end); 1093 1094 /** 1095 * Fetch the current folder's listing 1096 * 1097 * @param start first index to retrieve 1098 * @param end last index to retrieve 1099 */ getFolderListNative(byte[] address, int start, int end)1100 public native void getFolderListNative(byte[] address, int start, int end); 1101 1102 /** 1103 * Fetch the listing of players 1104 * 1105 * @param start first index to retrieve 1106 * @param end last index to retrieve 1107 */ getPlayerListNative(byte[] address, int start, int end)1108 public native void getPlayerListNative(byte[] address, int start, int end); 1109 1110 /** 1111 * Change the current browsed folder 1112 * 1113 * @param direction up/down 1114 * @param uid folder unique id 1115 */ changeFolderPathNative(byte[] address, byte direction, long uid)1116 public native void changeFolderPathNative(byte[] address, byte direction, long uid); 1117 1118 /** 1119 * Play item with provided uid 1120 * 1121 * @param scope scope of item to played 1122 * @param uid song unique id 1123 * @param uidCounter counter 1124 */ playItemNative(byte[] address, byte scope, long uid, int uidCounter)1125 public native void playItemNative(byte[] address, byte scope, long uid, int uidCounter); 1126 1127 /** 1128 * Set a specific player for browsing 1129 * 1130 * @param playerId player number 1131 */ setBrowsedPlayerNative(byte[] address, int playerId)1132 public native void setBrowsedPlayerNative(byte[] address, int playerId); 1133 1134 /** 1135 * Set a specific player for handling playback commands 1136 * 1137 * @param playerId player number 1138 */ setAddressedPlayerNative(byte[] address, int playerId)1139 public native void setAddressedPlayerNative(byte[] address, int playerId); 1140 } 1141