1 /* 2 * Copyright 2018 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.avrcp; 18 19 import android.annotation.NonNull; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothProfile; 22 import android.bluetooth.BluetoothUtils; 23 import android.bluetooth.IBluetoothAvrcpTarget; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.media.AudioManager; 29 import android.os.Looper; 30 import android.os.UserManager; 31 import android.sysprop.BluetoothProperties; 32 import android.text.TextUtils; 33 import android.util.Log; 34 import android.view.KeyEvent; 35 36 import com.android.bluetooth.BluetoothEventLogger; 37 import com.android.bluetooth.BluetoothMetricsProto; 38 import com.android.bluetooth.R; 39 import com.android.bluetooth.Utils; 40 import com.android.bluetooth.a2dp.A2dpService; 41 import com.android.bluetooth.audio_util.MediaData; 42 import com.android.bluetooth.audio_util.MediaPlayerList; 43 import com.android.bluetooth.audio_util.MediaPlayerWrapper; 44 import com.android.bluetooth.audio_util.Metadata; 45 import com.android.bluetooth.audio_util.PlayStatus; 46 import com.android.bluetooth.audio_util.PlayerInfo; 47 import com.android.bluetooth.audio_util.PlayerSettingsManager; 48 import com.android.bluetooth.btservice.MetricsLogger; 49 import com.android.bluetooth.btservice.ProfileService; 50 import com.android.bluetooth.btservice.ServiceFactory; 51 import com.android.internal.annotations.VisibleForTesting; 52 53 import java.util.List; 54 import java.util.Objects; 55 56 /** Provides Bluetooth AVRCP Target profile as a service in the Bluetooth application. */ 57 public class AvrcpTargetService extends ProfileService { 58 private static final String TAG = AvrcpTargetService.class.getSimpleName(); 59 60 private static final int MEDIA_KEY_EVENT_LOGGER_SIZE = 20; 61 private static final String MEDIA_KEY_EVENT_LOGGER_TITLE = "BTAudio Media Key Events"; 62 private final BluetoothEventLogger mMediaKeyEventLogger = 63 new BluetoothEventLogger(MEDIA_KEY_EVENT_LOGGER_SIZE, MEDIA_KEY_EVENT_LOGGER_TITLE); 64 65 private AvrcpVersion mAvrcpVersion; 66 private MediaPlayerList mMediaPlayerList; 67 private PlayerSettingsManager mPlayerSettingsManager; 68 private AudioManager mAudioManager; 69 private AvrcpBroadcastReceiver mReceiver; 70 private AvrcpNativeInterface mNativeInterface; 71 private AvrcpVolumeManager mVolumeManager; 72 private ServiceFactory mFactory = new ServiceFactory(); 73 private final BroadcastReceiver mUserUnlockedReceiver = 74 new BroadcastReceiver() { 75 @Override 76 public void onReceive(Context context, Intent intent) { 77 // EXTRA_USER_HANDLE is sent for ACTION_USER_UNLOCKED 78 // (even if the documentation doesn't mention it) 79 final int userId = 80 intent.getIntExtra( 81 Intent.EXTRA_USER_HANDLE, 82 BluetoothUtils.USER_HANDLE_NULL.getIdentifier()); 83 if (userId == BluetoothUtils.USER_HANDLE_NULL.getIdentifier()) { 84 Log.e(TAG, "userChangeReceiver received an invalid EXTRA_USER_HANDLE"); 85 return; 86 } 87 if (mMediaPlayerList != null) { 88 mMediaPlayerList.init(new ListCallback()); 89 } 90 } 91 }; 92 93 // Only used to see if the metadata has changed from its previous value 94 private MediaData mCurrentData; 95 96 // Cover Art Service (Storage + BIP Server) 97 private AvrcpCoverArtService mAvrcpCoverArtService = null; 98 99 private static AvrcpTargetService sInstance = null; 100 AvrcpTargetService(Context ctx)101 public AvrcpTargetService(Context ctx) { 102 super(ctx); 103 } 104 105 /** Checks for profile enabled state in Bluetooth sysprops. */ isEnabled()106 public static boolean isEnabled() { 107 return BluetoothProperties.isProfileAvrcpTargetEnabled().orElse(false); 108 } 109 110 /** Callbacks from {@link MediaPlayerList} to update the MediaData and folder updates. */ 111 class ListCallback implements MediaPlayerList.MediaUpdateCallback { 112 @Override run(MediaData data)113 public void run(MediaData data) { 114 if (mNativeInterface == null) return; 115 116 boolean metadata = !Objects.equals(mCurrentData.metadata, data.metadata); 117 boolean state = !MediaPlayerWrapper.playstateEquals(mCurrentData.state, data.state); 118 boolean queue = !Objects.equals(mCurrentData.queue, data.queue); 119 120 Log.d( 121 TAG, 122 "onMediaUpdated: track_changed=" 123 + metadata 124 + " state=" 125 + state 126 + " queue=" 127 + queue); 128 mCurrentData = data; 129 130 mNativeInterface.sendMediaUpdate(metadata, state, queue); 131 } 132 133 @Override run(boolean availablePlayers, boolean addressedPlayers, boolean uids)134 public void run(boolean availablePlayers, boolean addressedPlayers, boolean uids) { 135 if (mNativeInterface == null) return; 136 137 mNativeInterface.sendFolderUpdate(availablePlayers, addressedPlayers, uids); 138 } 139 } 140 141 /** 142 * Listens for {@link AudioManager.ACTION_VOLUME_CHANGED} events to update {@link 143 * AvrcpVolumeManager}. 144 */ 145 private class AvrcpBroadcastReceiver extends BroadcastReceiver { 146 @Override onReceive(Context context, Intent intent)147 public void onReceive(Context context, Intent intent) { 148 String action = intent.getAction(); 149 if (action.equals(AudioManager.ACTION_VOLUME_CHANGED)) { 150 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 151 if (streamType == AudioManager.STREAM_MUSIC) { 152 int volume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); 153 BluetoothDevice activeDevice = getA2dpActiveDevice(); 154 if (activeDevice != null 155 && !mVolumeManager.getAbsoluteVolumeSupported(activeDevice)) { 156 Log.d(TAG, "stream volume change to " + volume + " " + activeDevice); 157 mVolumeManager.storeVolumeForDevice(activeDevice, volume); 158 } 159 } 160 } 161 } 162 } 163 164 /** Sets the AvrcpTargetService instance. */ 165 @VisibleForTesting set(AvrcpTargetService instance)166 public static void set(AvrcpTargetService instance) { 167 sInstance = instance; 168 } 169 170 /** 171 * Returns the {@link AvrcpTargetService} instance. 172 * 173 * <p>Returns null if the service hasn't been initialized. 174 */ get()175 public static AvrcpTargetService get() { 176 return sInstance; 177 } 178 179 /** Returns the {@link AvrcpCoverArtService} instance. */ getCoverArtService()180 public AvrcpCoverArtService getCoverArtService() { 181 return mAvrcpCoverArtService; 182 } 183 184 @Override getName()185 public String getName() { 186 return TAG; 187 } 188 189 @Override initBinder()190 protected IProfileServiceBinder initBinder() { 191 return new AvrcpTargetBinder(this); 192 } 193 194 @Override start()195 public void start() { 196 if (sInstance != null) { 197 throw new IllegalStateException("start() called twice"); 198 } 199 200 IntentFilter userFilter = new IntentFilter(); 201 userFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 202 userFilter.addAction(Intent.ACTION_USER_UNLOCKED); 203 getApplicationContext().registerReceiver(mUserUnlockedReceiver, userFilter); 204 205 Log.i(TAG, "Starting the AVRCP Target Service"); 206 mCurrentData = new MediaData(null, null, null); 207 208 mAudioManager = getSystemService(AudioManager.class); 209 210 mMediaPlayerList = new MediaPlayerList(Looper.myLooper(), this); 211 212 mPlayerSettingsManager = new PlayerSettingsManager(mMediaPlayerList, this); 213 214 mNativeInterface = AvrcpNativeInterface.getInstance(); 215 mNativeInterface.init(AvrcpTargetService.this); 216 217 mAvrcpVersion = AvrcpVersion.getCurrentSystemPropertiesValue(); 218 219 mVolumeManager = new AvrcpVolumeManager(this, mAudioManager, mNativeInterface); 220 221 UserManager userManager = getApplicationContext().getSystemService(UserManager.class); 222 if (userManager.isUserUnlocked()) { 223 mMediaPlayerList.init(new ListCallback()); 224 } 225 226 if (getResources().getBoolean(R.bool.avrcp_target_enable_cover_art)) { 227 if (mAvrcpVersion.isAtleastVersion(AvrcpVersion.AVRCP_VERSION_1_6)) { 228 mAvrcpCoverArtService = new AvrcpCoverArtService(); 229 boolean started = mAvrcpCoverArtService.start(); 230 if (!started) { 231 Log.e(TAG, "Failed to start cover art service"); 232 mAvrcpCoverArtService = null; 233 } 234 } else { 235 Log.e(TAG, "Please use AVRCP version 1.6 to enable cover art"); 236 } 237 } 238 239 mReceiver = new AvrcpBroadcastReceiver(); 240 IntentFilter filter = new IntentFilter(); 241 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 242 filter.addAction(AudioManager.ACTION_VOLUME_CHANGED); 243 registerReceiver(mReceiver, filter); 244 245 // Only allow the service to be used once it is initialized 246 sInstance = this; 247 } 248 249 @Override stop()250 public void stop() { 251 Log.i(TAG, "Stopping the AVRCP Target Service"); 252 253 if (sInstance == null) { 254 Log.w(TAG, "stop() called before start()"); 255 return; 256 } 257 258 if (mAvrcpCoverArtService != null) { 259 mAvrcpCoverArtService.stop(); 260 } 261 mAvrcpCoverArtService = null; 262 263 sInstance = null; 264 unregisterReceiver(mReceiver); 265 266 // We check the interfaces first since they only get set on User Unlocked 267 if (mPlayerSettingsManager != null) mPlayerSettingsManager.cleanup(); 268 if (mMediaPlayerList != null) mMediaPlayerList.cleanup(); 269 if (mNativeInterface != null) mNativeInterface.cleanup(); 270 getApplicationContext().unregisterReceiver(mUserUnlockedReceiver); 271 272 mPlayerSettingsManager = null; 273 mMediaPlayerList = null; 274 mNativeInterface = null; 275 mAudioManager = null; 276 mReceiver = null; 277 } 278 279 /** Returns the active A2DP {@link BluetoothDevice} */ getA2dpActiveDevice()280 private BluetoothDevice getA2dpActiveDevice() { 281 A2dpService service = mFactory.getA2dpService(); 282 if (service == null) { 283 return null; 284 } 285 return service.getActiveDevice(); 286 } 287 288 /** 289 * Sets a {@link BluetoothDevice} as active A2DP device. 290 * 291 * <p>This will be called by the native stack when a play event is received from a remote 292 * device. See packages/modules/Bluetooth/system/profile/avrcp/device.cc. 293 */ setA2dpActiveDevice(@onNull BluetoothDevice device)294 private void setA2dpActiveDevice(@NonNull BluetoothDevice device) { 295 A2dpService service = A2dpService.getA2dpService(); 296 if (service == null) { 297 Log.d(TAG, "setA2dpActiveDevice: A2dp service not found"); 298 return; 299 } 300 service.setActiveDevice(device); 301 } 302 303 /** Informs {@link AvrcpVolumeManager} that a new device is connected */ deviceConnected(BluetoothDevice device, boolean absoluteVolume)304 void deviceConnected(BluetoothDevice device, boolean absoluteVolume) { 305 Log.i(TAG, "deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume); 306 mVolumeManager.deviceConnected(device, absoluteVolume); 307 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.AVRCP); 308 } 309 310 /** Informs {@link AvrcpVolumeManager} that a device is disconnected */ deviceDisconnected(BluetoothDevice device)311 void deviceDisconnected(BluetoothDevice device) { 312 Log.i(TAG, "deviceDisconnected: device=" + device); 313 mVolumeManager.deviceDisconnected(device); 314 } 315 316 /** Removes the stored volume for a device. */ removeStoredVolumeForDevice(BluetoothDevice device)317 public void removeStoredVolumeForDevice(BluetoothDevice device) { 318 if (device == null) return; 319 320 mVolumeManager.removeStoredVolumeForDevice(device); 321 } 322 323 /** 324 * Returns the remembered volume for a device or -1 if none. 325 * 326 * <p>See {@link AvrcpVolumeManager}. 327 */ getRememberedVolumeForDevice(BluetoothDevice device)328 public int getRememberedVolumeForDevice(BluetoothDevice device) { 329 if (device == null) return -1; 330 331 return mVolumeManager.getVolume(device, mVolumeManager.getNewDeviceVolume()); 332 } 333 334 /** 335 * Handle when A2DP connection state changes. 336 * 337 * <p>If the A2DP connection disconnects, we request AVRCP to disconnect device as well. 338 */ handleA2dpConnectionStateChanged(BluetoothDevice device, int newState)339 public void handleA2dpConnectionStateChanged(BluetoothDevice device, int newState) { 340 if (device == null || mNativeInterface == null) return; 341 if (newState == BluetoothProfile.STATE_DISCONNECTED) { 342 // If there is no connection, disconnectDevice() will do nothing 343 if (mNativeInterface.disconnectDevice(device)) { 344 Log.d(TAG, "request to disconnect device " + device); 345 } 346 } 347 } 348 349 /** 350 * Handles active device changes in A2DP. 351 * 352 * <p>Signals {@link AvrcpVolumeManager} that the current A2DP active device has changed which 353 * will then inform {@link AudioManager} about its absolute volume support. If absolute volume 354 * is supported, it will also set the volume level on the remote device. 355 * 356 * <p>Informs all remote devices that there is a play status update. 357 */ handleA2dpActiveDeviceChanged(BluetoothDevice device)358 public void handleA2dpActiveDeviceChanged(BluetoothDevice device) { 359 mVolumeManager.volumeDeviceSwitched(device); 360 if (mNativeInterface != null) { 361 // Update all the playback status info for each connected device 362 mNativeInterface.sendMediaUpdate(false, true, false); 363 } 364 } 365 366 /** Informs {@link AvrcpVolumeManager} that a remote device requests a volume change */ setVolume(int avrcpVolume)367 void setVolume(int avrcpVolume) { 368 BluetoothDevice activeDevice = getA2dpActiveDevice(); 369 if (activeDevice == null) { 370 Log.d(TAG, "setVolume: no active device"); 371 return; 372 } 373 374 mVolumeManager.setVolume(activeDevice, avrcpVolume); 375 } 376 377 /** 378 * Sends a volume change request to the remote device. 379 * 380 * <p>Does nothing if the device doesn't support absolute volume. 381 * 382 * <p>The remote device that will receive the request is the A2DP active device. 383 */ sendVolumeChanged(int deviceVolume)384 public void sendVolumeChanged(int deviceVolume) { 385 BluetoothDevice activeDevice = getA2dpActiveDevice(); 386 if (activeDevice == null) { 387 Log.d(TAG, "sendVolumeChanged: no active device"); 388 return; 389 } 390 391 mVolumeManager.sendVolumeChanged(activeDevice, deviceVolume); 392 } 393 394 /** 395 * Returns the current song info from the active player in {@link MediaPlayerList}. 396 * 397 * <p>If a {@link com.android.bluetooth.audio_util.Image} is present in the {@link Metadata}, 398 * add its handle from {@link AvrcpCoverArtService}. 399 */ getCurrentSongInfo()400 Metadata getCurrentSongInfo() { 401 Metadata metadata = mMediaPlayerList.getCurrentSongInfo(); 402 if (mAvrcpCoverArtService != null && metadata.image != null) { 403 String imageHandle = mAvrcpCoverArtService.storeImage(metadata.image); 404 if (imageHandle != null) metadata.image.setImageHandle(imageHandle); 405 } 406 return metadata; 407 } 408 409 /** Returns the current play status of the active player from {@link MediaPlayerList}. */ getPlayState()410 PlayStatus getPlayState() { 411 return PlayStatus.fromPlaybackState( 412 mMediaPlayerList.getCurrentPlayStatus(), 413 Long.parseLong(getCurrentSongInfo().duration)); 414 } 415 416 /** Returns the current media ID of the active player from {@link MediaPlayerList}. */ getCurrentMediaId()417 String getCurrentMediaId() { 418 String id = mMediaPlayerList.getCurrentMediaId(); 419 if (id != null && !id.isEmpty()) return id; 420 421 Metadata song = getCurrentSongInfo(); 422 if (song != null && !song.mediaId.isEmpty()) return song.mediaId; 423 424 // We always want to return something, the error string just makes debugging easier 425 return "error"; 426 } 427 428 /** 429 * Returns the playing queue of the active player from {@link MediaPlayerList}. 430 * 431 * <p>If a {@link com.android.bluetooth.audio_util.Image} is present in the {@link Metadata} of 432 * the queued items, add its handle from {@link AvrcpCoverArtService}. 433 */ getNowPlayingList()434 List<Metadata> getNowPlayingList() { 435 String currentMediaId = getCurrentMediaId(); 436 Metadata currentTrack = null; 437 String imageHandle = null; 438 List<Metadata> nowPlayingList = mMediaPlayerList.getNowPlayingList(); 439 if (mAvrcpCoverArtService != null) { 440 for (Metadata metadata : nowPlayingList) { 441 if (TextUtils.equals(metadata.mediaId, currentMediaId)) { 442 currentTrack = metadata; 443 } else if (metadata.image != null) { 444 imageHandle = mAvrcpCoverArtService.storeImage(metadata.image); 445 if (imageHandle != null) { 446 metadata.image.setImageHandle(imageHandle); 447 } 448 } 449 } 450 451 // Always store the current item from the queue last so we know the image is in storage 452 if (currentTrack != null) { 453 imageHandle = mAvrcpCoverArtService.storeImage(currentTrack.image); 454 if (imageHandle != null) { 455 currentTrack.image.setImageHandle(imageHandle); 456 } 457 } 458 } 459 return nowPlayingList; 460 } 461 462 /** 463 * Returns the active browsable player ID from {@link MediaPlayerList}. 464 * 465 * <p>Note: Currently, only returns the Bluetooth player ID. Browsable players are 466 * subdirectories of the Bluetooth player. See {@link MediaPlayerList} class description. 467 */ getCurrentPlayerId()468 int getCurrentPlayerId() { 469 return mMediaPlayerList.getCurrentPlayerId(); 470 } 471 472 /** 473 * Returns the list of browsable players from {@link MediaPlayerList}. 474 * 475 * <p>Note: Currently, only returns the Bluetooth player. Browsable players are subdirectories 476 * of the Bluetooth player. See {@link MediaPlayerList} class description. 477 */ getMediaPlayerList()478 List<PlayerInfo> getMediaPlayerList() { 479 return mMediaPlayerList.getMediaPlayerList(); 480 } 481 482 /** See {@link MediaPlayerList#getPlayerRoot}. */ getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb)483 void getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb) { 484 mMediaPlayerList.getPlayerRoot(playerId, cb); 485 } 486 487 /** See {@link MediaPlayerList#getFolderItems}. */ getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb)488 void getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb) { 489 mMediaPlayerList.getFolderItems(playerId, mediaId, cb); 490 } 491 492 /** See {@link MediaPlayerList#playItem}. */ playItem(int playerId, boolean nowPlaying, String mediaId)493 void playItem(int playerId, boolean nowPlaying, String mediaId) { 494 // NOTE: playerId isn't used if nowPlaying is true, since its assumed to be the current 495 // active player 496 mMediaPlayerList.playItem(playerId, nowPlaying, mediaId); 497 } 498 499 /** Informs {@link AudioManager} of an incoming key event from a remote device. */ sendMediaKeyEvent(int key, boolean pushed)500 void sendMediaKeyEvent(int key, boolean pushed) { 501 BluetoothDevice activeDevice = getA2dpActiveDevice(); 502 MediaPlayerWrapper player = mMediaPlayerList.getActivePlayer(); 503 mMediaKeyEventLogger.logd( 504 TAG, 505 "sendMediaKeyEvent:" 506 + " device=" 507 + activeDevice 508 + " key=" 509 + key 510 + " pushed=" 511 + pushed 512 + " to " 513 + (player == null ? null : player.getPackageName())); 514 int action = pushed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP; 515 KeyEvent event = new KeyEvent(action, AvrcpPassthrough.toKeyCode(key)); 516 mAudioManager.dispatchMediaKeyEvent(event); 517 } 518 519 /** 520 * Sets a {@link BluetoothDevice} as active A2DP device. 521 * 522 * <p>This will be called by the native stack when a play event is received from a remote 523 * device. See packages/modules/Bluetooth/system/profile/avrcp/device.cc. 524 */ setActiveDevice(BluetoothDevice device)525 void setActiveDevice(BluetoothDevice device) { 526 Log.i(TAG, "setActiveDevice: device=" + device); 527 if (device == null) { 528 Log.wtf(TAG, "setActiveDevice: could not find device " + device); 529 return; 530 } 531 setA2dpActiveDevice(device); 532 } 533 534 /** Called from native to update current active player shuffle mode. */ setShuffleMode(int shuffleMode)535 boolean setShuffleMode(int shuffleMode) { 536 return mPlayerSettingsManager.setPlayerShuffleMode(shuffleMode); 537 } 538 539 /** Called from native to update current active player repeat mode. */ setRepeatMode(int repeatMode)540 boolean setRepeatMode(int repeatMode) { 541 return mPlayerSettingsManager.setPlayerRepeatMode(repeatMode); 542 } 543 544 /** Called from native to get the current active player repeat mode. */ getRepeatMode()545 int getRepeatMode() { 546 return mPlayerSettingsManager.getPlayerRepeatMode(); 547 } 548 549 /** Called from native to get the current active player shuffle mode. */ getShuffleMode()550 int getShuffleMode() { 551 return mPlayerSettingsManager.getPlayerShuffleMode(); 552 } 553 554 /** Called from player callback to indicate new settings to remote device. */ sendPlayerSettings(int repeatMode, int shuffleMode)555 public void sendPlayerSettings(int repeatMode, int shuffleMode) { 556 if (mNativeInterface == null) { 557 Log.i(TAG, "Tried to send Player Settings while native interface is null"); 558 return; 559 } 560 561 mNativeInterface.sendPlayerSettings(repeatMode, shuffleMode); 562 } 563 564 /** Dump debugging information to the string builder */ dump(StringBuilder sb)565 public void dump(StringBuilder sb) { 566 sb.append("\nProfile: AvrcpTargetService:\n"); 567 if (sInstance == null) { 568 sb.append("AvrcpTargetService not running"); 569 return; 570 } 571 572 StringBuilder tempBuilder = new StringBuilder(); 573 tempBuilder.append("AVRCP version: " + mAvrcpVersion + "\n"); 574 575 if (mMediaPlayerList != null) { 576 mMediaPlayerList.dump(tempBuilder); 577 } else { 578 tempBuilder.append("\nMedia Player List is empty\n"); 579 } 580 581 mMediaKeyEventLogger.dump(tempBuilder); 582 tempBuilder.append("\n"); 583 mVolumeManager.dump(tempBuilder); 584 if (mAvrcpCoverArtService != null) { 585 tempBuilder.append("\n"); 586 mAvrcpCoverArtService.dump(tempBuilder); 587 } 588 589 // Tab everything over by two spaces 590 sb.append(tempBuilder.toString().replaceAll("(?m)^", " ")); 591 } 592 593 private static class AvrcpTargetBinder extends IBluetoothAvrcpTarget.Stub 594 implements IProfileServiceBinder { 595 private AvrcpTargetService mService; 596 AvrcpTargetBinder(AvrcpTargetService service)597 AvrcpTargetBinder(AvrcpTargetService service) { 598 mService = service; 599 } 600 601 @Override cleanup()602 public void cleanup() { 603 mService = null; 604 } 605 606 @Override sendVolumeChanged(int volume)607 public void sendVolumeChanged(int volume) { 608 if (mService == null 609 || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)) { 610 return; 611 } 612 613 mService.sendVolumeChanged(volume); 614 } 615 } 616 } 617