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.bluetooth.BluetoothAvrcpController; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothProfile; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.media.AudioManager; 25 import android.net.Uri; 26 import android.os.Bundle; 27 import android.os.Message; 28 import android.support.v4.media.session.MediaSessionCompat; 29 import android.support.v4.media.session.PlaybackStateCompat; 30 import android.util.Log; 31 import android.util.SparseArray; 32 33 import com.android.bluetooth.BluetoothMetricsProto; 34 import com.android.bluetooth.R; 35 import com.android.bluetooth.Utils; 36 import com.android.bluetooth.a2dpsink.A2dpSinkService; 37 import com.android.bluetooth.btservice.MetricsLogger; 38 import com.android.bluetooth.btservice.ProfileService; 39 import com.android.bluetooth.statemachine.State; 40 import com.android.bluetooth.statemachine.StateMachine; 41 import com.android.internal.annotations.VisibleForTesting; 42 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.Set; 46 47 /** 48 * Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections 49 * and interactions with a remote controlable device. 50 */ 51 class AvrcpControllerStateMachine extends StateMachine { 52 static final String TAG = "AvrcpControllerStateMachine"; 53 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 54 55 //0->99 Events from Outside 56 public static final int CONNECT = 1; 57 public static final int DISCONNECT = 2; 58 59 //100->199 Internal Events 60 protected static final int CLEANUP = 100; 61 private static final int CONNECT_TIMEOUT = 101; 62 static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 102; 63 64 //200->299 Events from Native 65 static final int STACK_EVENT = 200; 66 static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 201; 67 68 static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 203; 69 static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 204; 70 static final int MESSAGE_PROCESS_TRACK_CHANGED = 205; 71 static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 206; 72 static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 207; 73 static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 208; 74 static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 209; 75 static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 210; 76 static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 211; 77 static final int MESSAGE_PROCESS_FOLDER_PATH = 212; 78 static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 213; 79 static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214; 80 static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215; 81 static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216; 82 static final int MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS = 217; 83 static final int MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS = 218; 84 static final int MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED = 219; 85 static final int MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM = 220; 86 87 //300->399 Events for Browsing 88 static final int MESSAGE_GET_FOLDER_ITEMS = 300; 89 static final int MESSAGE_PLAY_ITEM = 301; 90 static final int MSG_AVRCP_PASSTHRU = 302; 91 static final int MSG_AVRCP_SET_SHUFFLE = 303; 92 static final int MSG_AVRCP_SET_REPEAT = 304; 93 94 //400->499 Events for Cover Artwork 95 static final int MESSAGE_PROCESS_IMAGE_DOWNLOADED = 400; 96 97 /* 98 * Base value for absolute volume from JNI 99 */ 100 private static final int ABS_VOL_BASE = 127; 101 102 /* 103 * Notification types for Avrcp protocol JNI. 104 */ 105 private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00; 106 private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01; 107 108 private static BluetoothDevice sActiveDevice; 109 private final AudioManager mAudioManager; 110 private final boolean mIsVolumeFixed; 111 112 protected final BluetoothDevice mDevice; 113 protected final byte[] mDeviceAddress; 114 protected final AvrcpControllerService mService; 115 protected int mCoverArtPsm; 116 protected final AvrcpCoverArtManager mCoverArtManager; 117 protected final Disconnected mDisconnected; 118 protected final Connecting mConnecting; 119 protected final Connected mConnected; 120 protected final Disconnecting mDisconnecting; 121 122 protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED; 123 124 boolean mRemoteControlConnected = false; 125 boolean mBrowsingConnected = false; 126 final BrowseTree mBrowseTree; 127 private AvrcpPlayer mAddressedPlayer = new AvrcpPlayer(); 128 private int mAddressedPlayerId = -1; 129 private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>(); 130 private int mVolumeChangedNotificationsToIgnore = 0; 131 private int mVolumeNotificationLabel = -1; 132 133 GetFolderList mGetFolderList = null; 134 135 //Number of items to get in a single fetch 136 static final int ITEM_PAGE_SIZE = 20; 137 static final int CMD_TIMEOUT_MILLIS = 10000; 138 static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s 139 AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service)140 AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service) { 141 super(TAG); 142 mDevice = device; 143 mDeviceAddress = Utils.getByteAddress(mDevice); 144 mService = service; 145 mCoverArtPsm = 0; 146 mCoverArtManager = service.getCoverArtManager(); 147 logD(device.toString()); 148 149 mBrowseTree = new BrowseTree(mDevice); 150 mDisconnected = new Disconnected(); 151 mConnecting = new Connecting(); 152 mConnected = new Connected(); 153 mDisconnecting = new Disconnecting(); 154 155 addState(mDisconnected); 156 addState(mConnecting); 157 addState(mConnected); 158 addState(mDisconnecting); 159 160 mGetFolderList = new GetFolderList(); 161 addState(mGetFolderList, mConnected); 162 mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE); 163 mIsVolumeFixed = mAudioManager.isVolumeFixed(); 164 165 setInitialState(mDisconnected); 166 } 167 findNode(String parentMediaId)168 BrowseTree.BrowseNode findNode(String parentMediaId) { 169 logD("FindNode"); 170 return mBrowseTree.findBrowseNodeByID(parentMediaId); 171 } 172 173 /** 174 * Get the current connection state 175 * 176 * @return current State 177 */ getState()178 public int getState() { 179 return mMostRecentState; 180 } 181 182 /** 183 * Get the underlying device tracked by this state machine 184 * 185 * @return device in focus 186 */ getDevice()187 public synchronized BluetoothDevice getDevice() { 188 return mDevice; 189 } 190 191 /** 192 * send the connection event asynchronously 193 */ connect(StackEvent event)194 public boolean connect(StackEvent event) { 195 if (event.mBrowsingConnected) { 196 onBrowsingConnected(); 197 } 198 mRemoteControlConnected = event.mRemoteControlConnected; 199 sendMessage(CONNECT); 200 return true; 201 } 202 203 /** 204 * send the Disconnect command asynchronously 205 */ disconnect()206 public void disconnect() { 207 sendMessage(DISCONNECT); 208 } 209 210 /** 211 * Get the current playing track 212 */ getCurrentTrack()213 public AvrcpItem getCurrentTrack() { 214 return mAddressedPlayer.getCurrentTrack(); 215 } 216 217 /** 218 * Dump the current State Machine to the string builder. 219 * 220 * @param sb output string 221 */ dump(StringBuilder sb)222 public void dump(StringBuilder sb) { 223 ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "(" 224 + mDevice.getName() + ") " + this.toString()); 225 ProfileService.println(sb, "isActive: " + isActive()); 226 } 227 228 @VisibleForTesting isActive()229 boolean isActive() { 230 return mDevice == sActiveDevice; 231 } 232 233 /* 234 * requestActive 235 * 236 * Set the current device active if nothing an already connected device isn't playing 237 */ requestActive()238 private boolean requestActive() { 239 if (sActiveDevice == null 240 || BluetoothMediaBrowserService.getPlaybackState() 241 != PlaybackStateCompat.STATE_PLAYING) { 242 return setActive(true); 243 } 244 return false; 245 } 246 247 /** 248 * Attempt to set the active status for this device 249 */ setActive(boolean becomeActive)250 boolean setActive(boolean becomeActive) { 251 logD("setActive(" + becomeActive + ")"); 252 if (becomeActive) { 253 if (isActive()) { 254 return true; 255 } 256 257 A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); 258 if (a2dpSinkService == null) { 259 return false; 260 } 261 262 if (a2dpSinkService.setActiveDeviceNative(mDeviceAddress)) { 263 sActiveDevice = mDevice; 264 BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks); 265 BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); 266 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); 267 } 268 return mDevice == sActiveDevice; 269 } else if (isActive()) { 270 sActiveDevice = null; 271 BluetoothMediaBrowserService.trackChanged(null); 272 BluetoothMediaBrowserService.addressedPlayerChanged(null); 273 } 274 return true; 275 } 276 277 @Override unhandledMessage(Message msg)278 protected void unhandledMessage(Message msg) { 279 Log.w(TAG, "Unhandled message in state " + getCurrentState() + "msg.what=" + msg.what); 280 } 281 logD(String message)282 private static void logD(String message) { 283 if (DBG) { 284 Log.d(TAG, message); 285 } 286 } 287 onBrowsingConnected()288 synchronized void onBrowsingConnected() { 289 if (mBrowsingConnected) return; 290 mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode); 291 BluetoothMediaBrowserService.notifyChanged(mService 292 .sBrowseTree.mRootNode); 293 mBrowsingConnected = true; 294 } 295 onBrowsingDisconnected()296 synchronized void onBrowsingDisconnected() { 297 if (!mBrowsingConnected) return; 298 mAddressedPlayer.setPlayStatus(PlaybackStateCompat.STATE_ERROR); 299 AvrcpItem previousTrack = mAddressedPlayer.getCurrentTrack(); 300 String previousTrackUuid = previousTrack != null ? previousTrack.getCoverArtUuid() : null; 301 mAddressedPlayer.updateCurrentTrack(null); 302 mBrowseTree.mNowPlayingNode.setCached(false); 303 if (isActive()) { 304 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); 305 } 306 mService.sBrowseTree.mRootNode.removeChild( 307 mBrowseTree.mRootNode); 308 BluetoothMediaBrowserService.notifyChanged(mService 309 .sBrowseTree.mRootNode); 310 removeUnusedArtwork(previousTrackUuid); 311 removeUnusedArtworkFromBrowseTree(); 312 mBrowsingConnected = false; 313 } 314 connectCoverArt()315 synchronized void connectCoverArt() { 316 // Called from "connected" state, which assumes either control or browse is connected 317 if (mCoverArtManager != null && mCoverArtPsm != 0 318 && mCoverArtManager.getState(mDevice) != BluetoothProfile.STATE_CONNECTED) { 319 logD("Attempting to connect to AVRCP BIP, psm: " + mCoverArtPsm); 320 mCoverArtManager.connect(mDevice, /* psm */ mCoverArtPsm); 321 } 322 } 323 refreshCoverArt()324 synchronized void refreshCoverArt() { 325 if (mCoverArtManager != null && mCoverArtPsm != 0 326 && mCoverArtManager.getState(mDevice) == BluetoothProfile.STATE_CONNECTED) { 327 logD("Attempting to refresh AVRCP BIP OBEX session, psm: " + mCoverArtPsm); 328 mCoverArtManager.refreshSession(mDevice); 329 } 330 } 331 disconnectCoverArt()332 synchronized void disconnectCoverArt() { 333 // Safe to call even if we're not connected 334 if (mCoverArtManager != null) { 335 logD("Disconnect BIP cover artwork"); 336 mCoverArtManager.disconnect(mDevice); 337 } 338 } 339 340 /** 341 * Remove an unused cover art image from storage if it's unused by the browse tree and the 342 * current track. 343 */ removeUnusedArtwork(String previousTrackUuid)344 synchronized void removeUnusedArtwork(String previousTrackUuid) { 345 logD("removeUnusedArtwork(" + previousTrackUuid + ")"); 346 if (mCoverArtManager == null) return; 347 AvrcpItem currentTrack = getCurrentTrack(); 348 String currentTrackUuid = currentTrack != null ? currentTrack.getCoverArtUuid() : null; 349 if (previousTrackUuid != null) { 350 if (!previousTrackUuid.equals(currentTrackUuid) 351 && mBrowseTree.getNodesUsingCoverArt(previousTrackUuid).isEmpty()) { 352 mCoverArtManager.removeImage(mDevice, previousTrackUuid); 353 } 354 } 355 } 356 357 /** 358 * Queries the browse tree for unused uuids and removes the associated images from storage 359 * if the uuid is not used by the current track. 360 */ removeUnusedArtworkFromBrowseTree()361 synchronized void removeUnusedArtworkFromBrowseTree() { 362 logD("removeUnusedArtworkFromBrowseTree()"); 363 if (mCoverArtManager == null) return; 364 AvrcpItem currentTrack = getCurrentTrack(); 365 String currentTrackUuid = currentTrack != null ? currentTrack.getCoverArtUuid() : null; 366 ArrayList<String> unusedArtwork = mBrowseTree.getAndClearUnusedCoverArt(); 367 for (String uuid : unusedArtwork) { 368 if (!uuid.equals(currentTrackUuid)) { 369 mCoverArtManager.removeImage(mDevice, uuid); 370 } 371 } 372 } 373 notifyChanged(BrowseTree.BrowseNode node)374 private void notifyChanged(BrowseTree.BrowseNode node) { 375 // We should only notify now playing content updates if we're the active device. VFS 376 // updates are fine at any time 377 int scope = node.getScope(); 378 if (scope != AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING 379 || (scope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING 380 && isActive())) { 381 BluetoothMediaBrowserService.notifyChanged(node); 382 } 383 } 384 notifyChanged(PlaybackStateCompat state)385 private void notifyChanged(PlaybackStateCompat state) { 386 if (isActive()) { 387 BluetoothMediaBrowserService.notifyChanged(state); 388 } 389 } 390 requestContents(BrowseTree.BrowseNode node)391 void requestContents(BrowseTree.BrowseNode node) { 392 sendMessage(MESSAGE_GET_FOLDER_ITEMS, node); 393 logD("Fetching " + node); 394 } 395 playItem(BrowseTree.BrowseNode node)396 public void playItem(BrowseTree.BrowseNode node) { 397 sendMessage(MESSAGE_PLAY_ITEM, node); 398 } 399 nowPlayingContentChanged()400 void nowPlayingContentChanged() { 401 mBrowseTree.mNowPlayingNode.setCached(false); 402 removeUnusedArtworkFromBrowseTree(); 403 sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode); 404 } 405 406 protected class Disconnected extends State { 407 @Override enter()408 public void enter() { 409 logD("Enter Disconnected"); 410 if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) { 411 sendMessage(CLEANUP); 412 } 413 broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED); 414 } 415 416 @Override processMessage(Message message)417 public boolean processMessage(Message message) { 418 switch (message.what) { 419 case MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM: 420 mCoverArtPsm = message.arg1; 421 break; 422 case CONNECT: 423 logD("Connect"); 424 transitionTo(mConnecting); 425 break; 426 case CLEANUP: 427 mService.removeStateMachine(AvrcpControllerStateMachine.this); 428 break; 429 } 430 return true; 431 } 432 } 433 434 protected class Connecting extends State { 435 @Override enter()436 public void enter() { 437 logD("Enter Connecting"); 438 broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTING); 439 transitionTo(mConnected); 440 } 441 } 442 443 444 class Connected extends State { 445 private static final String STATE_TAG = "Avrcp.ConnectedAvrcpController"; 446 private int mCurrentlyHeldKey = 0; 447 448 @Override enter()449 public void enter() { 450 if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) { 451 requestActive(); 452 broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED); 453 connectCoverArt(); // only works if we have a valid PSM 454 } else { 455 logD("ReEnteringConnected"); 456 } 457 super.enter(); 458 } 459 460 @Override processMessage(Message msg)461 public boolean processMessage(Message msg) { 462 logD(STATE_TAG + " processMessage " + msg.what); 463 switch (msg.what) { 464 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 465 mVolumeChangedNotificationsToIgnore++; 466 removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT); 467 sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT, 468 ABS_VOL_TIMEOUT_MILLIS); 469 handleAbsVolumeRequest(msg.arg1, msg.arg2); 470 return true; 471 472 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: 473 mVolumeNotificationLabel = msg.arg1; 474 mService.sendRegisterAbsVolRspNative(mDeviceAddress, 475 NOTIFICATION_RSP_TYPE_INTERIM, 476 getAbsVolume(), mVolumeNotificationLabel); 477 return true; 478 479 case MESSAGE_GET_FOLDER_ITEMS: 480 transitionTo(mGetFolderList); 481 return true; 482 483 case MESSAGE_PLAY_ITEM: 484 //Set Addressed Player 485 processPlayItem((BrowseTree.BrowseNode) msg.obj); 486 return true; 487 488 case MSG_AVRCP_PASSTHRU: 489 passThru(msg.arg1); 490 return true; 491 492 case MSG_AVRCP_SET_REPEAT: 493 setRepeat(msg.arg1); 494 return true; 495 496 case MSG_AVRCP_SET_SHUFFLE: 497 setShuffle(msg.arg1); 498 return true; 499 500 case MESSAGE_PROCESS_TRACK_CHANGED: 501 AvrcpItem track = (AvrcpItem) msg.obj; 502 AvrcpItem previousTrack = mAddressedPlayer.getCurrentTrack(); 503 downloadImageIfNeeded(track); 504 mAddressedPlayer.updateCurrentTrack(track); 505 if (isActive()) { 506 BluetoothMediaBrowserService.trackChanged(track); 507 } 508 if (previousTrack != null) { 509 removeUnusedArtwork(previousTrack.getCoverArtUuid()); 510 removeUnusedArtworkFromBrowseTree(); 511 } 512 return true; 513 514 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 515 mAddressedPlayer.setPlayStatus(msg.arg1); 516 if (!isActive()) { 517 sendMessage(MSG_AVRCP_PASSTHRU, 518 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 519 return true; 520 } 521 522 PlaybackStateCompat playbackState = mAddressedPlayer.getPlaybackState(); 523 BluetoothMediaBrowserService.notifyChanged(playbackState); 524 525 int focusState = AudioManager.ERROR; 526 A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); 527 if (a2dpSinkService != null) { 528 focusState = a2dpSinkService.getFocusState(); 529 } 530 531 if (focusState == AudioManager.ERROR) { 532 sendMessage(MSG_AVRCP_PASSTHRU, 533 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 534 return true; 535 } 536 537 if (playbackState.getState() == PlaybackStateCompat.STATE_PLAYING 538 && focusState == AudioManager.AUDIOFOCUS_NONE) { 539 if (shouldRequestFocus()) { 540 mSessionCallbacks.onPrepare(); 541 } else { 542 sendMessage(MSG_AVRCP_PASSTHRU, 543 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 544 } 545 } 546 return true; 547 548 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 549 if (msg.arg2 != -1) { 550 mAddressedPlayer.setPlayTime(msg.arg2); 551 notifyChanged(mAddressedPlayer.getPlaybackState()); 552 } 553 return true; 554 555 case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED: 556 mAddressedPlayerId = msg.arg1; 557 logD("AddressedPlayer = " + mAddressedPlayerId); 558 559 // The now playing list is tied to the addressed player by specification in 560 // AVRCP 5.9.1. A new addressed player means our now playing content is now 561 // invalid 562 mBrowseTree.mNowPlayingNode.setCached(false); 563 if (isActive()) { 564 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); 565 } 566 567 AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerId); 568 if (updatedPlayer != null) { 569 mAddressedPlayer = updatedPlayer; 570 // If the new player supports the now playing feature then fetch it 571 if (mAddressedPlayer.supportsFeature(AvrcpPlayer.FEATURE_NOW_PLAYING)) { 572 sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode); 573 } 574 logD("AddressedPlayer = " + mAddressedPlayer.getName()); 575 } else { 576 logD("Addressed player changed to unknown ID=" + mAddressedPlayerId); 577 mBrowseTree.mRootNode.setCached(false); 578 mBrowseTree.mRootNode.setExpectedChildren(255); 579 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode); 580 } 581 removeUnusedArtworkFromBrowseTree(); 582 return true; 583 584 case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS: 585 mAddressedPlayer.setSupportedPlayerApplicationSettings( 586 (PlayerApplicationSettings) msg.obj); 587 notifyChanged(mAddressedPlayer.getPlaybackState()); 588 return true; 589 590 case MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS: 591 mAddressedPlayer.setCurrentPlayerApplicationSettings( 592 (PlayerApplicationSettings) msg.obj); 593 notifyChanged(mAddressedPlayer.getPlaybackState()); 594 return true; 595 596 case MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED: 597 processAvailablePlayerChanged(); 598 return true; 599 600 case MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM: 601 mCoverArtPsm = msg.arg1; 602 connectCoverArt(); 603 return true; 604 605 case MESSAGE_PROCESS_IMAGE_DOWNLOADED: 606 AvrcpCoverArtManager.DownloadEvent event = 607 (AvrcpCoverArtManager.DownloadEvent) msg.obj; 608 String uuid = event.getUuid(); 609 Uri uri = event.getUri(); 610 logD("Received image for " + uuid + " at " + uri.toString()); 611 612 // Let the addressed player know we got an image so it can see if the current 613 // track now has cover artwork 614 boolean addedArtwork = mAddressedPlayer.notifyImageDownload(uuid, uri); 615 if (addedArtwork && isActive()) { 616 BluetoothMediaBrowserService.trackChanged( 617 mAddressedPlayer.getCurrentTrack()); 618 } 619 620 // Let the browse tree know of the newly downloaded image so it can attach it to 621 // all the items that need it. Notify of changed nodes accordingly 622 Set<BrowseTree.BrowseNode> nodes = mBrowseTree.notifyImageDownload(uuid, uri); 623 for (BrowseTree.BrowseNode node : nodes) { 624 notifyChanged(node); 625 } 626 627 // Delete images that were downloaded and entirely unused 628 if (!addedArtwork && nodes.isEmpty()) { 629 removeUnusedArtwork(uuid); 630 removeUnusedArtworkFromBrowseTree(); 631 } 632 633 return true; 634 635 case DISCONNECT: 636 transitionTo(mDisconnecting); 637 return true; 638 639 default: 640 return super.processMessage(msg); 641 } 642 643 } 644 processPlayItem(BrowseTree.BrowseNode node)645 private void processPlayItem(BrowseTree.BrowseNode node) { 646 setActive(true); 647 if (node == null) { 648 Log.w(TAG, "Invalid item to play"); 649 } else { 650 mService.playItemNative( 651 mDeviceAddress, node.getScope(), 652 node.getBluetoothID(), 0); 653 } 654 } 655 passThru(int cmd)656 private synchronized void passThru(int cmd) { 657 logD("msgPassThru " + cmd); 658 // Some keys should be held until the next event. 659 if (mCurrentlyHeldKey != 0) { 660 mService.sendPassThroughCommandNative( 661 mDeviceAddress, mCurrentlyHeldKey, 662 AvrcpControllerService.KEY_STATE_RELEASED); 663 664 if (mCurrentlyHeldKey == cmd) { 665 // Return to prevent starting FF/FR operation again 666 mCurrentlyHeldKey = 0; 667 return; 668 } else { 669 // FF/FR is in progress and other operation is desired 670 // so after stopping FF/FR, not returning so that command 671 // can be sent for the desired operation. 672 mCurrentlyHeldKey = 0; 673 } 674 } 675 676 // Send the pass through. 677 mService.sendPassThroughCommandNative(mDeviceAddress, cmd, 678 AvrcpControllerService.KEY_STATE_PRESSED); 679 680 if (isHoldableKey(cmd)) { 681 // Release cmd next time a command is sent. 682 mCurrentlyHeldKey = cmd; 683 } else { 684 mService.sendPassThroughCommandNative(mDeviceAddress, 685 cmd, AvrcpControllerService.KEY_STATE_RELEASED); 686 } 687 } 688 isHoldableKey(int cmd)689 private boolean isHoldableKey(int cmd) { 690 return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND) 691 || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF); 692 } 693 setRepeat(int repeatMode)694 private void setRepeat(int repeatMode) { 695 mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1, 696 new byte[]{PlayerApplicationSettings.REPEAT_STATUS}, new byte[]{ 697 PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal( 698 PlayerApplicationSettings.REPEAT_STATUS, repeatMode)}); 699 } 700 setShuffle(int shuffleMode)701 private void setShuffle(int shuffleMode) { 702 mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1, 703 new byte[]{PlayerApplicationSettings.SHUFFLE_STATUS}, new byte[]{ 704 PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal( 705 PlayerApplicationSettings.SHUFFLE_STATUS, shuffleMode)}); 706 } 707 processAvailablePlayerChanged()708 private void processAvailablePlayerChanged() { 709 logD("processAvailablePlayerChanged"); 710 mBrowseTree.mRootNode.setCached(false); 711 mBrowseTree.mRootNode.setExpectedChildren(255); 712 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode); 713 removeUnusedArtworkFromBrowseTree(); 714 } 715 } 716 717 // Handle the get folder listing action 718 // a) Fetch the listing of folders 719 // b) Once completed return the object listing 720 class GetFolderList extends State { 721 private static final String STATE_TAG = "Avrcp.GetFolderList"; 722 723 boolean mAbort; 724 BrowseTree.BrowseNode mBrowseNode; 725 BrowseTree.BrowseNode mNextStep; 726 727 @Override enter()728 public void enter() { 729 logD(STATE_TAG + " Entering GetFolderList"); 730 // Setup the timeouts. 731 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 732 super.enter(); 733 mAbort = false; 734 Message msg = getCurrentMessage(); 735 if (msg.what == MESSAGE_GET_FOLDER_ITEMS) { 736 { 737 logD(STATE_TAG + " new Get Request"); 738 mBrowseNode = (BrowseTree.BrowseNode) msg.obj; 739 } 740 } 741 742 if (mBrowseNode == null) { 743 transitionTo(mConnected); 744 } else { 745 navigateToFolderOrRetrieve(mBrowseNode); 746 } 747 } 748 749 @Override processMessage(Message msg)750 public boolean processMessage(Message msg) { 751 logD(STATE_TAG + " processMessage " + msg.what); 752 switch (msg.what) { 753 case MESSAGE_PROCESS_GET_FOLDER_ITEMS: 754 ArrayList<AvrcpItem> folderList = (ArrayList<AvrcpItem>) msg.obj; 755 int endIndicator = mBrowseNode.getExpectedChildren() - 1; 756 logD("GetFolderItems: End " + endIndicator 757 + " received " + folderList.size()); 758 759 // Queue up image download if the item has an image and we don't have it yet 760 // Only do this if the feature is enabled. 761 for (AvrcpItem track : folderList) { 762 if (shouldDownloadBrowsedImages()) { 763 downloadImageIfNeeded(track); 764 } else { 765 track.setCoverArtUuid(null); 766 } 767 } 768 769 // Always update the node so that the user does not wait forever 770 // for the list to populate. 771 int newSize = mBrowseNode.addChildren(folderList); 772 logD("Added " + newSize + " items to the browse tree"); 773 notifyChanged(mBrowseNode); 774 775 if (mBrowseNode.getChildrenCount() >= endIndicator || folderList.size() == 0 776 || mAbort) { 777 // If we have fetched all the elements or if the remotes sends us 0 elements 778 // (which can lead us into a loop since mCurrInd does not proceed) we simply 779 // abort. 780 mBrowseNode.setCached(true); 781 transitionTo(mConnected); 782 } else { 783 // Fetch the next set of items. 784 fetchContents(mBrowseNode); 785 // Reset the timeout message since we are doing a new fetch now. 786 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 787 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 788 } 789 break; 790 case MESSAGE_PROCESS_SET_BROWSED_PLAYER: 791 mBrowseTree.setCurrentBrowsedPlayer(mNextStep.getID(), msg.arg1, msg.arg2); 792 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 793 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 794 navigateToFolderOrRetrieve(mBrowseNode); 795 break; 796 797 case MESSAGE_PROCESS_FOLDER_PATH: 798 mBrowseTree.setCurrentBrowsedFolder(mNextStep.getID()); 799 mBrowseTree.getCurrentBrowsedFolder().setExpectedChildren(msg.arg1); 800 801 // AVRCP Specification says, if we're not database aware, we must disconnect and 802 // reconnect our BIP client each time we successfully change path 803 refreshCoverArt(); 804 805 if (mAbort) { 806 transitionTo(mConnected); 807 } else { 808 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 809 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 810 navigateToFolderOrRetrieve(mBrowseNode); 811 } 812 break; 813 814 case MESSAGE_PROCESS_GET_PLAYER_ITEMS: 815 BrowseTree.BrowseNode rootNode = mBrowseTree.mRootNode; 816 if (!rootNode.isCached()) { 817 List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj; 818 mAvailablePlayerList.clear(); 819 for (AvrcpPlayer player : playerList) { 820 mAvailablePlayerList.put(player.getId(), player); 821 } 822 rootNode.addChildren(playerList); 823 mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT); 824 rootNode.setExpectedChildren(playerList.size()); 825 rootNode.setCached(true); 826 notifyChanged(rootNode); 827 } 828 transitionTo(mConnected); 829 break; 830 831 case MESSAGE_INTERNAL_CMD_TIMEOUT: 832 // We have timed out to execute the request, we should simply send 833 // whatever listing we have gotten until now. 834 Log.w(TAG, "TIMEOUT"); 835 transitionTo(mConnected); 836 break; 837 838 case MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE: 839 // If we have gotten an error for OUT OF RANGE we have 840 // already sent all the items to the client hence simply 841 // transition to Connected state here. 842 mBrowseNode.setCached(true); 843 transitionTo(mConnected); 844 break; 845 846 case MESSAGE_GET_FOLDER_ITEMS: 847 if (!mBrowseNode.equals(msg.obj)) { 848 if (shouldAbort(mBrowseNode.getScope(), 849 ((BrowseTree.BrowseNode) msg.obj).getScope())) { 850 mAbort = true; 851 } 852 deferMessage(msg); 853 logD("GetFolderItems: Go Get Another Directory"); 854 } else { 855 logD("GetFolderItems: Get The Same Directory, ignore"); 856 } 857 break; 858 859 default: 860 // All of these messages should be handled by parent state immediately. 861 return false; 862 } 863 return true; 864 } 865 866 /** 867 * shouldAbort calculates the cases where fetching the current directory is no longer 868 * necessary. 869 * 870 * @return true: a new folder in the same scope 871 * a new player while fetching contents of a folder 872 * false: other cases, specifically Now Playing while fetching a folder 873 */ shouldAbort(int currentScope, int fetchScope)874 private boolean shouldAbort(int currentScope, int fetchScope) { 875 if ((currentScope == fetchScope) 876 || (currentScope == AvrcpControllerService.BROWSE_SCOPE_VFS 877 && fetchScope == AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST)) { 878 return true; 879 } 880 return false; 881 } 882 fetchContents(BrowseTree.BrowseNode target)883 private void fetchContents(BrowseTree.BrowseNode target) { 884 int start = target.getChildrenCount(); 885 int end = Math.min(target.getExpectedChildren(), target.getChildrenCount() 886 + ITEM_PAGE_SIZE) - 1; 887 logD("fetchContents(title=" + target.getID() + ", scope=" + target.getScope() 888 + ", start=" + start + ", end=" + end + ", expected=" 889 + target.getExpectedChildren() + ")"); 890 switch (target.getScope()) { 891 case AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST: 892 mService.getPlayerListNative(mDeviceAddress, 893 start, end); 894 break; 895 case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING: 896 mService.getNowPlayingListNative( 897 mDeviceAddress, start, end); 898 break; 899 case AvrcpControllerService.BROWSE_SCOPE_VFS: 900 mService.getFolderListNative(mDeviceAddress, 901 start, end); 902 break; 903 default: 904 Log.e(TAG, STATE_TAG + " Scope " + target.getScope() 905 + " cannot be handled here."); 906 } 907 } 908 909 /* One of several things can happen when trying to get a folder list 910 * 911 * 912 * 0: The folder handle is no longer valid 913 * 1: The folder contents can be retrieved directly (NowPlaying, Root, Current) 914 * 2: The folder is a browsable player 915 * 3: The folder is a non browsable player 916 * 4: The folder is not a child of the current folder 917 * 5: The folder is a child of the current folder 918 * 919 */ navigateToFolderOrRetrieve(BrowseTree.BrowseNode target)920 private void navigateToFolderOrRetrieve(BrowseTree.BrowseNode target) { 921 mNextStep = mBrowseTree.getNextStepToFolder(target); 922 logD("NAVIGATING From " 923 + mBrowseTree.getCurrentBrowsedFolder().toString()); 924 logD("NAVIGATING Toward " + target.toString()); 925 if (mNextStep == null) { 926 return; 927 } else if (target.equals(mBrowseTree.mNowPlayingNode) 928 || target.equals(mBrowseTree.mRootNode) 929 || mNextStep.equals(mBrowseTree.getCurrentBrowsedFolder())) { 930 fetchContents(mNextStep); 931 } else if (mNextStep.isPlayer()) { 932 logD("NAVIGATING Player " + mNextStep.toString()); 933 if (mNextStep.isBrowsable()) { 934 mService.setBrowsedPlayerNative( 935 mDeviceAddress, (int) mNextStep.getBluetoothID()); 936 } else { 937 logD("Player doesn't support browsing"); 938 mNextStep.setCached(true); 939 transitionTo(mConnected); 940 } 941 } else if (mNextStep.equals(mBrowseTree.mNavigateUpNode)) { 942 logD("NAVIGATING UP " + mNextStep.toString()); 943 mNextStep = mBrowseTree.getCurrentBrowsedFolder().getParent(); 944 mBrowseTree.getCurrentBrowsedFolder().setCached(false); 945 removeUnusedArtworkFromBrowseTree(); 946 mService.changeFolderPathNative( 947 mDeviceAddress, 948 AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP, 949 0); 950 951 } else { 952 logD("NAVIGATING DOWN " + mNextStep.toString()); 953 mService.changeFolderPathNative( 954 mDeviceAddress, 955 AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN, 956 mNextStep.getBluetoothID()); 957 } 958 } 959 960 @Override exit()961 public void exit() { 962 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 963 mBrowseNode = null; 964 super.exit(); 965 } 966 } 967 968 protected class Disconnecting extends State { 969 @Override enter()970 public void enter() { 971 disconnectCoverArt(); 972 onBrowsingDisconnected(); 973 setActive(false); 974 broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING); 975 transitionTo(mDisconnected); 976 } 977 } 978 979 /** 980 * Handle a request to align our local volume with the volume of a remote device. If 981 * we're assuming the source volume is fixed then a response of ABS_VOL_MAX will always be 982 * sent and no volume adjustment action will be taken on the sink side. 983 * 984 * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX] 985 * @param label Volume notification label 986 */ handleAbsVolumeRequest(int absVol, int label)987 private void handleAbsVolumeRequest(int absVol, int label) { 988 logD("handleAbsVolumeRequest: absVol = " + absVol + ", label = " + label); 989 if (mIsVolumeFixed) { 990 logD("Source volume is assumed to be fixed, responding with max volume"); 991 absVol = ABS_VOL_BASE; 992 } else { 993 mVolumeChangedNotificationsToIgnore++; 994 removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT); 995 sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT, 996 ABS_VOL_TIMEOUT_MILLIS); 997 setAbsVolume(absVol); 998 } 999 mService.sendAbsVolRspNative(mDeviceAddress, absVol, label); 1000 } 1001 1002 /** 1003 * Align our volume with a requested absolute volume level 1004 * 1005 * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX] 1006 */ setAbsVolume(int absVol)1007 private void setAbsVolume(int absVol) { 1008 int maxLocalVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 1009 int curLocalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 1010 int reqLocalVolume = (maxLocalVolume * absVol) / ABS_VOL_BASE; 1011 logD("setAbsVolme: absVol = " + absVol + ", reqLocal = " + reqLocalVolume 1012 + ", curLocal = " + curLocalVolume + ", maxLocal = " + maxLocalVolume); 1013 1014 /* 1015 * In some cases change in percentage is not sufficient enough to warrant 1016 * change in index values which are in range of 0-15. For such cases 1017 * no action is required 1018 */ 1019 if (reqLocalVolume != curLocalVolume) { 1020 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, reqLocalVolume, 1021 AudioManager.FLAG_SHOW_UI); 1022 } 1023 } 1024 getAbsVolume()1025 private int getAbsVolume() { 1026 if (mIsVolumeFixed) { 1027 return ABS_VOL_BASE; 1028 } 1029 int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 1030 int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 1031 int newIndex = (currIndex * ABS_VOL_BASE) / maxVolume; 1032 return newIndex; 1033 } 1034 shouldDownloadBrowsedImages()1035 private boolean shouldDownloadBrowsedImages() { 1036 return mService.getResources() 1037 .getBoolean(R.bool.avrcp_controller_cover_art_browsed_images); 1038 } 1039 downloadImageIfNeeded(AvrcpItem track)1040 private void downloadImageIfNeeded(AvrcpItem track) { 1041 if (mCoverArtManager == null) return; 1042 String uuid = track.getCoverArtUuid(); 1043 Uri imageUri = null; 1044 if (uuid != null) { 1045 imageUri = mCoverArtManager.getImageUri(mDevice, uuid); 1046 if (imageUri != null) { 1047 track.setCoverArtLocation(imageUri); 1048 } else { 1049 mCoverArtManager.downloadImage(mDevice, uuid); 1050 } 1051 } 1052 } 1053 1054 MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() { 1055 @Override 1056 public void onPlay() { 1057 logD("onPlay"); 1058 onPrepare(); 1059 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY); 1060 } 1061 1062 @Override 1063 public void onPause() { 1064 logD("onPause"); 1065 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 1066 } 1067 1068 @Override 1069 public void onSkipToNext() { 1070 logD("onSkipToNext"); 1071 onPrepare(); 1072 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD); 1073 } 1074 1075 @Override 1076 public void onSkipToPrevious() { 1077 logD("onSkipToPrevious"); 1078 onPrepare(); 1079 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD); 1080 } 1081 1082 @Override 1083 public void onSkipToQueueItem(long id) { 1084 logD("onSkipToQueueItem id=" + id); 1085 onPrepare(); 1086 BrowseTree.BrowseNode node = mBrowseTree.getTrackFromNowPlayingList((int) id); 1087 if (node != null) { 1088 sendMessage(MESSAGE_PLAY_ITEM, node); 1089 } 1090 } 1091 1092 @Override 1093 public void onStop() { 1094 logD("onStop"); 1095 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_STOP); 1096 } 1097 1098 @Override 1099 public void onPrepare() { 1100 logD("onPrepare"); 1101 A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); 1102 if (a2dpSinkService != null) { 1103 a2dpSinkService.requestAudioFocus(mDevice, true); 1104 } 1105 } 1106 1107 @Override 1108 public void onRewind() { 1109 logD("onRewind"); 1110 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_REWIND); 1111 } 1112 1113 @Override 1114 public void onFastForward() { 1115 logD("onFastForward"); 1116 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FF); 1117 } 1118 1119 @Override 1120 public void onPlayFromMediaId(String mediaId, Bundle extras) { 1121 logD("onPlayFromMediaId"); 1122 // Play the item if possible. 1123 onPrepare(); 1124 BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId); 1125 if (node != null) { 1126 // node was found on this bluetooth device 1127 sendMessage(MESSAGE_PLAY_ITEM, node); 1128 } else { 1129 // node was not found on this device, pause here, and play on another device 1130 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 1131 mService.playItem(mediaId); 1132 } 1133 } 1134 1135 @Override 1136 public void onSetRepeatMode(int repeatMode) { 1137 logD("onSetRepeatMode"); 1138 sendMessage(MSG_AVRCP_SET_REPEAT, repeatMode); 1139 } 1140 1141 @Override 1142 public void onSetShuffleMode(int shuffleMode) { 1143 logD("onSetShuffleMode"); 1144 sendMessage(MSG_AVRCP_SET_SHUFFLE, shuffleMode); 1145 1146 } 1147 }; 1148 broadcastConnectionStateChanged(int currentState)1149 protected void broadcastConnectionStateChanged(int currentState) { 1150 if (mMostRecentState == currentState) { 1151 return; 1152 } 1153 if (currentState == BluetoothProfile.STATE_CONNECTED) { 1154 MetricsLogger.logProfileConnectionEvent( 1155 BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER); 1156 } 1157 logD("Connection state " + mDevice + ": " + mMostRecentState + "->" + currentState); 1158 Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED); 1159 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState); 1160 intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState); 1161 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 1162 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 1163 mMostRecentState = currentState; 1164 mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 1165 } 1166 shouldRequestFocus()1167 private boolean shouldRequestFocus() { 1168 return mService.getResources() 1169 .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus); 1170 } 1171 } 1172