Home | History | Annotate | Download | only in newavrcp
      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.content.BroadcastReceiver;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.ResolveInfo;
     27 import android.media.session.MediaSession;
     28 import android.media.session.MediaSessionManager;
     29 import android.media.session.PlaybackState;
     30 import android.os.Handler;
     31 import android.os.Looper;
     32 import android.util.Log;
     33 import android.view.KeyEvent;
     34 
     35 import com.android.bluetooth.Utils;
     36 
     37 import java.util.ArrayList;
     38 import java.util.Collections;
     39 import java.util.HashMap;
     40 import java.util.HashSet;
     41 import java.util.List;
     42 import java.util.Map;
     43 import java.util.regex.Matcher;
     44 import java.util.regex.Pattern;
     45 
     46 /**
     47  * This class is directly responsible of maintaining the list of Browsable Players as well as
     48  * the list of Addressable Players. This variation of the list doesn't actually list all the
     49  * available players for a getAvailableMediaPlayers request. Instead it only reports one media
     50  * player with ID=0 and all the other browsable players are folders in the root of that player.
     51  *
     52  * Changing the directory to a browsable player will allow you to traverse that player as normal.
     53  * By only having one root player, we never have to send Addressed Player Changed notifications,
     54  * UIDs Changed notifications, or Available Players Changed notifications.
     55  *
     56  * TODO (apanicke): Add non-browsable players as song items to the root folder. Selecting that
     57  * player would effectively cause player switch by sending a play command to that player.
     58  */
     59 public class MediaPlayerList {
     60     private static final String TAG = "NewAvrcpMediaPlayerList";
     61     private static final boolean DEBUG = true;
     62     static boolean sTesting = false;
     63 
     64     private static final String PACKAGE_SCHEME = "package";
     65     private static final int NO_ACTIVE_PLAYER = 0;
     66     private static final int BLUETOOTH_PLAYER_ID = 0;
     67     private static final String BLUETOOTH_PLAYER_NAME = "Bluetooth Player";
     68 
     69     // mediaId's for the now playing list will be in the form of "NowPlayingId[XX]" where [XX]
     70     // is the Queue ID for the requested item.
     71     private static final String NOW_PLAYING_ID_PATTERN = Util.NOW_PLAYING_PREFIX + "([0-9]*)";
     72 
     73     // mediaId's for folder browsing will be in the form of [XX][mediaid],  where [XX] is a
     74     // two digit representation of the player id and [mediaid] is the original media id as a
     75     // string.
     76     private static final String BROWSE_ID_PATTERN = "\\d\\d.*";
     77 
     78     private Context mContext;
     79     private Looper mLooper; // Thread all media player callbacks and timeouts happen on
     80     private PackageManager mPackageManager;
     81     private MediaSessionManager mMediaSessionManager;
     82 
     83     private Map<Integer, MediaPlayerWrapper> mMediaPlayers =
     84             Collections.synchronizedMap(new HashMap<Integer, MediaPlayerWrapper>());
     85     private Map<String, Integer> mMediaPlayerIds =
     86             Collections.synchronizedMap(new HashMap<String, Integer>());
     87     private Map<Integer, BrowsedPlayerWrapper> mBrowsablePlayers =
     88             Collections.synchronizedMap(new HashMap<Integer, BrowsedPlayerWrapper>());
     89     private int mActivePlayerId = NO_ACTIVE_PLAYER;
     90 
     91     private AvrcpTargetService.ListCallback mCallback;
     92 
     93     interface MediaUpdateCallback {
     94         void run(MediaData data);
     95     }
     96 
     97     interface GetPlayerRootCallback {
     98         void run(int playerId, boolean success, String rootId, int numItems);
     99     }
    100 
    101     interface GetFolderItemsCallback {
    102         void run(String parentId, List<ListItem> items);
    103     }
    104 
    105     interface FolderUpdateCallback {
    106         void run(boolean availablePlayers, boolean addressedPlayers, boolean uids);
    107     }
    108 
    109     MediaPlayerList(Looper looper, Context context) {
    110         Log.v(TAG, "Creating MediaPlayerList");
    111 
    112         mLooper = looper;
    113         mContext = context;
    114 
    115         // Register for intents where available players might have changed
    116         IntentFilter pkgFilter = new IntentFilter();
    117         pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
    118         pkgFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
    119         pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
    120         pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
    121         pkgFilter.addDataScheme(PACKAGE_SCHEME);
    122         context.registerReceiver(mPackageChangedBroadcastReceiver, pkgFilter);
    123 
    124         mMediaSessionManager =
    125                 (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
    126         mMediaSessionManager.addOnActiveSessionsChangedListener(
    127                 mActiveSessionsChangedListener, null, new Handler(looper));
    128         mMediaSessionManager.setCallback(mButtonDispatchCallback, null);
    129     }
    130 
    131     void init(AvrcpTargetService.ListCallback callback) {
    132         Log.v(TAG, "Initializing MediaPlayerList");
    133         mCallback = callback;
    134 
    135         // Build the list of browsable players and afterwards, build the list of media players
    136         Intent intent = new Intent(android.service.media.MediaBrowserService.SERVICE_INTERFACE);
    137         List<ResolveInfo> playerList =
    138                 mContext
    139                     .getApplicationContext()
    140                     .getPackageManager()
    141                     .queryIntentServices(intent, PackageManager.MATCH_ALL);
    142 
    143         BrowsablePlayerConnector.connectToPlayers(mContext, mLooper, playerList,
    144                 (List<BrowsedPlayerWrapper> players) -> {
    145                 Log.i(TAG, "init: Browsable Player list size is " + players.size());
    146 
    147                 // Check to see if the list has been cleaned up before this completed
    148                 if (mMediaSessionManager == null) {
    149                     return;
    150                 }
    151 
    152                 for (BrowsedPlayerWrapper wrapper : players) {
    153                     // Generate new id and add the browsable player
    154                     if (!mMediaPlayerIds.containsKey(wrapper.getPackageName())) {
    155                         mMediaPlayerIds.put(wrapper.getPackageName(), mMediaPlayerIds.size() + 1);
    156                     }
    157 
    158                     d("Adding Browser Wrapper for " + wrapper.getPackageName() + " with id "
    159                             + mMediaPlayerIds.get(wrapper.getPackageName()));
    160 
    161                     mBrowsablePlayers.put(mMediaPlayerIds.get(wrapper.getPackageName()), wrapper);
    162 
    163                     wrapper.getFolderItems(wrapper.getRootId(),
    164                             (int status, String mediaId, List<ListItem> results) -> {
    165                                 d("Got the contents for: " + mediaId + " : num results="
    166                                         + results.size());
    167                             });
    168                 }
    169 
    170                 // Construct the list of current players
    171                 d("Initializing list of current media players");
    172                 List<android.media.session.MediaController> controllers =
    173                         mMediaSessionManager.getActiveSessions(null);
    174 
    175                 for (android.media.session.MediaController controller : controllers) {
    176                     addMediaPlayer(controller);
    177                 }
    178 
    179                 // If there were any active players and we don't already have one due to the Media
    180                 // Framework Callbacks then set the highest priority one to active
    181                 if (mActivePlayerId == 0 && mMediaPlayers.size() > 0) setActivePlayer(1);
    182             });
    183     }
    184 
    185     void cleanup() {
    186         mContext.unregisterReceiver(mPackageChangedBroadcastReceiver);
    187 
    188         mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveSessionsChangedListener);
    189         mMediaSessionManager.setCallback(null, null);
    190         mMediaSessionManager = null;
    191 
    192         mMediaPlayerIds.clear();
    193 
    194         for (MediaPlayerWrapper player : mMediaPlayers.values()) {
    195             player.cleanup();
    196         }
    197         mMediaPlayers.clear();
    198 
    199         for (BrowsedPlayerWrapper player : mBrowsablePlayers.values()) {
    200             player.disconnect();
    201         }
    202         mBrowsablePlayers.clear();
    203     }
    204 
    205     int getCurrentPlayerId() {
    206         return BLUETOOTH_PLAYER_ID;
    207     }
    208 
    209     MediaPlayerWrapper getActivePlayer() {
    210         return mMediaPlayers.get(mActivePlayerId);
    211     }
    212 
    213 
    214 
    215     // In this case the displayed player is the Bluetooth Player, the number of items is equal
    216     // to the number of players. The root ID will always be empty string in this case as well.
    217     void getPlayerRoot(int playerId, GetPlayerRootCallback cb) {
    218         cb.run(playerId, playerId == BLUETOOTH_PLAYER_ID, "", mBrowsablePlayers.size());
    219     }
    220 
    221     // Return the "Bluetooth Player" as the only player always
    222     List<PlayerInfo> getMediaPlayerList() {
    223         PlayerInfo info = new PlayerInfo();
    224         info.id = BLUETOOTH_PLAYER_ID;
    225         info.name = BLUETOOTH_PLAYER_NAME;
    226         info.browsable = true;
    227         List<PlayerInfo> ret = new ArrayList<PlayerInfo>();
    228         ret.add(info);
    229 
    230         return ret;
    231     }
    232 
    233     @NonNull
    234     String getCurrentMediaId() {
    235         final MediaPlayerWrapper player = getActivePlayer();
    236         if (player == null) return "";
    237 
    238         final PlaybackState state = player.getPlaybackState();
    239         final List<Metadata> queue = player.getCurrentQueue();
    240 
    241         // Disable the now playing list if the player doesn't have a queue or provide an active
    242         // queue ID that can be used to determine the active song in the queue.
    243         if (state == null
    244                 || state.getActiveQueueItemId() == MediaSession.QueueItem.UNKNOWN_ID
    245                 || queue.size() == 0) {
    246             d("getCurrentMediaId: No active queue item Id sending empty mediaId: PlaybackState="
    247                      + state);
    248             return "";
    249         }
    250 
    251         return Util.NOW_PLAYING_PREFIX + state.getActiveQueueItemId();
    252     }
    253 
    254     @NonNull
    255     Metadata getCurrentSongInfo() {
    256         final MediaPlayerWrapper player = getActivePlayer();
    257         if (player == null) return Util.empty_data();
    258 
    259         return player.getCurrentMetadata();
    260     }
    261 
    262     PlaybackState getCurrentPlayStatus() {
    263         final MediaPlayerWrapper player = getActivePlayer();
    264         if (player == null) return null;
    265 
    266         return player.getPlaybackState();
    267     }
    268 
    269     @NonNull
    270     List<Metadata> getNowPlayingList() {
    271         // Only send the current song for the now playing if there is no active song. See
    272         // |getCurrentMediaId()| for reasons why there might be no active song.
    273         if (getCurrentMediaId().equals("")) {
    274             List<Metadata> ret = new ArrayList<Metadata>();
    275             Metadata data = getCurrentSongInfo();
    276             data.mediaId = "";
    277             ret.add(data);
    278             return ret;
    279         }
    280 
    281         return getActivePlayer().getCurrentQueue();
    282     }
    283 
    284     void playItem(int playerId, boolean nowPlaying, String mediaId) {
    285         if (nowPlaying) {
    286             playNowPlayingItem(mediaId);
    287         } else {
    288             playFolderItem(mediaId);
    289         }
    290     }
    291 
    292     private void playNowPlayingItem(String mediaId) {
    293         d("playNowPlayingItem: mediaId=" + mediaId);
    294 
    295         Pattern regex = Pattern.compile(NOW_PLAYING_ID_PATTERN);
    296         Matcher m = regex.matcher(mediaId);
    297         if (!m.find()) {
    298             // This should never happen since we control the media ID's reported
    299             Log.wtf(TAG, "playNowPlayingItem: Couldn't match mediaId to pattern: mediaId="
    300                     + mediaId);
    301         }
    302 
    303         long queueItemId = Long.parseLong(m.group(1));
    304         if (getActivePlayer() != null) {
    305             getActivePlayer().playItemFromQueue(queueItemId);
    306         }
    307     }
    308 
    309     private void playFolderItem(String mediaId) {
    310         d("playFolderItem: mediaId=" + mediaId);
    311 
    312         if (!mediaId.matches(BROWSE_ID_PATTERN)) {
    313             // This should never happen since we control the media ID's reported
    314             Log.wtf(TAG, "playFolderItem: mediaId didn't match pattern: mediaId=" + mediaId);
    315         }
    316 
    317         int playerIndex = Integer.parseInt(mediaId.substring(0, 2));
    318         String itemId = mediaId.substring(2);
    319 
    320         if (!mBrowsablePlayers.containsKey(playerIndex)) {
    321             e("playFolderItem: Do not have the a browsable player with ID " + playerIndex);
    322             return;
    323         }
    324 
    325         mBrowsablePlayers.get(playerIndex).playItem(itemId);
    326     }
    327 
    328     void getFolderItemsMediaPlayerList(GetFolderItemsCallback cb) {
    329         d("getFolderItemsMediaPlayerList: Sending Media Player list for root directory");
    330 
    331         ArrayList<ListItem> playerList = new ArrayList<ListItem>();
    332         for (BrowsedPlayerWrapper player : mBrowsablePlayers.values()) {
    333 
    334             String displayName = Util.getDisplayName(mContext, player.getPackageName());
    335             int id = mMediaPlayerIds.get(player.getPackageName());
    336 
    337             d("getFolderItemsMediaPlayerList: Adding player " + displayName);
    338             Folder playerFolder = new Folder(String.format("%02d", id), false, displayName);
    339             playerList.add(new ListItem(playerFolder));
    340         }
    341         cb.run("", playerList);
    342         return;
    343     }
    344 
    345     void getFolderItems(int playerId, String mediaId, GetFolderItemsCallback cb) {
    346         // The playerId is unused since we always assume the remote device is using the
    347         // Bluetooth Player.
    348         d("getFolderItems(): playerId=" + playerId + ", mediaId=" + mediaId);
    349 
    350         // The device is requesting the content of the root folder. This folder contains a list of
    351         // Browsable Media Players displayed as folders with their contents contained within.
    352         if (mediaId.equals("")) {
    353             getFolderItemsMediaPlayerList(cb);
    354             return;
    355         }
    356 
    357         if (!mediaId.matches(BROWSE_ID_PATTERN)) {
    358             // This should never happen since we control the media ID's reported
    359             Log.wtf(TAG, "getFolderItems: mediaId didn't match pattern: mediaId=" + mediaId);
    360         }
    361 
    362         int playerIndex = Integer.parseInt(mediaId.substring(0, 2));
    363         String itemId = mediaId.substring(2);
    364 
    365         // TODO (apanicke): Add timeouts for looking up folder items since media browsers don't
    366         // have to respond.
    367         if (mBrowsablePlayers.containsKey(playerIndex)) {
    368             BrowsedPlayerWrapper wrapper = mBrowsablePlayers.get(playerIndex);
    369             if (itemId.equals("")) {
    370                 Log.i(TAG, "Empty media id, getting the root for "
    371                         + wrapper.getPackageName());
    372                 itemId = wrapper.getRootId();
    373             }
    374 
    375             wrapper.getFolderItems(itemId, (status, id, results) -> {
    376                 if (status != BrowsedPlayerWrapper.STATUS_SUCCESS) {
    377                     cb.run(mediaId, new ArrayList<ListItem>());
    378                     return;
    379                 }
    380 
    381                 String playerPrefix = String.format("%02d", playerIndex);
    382                 for (ListItem item : results) {
    383                     if (item.isFolder) {
    384                         item.folder.mediaId = playerPrefix.concat(item.folder.mediaId);
    385                     } else {
    386                         item.song.mediaId = playerPrefix.concat(item.song.mediaId);
    387                     }
    388                 }
    389                 cb.run(mediaId, results);
    390             });
    391             return;
    392         } else {
    393             cb.run(mediaId, new ArrayList<ListItem>());
    394         }
    395     }
    396 
    397     // Adds the controller to the MediaPlayerList or updates the controller if we already had
    398     // a controller for a package. Returns the new ID of the controller where its added or its
    399     // previous value if it already existed. Returns -1 if the controller passed in is invalid
    400     int addMediaPlayer(android.media.session.MediaController controller) {
    401         if (controller == null) return -1;
    402 
    403         // Each new player has an ID of 1 plus the highest ID. The ID 0 is reserved to signify that
    404         // there is no active player. If we already have a browsable player for the package, reuse
    405         // that key.
    406         String packageName = controller.getPackageName();
    407         if (!mMediaPlayerIds.containsKey(packageName)) {
    408             mMediaPlayerIds.put(packageName, mMediaPlayerIds.size() + 1);
    409         }
    410 
    411         int playerId = mMediaPlayerIds.get(packageName);
    412 
    413         // If we already have a controller for the package, then update it with this new controller
    414         // as the old controller has probably gone stale.
    415         if (mMediaPlayers.containsKey(playerId)) {
    416             d("Already have a controller for the player: " + packageName + ", updating instead");
    417             MediaPlayerWrapper player = mMediaPlayers.get(playerId);
    418             player.updateMediaController(MediaControllerFactory.wrap(controller));
    419 
    420             // If the media controller we updated was the active player check if the media updated
    421             if (playerId == mActivePlayerId) {
    422                 sendMediaUpdate(getActivePlayer().getCurrentMediaData());
    423             }
    424 
    425             return playerId;
    426         }
    427 
    428         MediaPlayerWrapper newPlayer = MediaPlayerWrapper.wrap(
    429                 MediaControllerFactory.wrap(controller),
    430                 mLooper);
    431 
    432         Log.i(TAG, "Adding wrapped media player: " + packageName + " at key: "
    433                 + mMediaPlayerIds.get(controller.getPackageName()));
    434 
    435         mMediaPlayers.put(playerId, newPlayer);
    436         return playerId;
    437     }
    438 
    439     void removeMediaPlayer(int playerId) {
    440         if (!mMediaPlayers.containsKey(playerId)) {
    441             e("Trying to remove nonexistent media player: " + playerId);
    442             return;
    443         }
    444 
    445         // If we removed the active player, set no player as active until the Media Framework
    446         // tells us otherwise
    447         if (playerId == mActivePlayerId && playerId != NO_ACTIVE_PLAYER) {
    448             getActivePlayer().unregisterCallback();
    449             mActivePlayerId = NO_ACTIVE_PLAYER;
    450         }
    451 
    452         final MediaPlayerWrapper wrapper = mMediaPlayers.get(playerId);
    453         d("Removing media player " + wrapper.getPackageName());
    454         mMediaPlayerIds.remove(wrapper.getPackageName());
    455         mMediaPlayers.remove(playerId);
    456         wrapper.cleanup();
    457     }
    458 
    459     void setActivePlayer(int playerId) {
    460         if (!mMediaPlayers.containsKey(playerId)) {
    461             e("Player doesn't exist in list(): " + playerId);
    462             return;
    463         }
    464 
    465         if (playerId == mActivePlayerId) {
    466             Log.w(TAG, getActivePlayer().getPackageName() + " is already the active player");
    467             return;
    468         }
    469 
    470         if (mActivePlayerId != NO_ACTIVE_PLAYER) getActivePlayer().unregisterCallback();
    471 
    472         mActivePlayerId = playerId;
    473         getActivePlayer().registerCallback(mMediaPlayerCallback);
    474         Log.i(TAG, "setActivePlayer(): setting player to " + getActivePlayer().getPackageName());
    475 
    476         // Ensure that metadata is synced on the new player
    477         if (!getActivePlayer().isMetadataSynced()) {
    478             Log.w(TAG, "setActivePlayer(): Metadata not synced on new player");
    479             return;
    480         }
    481 
    482         if (Utils.isPtsTestMode()) {
    483             sendFolderUpdate(true, true, false);
    484         }
    485 
    486         sendMediaUpdate(getActivePlayer().getCurrentMediaData());
    487     }
    488 
    489     // TODO (apanicke): Add logging for media key events in dumpsys
    490     void sendMediaKeyEvent(int key, boolean pushed) {
    491         d("sendMediaKeyEvent: key=" + key + " pushed=" + pushed);
    492         int action = pushed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
    493         KeyEvent event = new KeyEvent(action, AvrcpPassthrough.toKeyCode(key));
    494         mMediaSessionManager.dispatchMediaKeyEvent(event);
    495     }
    496 
    497     private void sendFolderUpdate(boolean availablePlayers, boolean addressedPlayers,
    498             boolean uids) {
    499         d("sendFolderUpdate");
    500         if (mCallback == null) {
    501             return;
    502         }
    503 
    504         mCallback.run(availablePlayers, addressedPlayers, uids);
    505     }
    506 
    507     private void sendMediaUpdate(MediaData data) {
    508         d("sendMediaUpdate");
    509         if (mCallback == null) {
    510             return;
    511         }
    512 
    513         // Always have items in the queue
    514         if (data.queue.size() == 0) {
    515             Log.i(TAG, "sendMediaUpdate: Creating a one item queue for a player with no queue");
    516             data.queue.add(data.metadata);
    517         }
    518 
    519         mCallback.run(data);
    520     }
    521 
    522     private final MediaSessionManager.OnActiveSessionsChangedListener
    523             mActiveSessionsChangedListener =
    524             new MediaSessionManager.OnActiveSessionsChangedListener() {
    525         @Override
    526         public void onActiveSessionsChanged(
    527                 List<android.media.session.MediaController> newControllers) {
    528             synchronized (MediaPlayerList.this) {
    529                 Log.v(TAG, "onActiveSessionsChanged: number of controllers: "
    530                         + newControllers.size());
    531                 if (newControllers.size() == 0) return;
    532 
    533                 // Apps are allowed to have multiple MediaControllers. If an app does have
    534                 // multiple controllers then newControllers contains them in highest
    535                 // priority order. Since we only want to keep the highest priority one,
    536                 // we keep track of which controllers we updated and skip over ones
    537                 // we've already looked at.
    538                 HashSet<String> addedPackages = new HashSet<String>();
    539 
    540                 for (int i = 0; i < newControllers.size(); i++) {
    541                     Log.d(TAG, "onActiveSessionsChanged: controller: "
    542                             + newControllers.get(i).getPackageName());
    543                     if (addedPackages.contains(newControllers.get(i).getPackageName())) {
    544                         continue;
    545                     }
    546 
    547                     addedPackages.add(newControllers.get(i).getPackageName());
    548                     addMediaPlayer(newControllers.get(i));
    549                 }
    550             }
    551         }
    552     };
    553 
    554     // TODO (apanicke): Write a test that tests uninstalling the active session
    555     private final BroadcastReceiver mPackageChangedBroadcastReceiver = new BroadcastReceiver() {
    556         @Override
    557         public void onReceive(Context context, Intent intent) {
    558             String action = intent.getAction();
    559             Log.v(TAG, "mPackageChangedBroadcastReceiver: action: " + action);
    560 
    561             if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
    562                     || action.equals(Intent.ACTION_PACKAGE_DATA_CLEARED)) {
    563                 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) return;
    564 
    565                 String packageName = intent.getData().getSchemeSpecificPart();
    566                 if (packageName != null && mMediaPlayerIds.containsKey(packageName)) {
    567                     removeMediaPlayer(mMediaPlayerIds.get(packageName));
    568                 }
    569             } else if (action.equals(Intent.ACTION_PACKAGE_ADDED)
    570                     || action.equals(Intent.ACTION_PACKAGE_CHANGED)) {
    571                 String packageName = intent.getData().getSchemeSpecificPart();
    572                 if (packageName != null) {
    573                     if (DEBUG) Log.d(TAG, "Name of package changed: " + packageName);
    574                     // TODO (apanicke): Handle either updating or adding the new package.
    575                     // Check if its browsable and send the UIDS changed to update the
    576                     // root folder
    577                 }
    578             }
    579         }
    580     };
    581 
    582     private final MediaPlayerWrapper.Callback mMediaPlayerCallback =
    583             new MediaPlayerWrapper.Callback() {
    584         @Override
    585         public void mediaUpdatedCallback(MediaData data) {
    586             if (data.metadata == null) {
    587                 Log.d(TAG, "mediaUpdatedCallback(): metadata is null");
    588                 return;
    589             }
    590 
    591             if (data.state == null) {
    592                 Log.w(TAG, "mediaUpdatedCallback(): Tried to update with null state");
    593                 return;
    594             }
    595 
    596             sendMediaUpdate(data);
    597         }
    598     };
    599 
    600     private final MediaSessionManager.Callback mButtonDispatchCallback =
    601             new MediaSessionManager.Callback() {
    602                 @Override
    603                 public void onMediaKeyEventDispatched(KeyEvent event, MediaSession.Token token) {
    604                     // TODO (apanicke): Add logging for these
    605                 }
    606 
    607                 @Override
    608                 public void onMediaKeyEventDispatched(KeyEvent event, ComponentName receiver) {
    609                     // TODO (apanicke): Add logging for these
    610                 }
    611 
    612                 @Override
    613                 public void onAddressedPlayerChanged(MediaSession.Token token) {
    614                     android.media.session.MediaController controller =
    615                             new android.media.session.MediaController(mContext, token);
    616 
    617                     if (!mMediaPlayerIds.containsKey(controller.getPackageName())) {
    618                         // Since we have a controller, we can try to to recover by adding the
    619                         // player and then setting it as active.
    620                         Log.w(TAG, "onAddressedPlayerChanged(Token): Addressed Player "
    621                                 + "changed to a player we didn't have a session for");
    622                         addMediaPlayer(controller);
    623                     }
    624 
    625                     Log.i(TAG, "onAddressedPlayerChanged: token=" + controller.getPackageName());
    626                     setActivePlayer(mMediaPlayerIds.get(controller.getPackageName()));
    627                 }
    628 
    629                 @Override
    630                 public void onAddressedPlayerChanged(ComponentName receiver) {
    631                     if (receiver == null) {
    632                         return;
    633                     }
    634 
    635                     if (!mMediaPlayerIds.containsKey(receiver.getPackageName())) {
    636                         e("onAddressedPlayerChanged(Component): Addressed Player "
    637                                 + "changed to a player we don't have a session for");
    638                         return;
    639                     }
    640 
    641                     Log.i(TAG, "onAddressedPlayerChanged: component=" + receiver.getPackageName());
    642                     setActivePlayer(mMediaPlayerIds.get(receiver.getPackageName()));
    643                 }
    644             };
    645 
    646 
    647     void dump(StringBuilder sb) {
    648         sb.append("List of MediaControllers: size=" + mMediaPlayers.size() + "\n");
    649         for (int id : mMediaPlayers.keySet()) {
    650             if (id == mActivePlayerId) {
    651                 sb.append("<Active> ");
    652             }
    653             MediaPlayerWrapper player = mMediaPlayers.get(id);
    654             sb.append("  Media Player " + id + ": " + player.getPackageName() + "\n");
    655             sb.append(player.toString().replaceAll("(?m)^", "  "));
    656             sb.append("\n");
    657         }
    658 
    659         sb.append("List of Browsers: size=" + mBrowsablePlayers.size() + "\n");
    660         for (BrowsedPlayerWrapper player : mBrowsablePlayers.values()) {
    661             sb.append(player.toString().replaceAll("(?m)^", "  "));
    662             sb.append("\n");
    663         }
    664         // TODO (apanicke): Add media key events
    665         // TODO (apanicke): Add last sent data
    666         // TODO (apanicke): Add addressed player history
    667     }
    668 
    669     private static void e(String message) {
    670         if (sTesting) {
    671             Log.wtfStack(TAG, message);
    672         } else {
    673             Log.e(TAG, message);
    674         }
    675     }
    676 
    677     private static void d(String message) {
    678         if (DEBUG) {
    679             Log.d(TAG, message);
    680         }
    681     }
    682 }
    683