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