AVRCP: Passthrough through MediaSessionService

Send passthrough keys through MediaSessionService instead of trying to
interpret them ourselves, and log who they get dispatched to.

Remove the hack around down fast-forward / rewind.

This should result in a lot less confusion about which app is addressed
when the user is sending a command (such as at car connection time).

Test: play / skip, switch app on phone, reboot, start from carkit, dumpsys logs
Bug: 33828042
Bug: 37476911

Change-Id: I8c8c40cb3792254a3720f64707e67fdcc940edaa
diff --git a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
index 37b0c45..0ceac6e 100644
--- a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
@@ -43,13 +43,9 @@
     private AvrcpMediaRspInterface mMediaInterface;
     private List<MediaSession.QueueItem> mNowPlayingList;
 
-    /* Now playing UID */
-    private static final byte[] NOW_PLAYING_UID = {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
-                                                  (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00};
-
-    public AddressedMediaPlayer(AvrcpMediaRspInterface _mediaInterface) {
+    public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface) {
         mNowPlayingList = null;
-        mMediaInterface = _mediaInterface;
+        mMediaInterface = mediaInterface;
     }
 
     void cleanup() {
@@ -82,7 +78,7 @@
 
         /* checking if item attributes has been asked for now playing item or
          * some other item with specific media id */
-        if (Arrays.equals(itemAttr.mUid, NOW_PLAYING_UID)) {
+        if (Arrays.equals(itemAttr.mUid, AvrcpConstants.TRACK_IS_SELECTED)) {
             if (DEBUG) Log.d(TAG, "getItemAttr: Remote requests for now playing contents:");
 
             if (mediaController == null) {
@@ -514,47 +510,6 @@
         }
     }
 
-    void handlePassthroughCmd(int id, int keyState, byte[] bdAddr,
-            MediaController mediaController) {
-
-        if (mediaController != null) {
-            MediaController.TransportControls mediaControllerCntrl =
-                mediaController.getTransportControls();
-            if (DEBUG) Log.v(TAG, "handlePassthroughCmd - id:" + id + " keyState:" + keyState);
-            if (keyState == AvrcpConstants.KEY_STATE_PRESS) {
-                switch (id) {
-                    case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
-                        mediaControllerCntrl.rewind();
-                        break;
-                    case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR:
-                        mediaControllerCntrl.fastForward();
-                        break;
-                    case BluetoothAvrcp.PASSTHROUGH_ID_PLAY:
-                        mediaControllerCntrl.play();
-                        break;
-                    case BluetoothAvrcp.PASSTHROUGH_ID_PAUSE:
-                        mediaControllerCntrl.pause();
-                        break;
-                    case BluetoothAvrcp.PASSTHROUGH_ID_STOP:
-                        mediaControllerCntrl.stop();
-                        break;
-                    case BluetoothAvrcp.PASSTHROUGH_ID_FORWARD:
-                        mediaControllerCntrl.skipToNext();
-                        break;
-                    case BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD:
-                        mediaControllerCntrl.skipToPrevious();
-                        break;
-                    default:
-                        Log.w(TAG, "unknown id:" + id + " keyState:" + keyState);
-                }
-            } else {
-                Log.i(TAG, "ignoring the release event for id:" + id + " keyState:" + keyState);
-            }
-        } else {
-            Log.e(TAG, "Unable to handlePassthroughCmd, mediaController is null!");
-        }
-    }
-
     private void printByteArray(String arrName, byte[] array) {
         StringBuilder byteArray = new StringBuilder(arrName + ": 0x");
 
@@ -563,5 +518,4 @@
         }
         Log.d(TAG, byteArray + "");
     }
-
 }
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
index 11808a1..497acc1 100644
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -157,17 +157,11 @@
     private static final int MSG_ABS_VOL_TIMEOUT = 17;
     private static final int MSG_FAST_FORWARD = 18;
     private static final int MSG_REWIND = 19;
-    private static final int MSG_CHANGE_PLAY_POS = 20;
-    private static final int MSG_SET_A2DP_AUDIO_STATE = 21;
-    private static final int MSG_ADDRESSED_PLAYER_CHANGED_RSP = 22;
-    private static final int MSG_AVAILABLE_PLAYERS_CHANGED_RSP = 23;
-    private static final int MSG_NOW_PLAYING_CHANGED_RSP = 24;
+    private static final int MSG_SET_A2DP_AUDIO_STATE = 20;
+    private static final int MSG_ADDRESSED_PLAYER_CHANGED_RSP = 21;
+    private static final int MSG_AVAILABLE_PLAYERS_CHANGED_RSP = 22;
+    private static final int MSG_NOW_PLAYING_CHANGED_RSP = 23;
 
-    private static final int BUTTON_TIMEOUT_TIME = 2000;
-    private static final int BASE_SKIP_AMOUNT = 2000;
-    private static final int SKIP_PERIOD = 400;
-    private static final int SKIP_DOUBLE_INTERVAL = 3000;
-    private static final long MAX_MULTIPLIER_VALUE = 128L;
     private static final int CMD_TIMEOUT_DELAY = 2000;
     private static final int MAX_ERROR_RETRY_TIMES = 6;
     private static final int AVRCP_MAX_VOL = 127;
@@ -176,7 +170,7 @@
     /* Communicates with MediaPlayer to fetch media content */
     private BrowsedMediaPlayer mBrowsedMediaPlayer;
 
-    /* Addressed player */
+    /* Addressed player handling */
     private AddressedMediaPlayer mAddressedMediaPlayer;
 
     /* List of Media player instances, useful for retrieving MediaPlayerList or MediaPlayerInfo */
@@ -192,6 +186,48 @@
     private final BroadcastReceiver mAvrcpReceiver = new AvrcpServiceBroadcastReceiver();
     private final BroadcastReceiver mBootReceiver = new AvrcpServiceBootReceiver();
 
+    /* Recording passthrough key dispatches */
+    static private final int PASSTHROUGH_LOG_MAX_SIZE = DEBUG ? 50 : 10;
+    private EvictingQueue<MediaKeyLog> mPassthroughLogs; // Passthorugh keys dispatched
+    private ArrayList<MediaKeyLog> mPassthroughPending; // Passthrough keys sent not dispatched yet
+    private int mPassthroughDispatched; // Number of keys dispatched
+
+    private class MediaKeyLog {
+        private long mTimeSent;
+        private long mTimeProcessed;
+        private String mPackage;
+        private KeyEvent mEvent;
+
+        public MediaKeyLog(long time, KeyEvent event) {
+            mEvent = event;
+            mTimeSent = time;
+        }
+
+        public boolean addDispatch(long time, KeyEvent event, String packageName) {
+            if (DEBUG)
+                Log.v(TAG, "addDispatch: Trying to match " + mEvent + " and record " + packageName);
+            if (mPackage != null) return false;
+            if (event.getAction() != mEvent.getAction()) return false;
+            if (event.getKeyCode() != mEvent.getKeyCode()) return false;
+            mPackage = packageName;
+            mTimeProcessed = time;
+            return true;
+        }
+
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append(android.text.format.DateFormat.format("MM-dd HH:mm:ss", mTimeSent));
+            sb.append(" " + mEvent.toString());
+            if (mPackage == null) {
+                sb.append(" (undispatched)");
+            } else {
+                sb.append(" to " + mPackage);
+                sb.append(" in " + (mTimeProcessed - mTimeSent) + "ms");
+            }
+            return sb.toString();
+        }
+    }
+
     static {
         classInitNative();
     }
@@ -264,9 +300,13 @@
         mAvrcpMediaRsp = new AvrcpMediaRsp();
         mMediaPlayerInfoList = new TreeMap<Integer, MediaPlayerInfo>();
         mBrowsePlayerInfoList = new ArrayList<BrowsePlayerInfo>();
+        mPassthroughDispatched = 0;
+        mPassthroughLogs = new EvictingQueue<MediaKeyLog>(PASSTHROUGH_LOG_MAX_SIZE);
+        mPassthroughPending = new ArrayList<MediaKeyLog>();
         if (mMediaSessionManager != null) {
             mMediaSessionManager.addOnActiveSessionsChangedListener(mActiveSessionListener, null,
                     mHandler);
+            mMediaSessionManager.setCallback(mButtonDispatchCallback, null);
         }
         mPackageManager = mContext.getApplicationContext().getPackageManager();
 
@@ -276,8 +316,7 @@
         /* initialize BrowseMananger which manages Browse commands and response */
         mAvrcpBrowseManager = new AvrcpBrowseManager(mContext, mAvrcpMediaRsp);
 
-        // Build the media players list
-        buildMediaPlayersList();
+        initMediaPlayersList();
 
         UserManager manager = UserManager.get(mContext);
         if (manager == null || manager.isUserUnlocked()) {
@@ -658,76 +697,6 @@
                 }
                 break;
 
-            case MSG_FAST_FORWARD:
-            case MSG_REWIND:
-                if (msg.what == MSG_FAST_FORWARD) {
-                    if ((mCurrentPlayState.getActions() &
-                            PlaybackState.ACTION_FAST_FORWARD) != 0) {
-                        int keyState = msg.arg1 == AvrcpConstants.KEY_STATE_PRESS ?
-                                KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
-                        KeyEvent keyEvent =
-                                new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
-                        mMediaController.dispatchMediaButtonEvent(keyEvent);
-                        break;
-                    }
-                } else if ((mCurrentPlayState.getActions() &
-                            PlaybackState.ACTION_REWIND) != 0) {
-                    int keyState = msg.arg1 == AvrcpConstants.KEY_STATE_PRESS ?
-                            KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
-                    KeyEvent keyEvent =
-                            new KeyEvent(keyState, KeyEvent.KEYCODE_MEDIA_REWIND);
-                    mMediaController.dispatchMediaButtonEvent(keyEvent);
-                    break;
-                }
-
-                int skipAmount;
-                int playStatus;
-                if (msg.what == MSG_FAST_FORWARD) {
-                    if (DEBUG) Log.v(TAG, "MSG_FAST_FORWARD");
-                    removeMessages(MSG_FAST_FORWARD);
-                    skipAmount = BASE_SKIP_AMOUNT;
-                    playStatus = PLAYSTATUS_FWD_SEEK;
-                } else {
-                    if (DEBUG) Log.v(TAG, "MSG_REWIND");
-                    removeMessages(MSG_REWIND);
-                    skipAmount = -BASE_SKIP_AMOUNT;
-                    playStatus = PLAYSTATUS_REV_SEEK;
-                }
-
-                if (hasMessages(MSG_CHANGE_PLAY_POS) &&
-                        (skipAmount != mSkipAmount)) {
-                    Log.w(TAG, "missing release button event:" + mSkipAmount);
-                }
-
-                if ((!hasMessages(MSG_CHANGE_PLAY_POS)) ||
-                        (skipAmount != mSkipAmount)) {
-                    mSkipStartTime = SystemClock.elapsedRealtime();
-                }
-
-                removeMessages(MSG_CHANGE_PLAY_POS);
-                if (msg.arg1 == AvrcpConstants.KEY_STATE_PRESS) {
-                    mSkipAmount = skipAmount;
-                    changePositionBy(mSkipAmount * getSkipMultiplier());
-                    Message posMsg = obtainMessage(MSG_CHANGE_PLAY_POS);
-                    posMsg.arg1 = 1;
-                    sendMessageDelayed(posMsg, SKIP_PERIOD);
-                }
-
-                registerNotificationRspPlayStatusNative(
-                        AvrcpConstants.NOTIFICATION_TYPE_CHANGED, playStatus);
-
-                break;
-
-            case MSG_CHANGE_PLAY_POS:
-                if (DEBUG) Log.v(TAG, "MSG_CHANGE_PLAY_POS:" + msg.arg1);
-                changePositionBy(mSkipAmount * getSkipMultiplier());
-                if (msg.arg1 * SKIP_PERIOD < BUTTON_TIMEOUT_TIME) {
-                    Message posMsg = obtainMessage(MSG_CHANGE_PLAY_POS);
-                    posMsg.arg1 = msg.arg1 + 1;
-                    sendMessageDelayed(posMsg, SKIP_PERIOD);
-                }
-                break;
-
             case MSG_SET_A2DP_AUDIO_STATE:
                 if (DEBUG) Log.v(TAG, "MSG_SET_A2DP_AUDIO_STATE:" + msg.arg1);
                 updateA2dpAudioState(msg.arg1);
@@ -808,11 +777,9 @@
 
             case MSG_NATIVE_REQ_PASS_THROUGH:
                 if (DEBUG)
-                    Log.v(TAG,
-                            "MSG_NATIVE_REQ_PASS_THROUGH: id=" + msg.arg1 + " state=" + msg.arg2);
-                // argument 1 is id, argument 2 is keyState, object is bdaddr
-                mAddressedMediaPlayer.handlePassthroughCmd(msg.arg1, msg.arg2, (byte[]) msg.obj,
-                        mMediaController);
+                    Log.v(TAG, "MSG_NATIVE_REQ_PASS_THROUGH: id=" + msg.arg1 + " st=" + msg.arg2);
+                // argument 1 is id, argument 2 is keyState
+                handlePassthroughCmd(msg.arg1, msg.arg2);
                 break;
 
             default:
@@ -1094,51 +1061,10 @@
     }
 
     private void handlePassthroughCmdRequestFromNative(byte[] address, int id, int keyState) {
-        switch (id) {
-            case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
-                rewind(address, keyState);
-                return;
-            case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR:
-                fastForward(address, keyState);
-                return;
-        }
-
-        /* For all other pass through commands other than fast forward and backward
-         * (like play, pause, next, previous, stop, etc.); sending to current addressed player.
-         */
-        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_PASS_THROUGH, id, keyState, address);
+        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_PASS_THROUGH, id, keyState);
         mHandler.sendMessage(msg);
     }
 
-    private void fastForward(byte[] address, int keyState) {
-        Message msg = mHandler.obtainMessage(MSG_FAST_FORWARD, keyState, 0);
-        Bundle data = new Bundle();
-        data.putByteArray("BdAddress" , address);
-        msg.setData(data);
-        mHandler.sendMessage(msg);
-    }
-
-    private void rewind(byte[] address, int keyState) {
-        Message msg = mHandler.obtainMessage(MSG_REWIND, keyState, 0);
-        Bundle data = new Bundle();
-        data.putByteArray("BdAddress" , address);
-        msg.setData(data);
-        mHandler.sendMessage(msg);
-    }
-
-    private void changePositionBy(long amount) {
-        long currentPosMs = getPlayPosition();
-        if (currentPosMs == -1L) return;
-        long newPosMs = Math.max(0L, currentPosMs + amount);
-        mMediaController.getTransportControls().seekTo(newPosMs);
-    }
-
-    private int getSkipMultiplier() {
-        long currentTime = SystemClock.elapsedRealtime();
-        long multi = (long) Math.pow(2, (currentTime - mSkipStartTime)/SKIP_DOUBLE_INTERVAL);
-        return (int) Math.min(MAX_MULTIPLIER_VALUE, multi);
-    }
-
     private void sendTrackChangedRsp() {
         MediaPlayerInfo info = getAddressedPlayerInfo();
         if (info != null && !info.isBrowseSupported()) {
@@ -1237,6 +1163,7 @@
         // and the old was valid.
         if (DEBUG) Log.d(TAG, "sendPlayPosNotificationRsp: (" + requested + ") "
                 + mPrevPosMs + " <=? " + playPositionMs + " <=? " + mNextPosMs);
+        if (DEBUG) Log.d(TAG, "sendPlayPosNotificationRsp: currentPlayState " + mCurrentPlayState);
         if (requested || ((mLastReportedPosition != playPositionMs) &&
                 (playPositionMs >= mNextPosMs) || (playPositionMs <= mPrevPosMs))) {
             if (!requested) mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
@@ -1616,78 +1543,75 @@
                 " , status: " + status);
     }
 
-    private MediaSessionManager.OnActiveSessionsChangedListener
-            mActiveSessionListener = new MediaSessionManager.OnActiveSessionsChangedListener() {
+    private MediaSessionManager.OnActiveSessionsChangedListener mActiveSessionListener =
+            new MediaSessionManager.OnActiveSessionsChangedListener() {
 
-        @Override
-        public void onActiveSessionsChanged(
-                List<android.media.session.MediaController> newControllers) {
-            boolean playersChanged = false;
+                @Override
+                public void onActiveSessionsChanged(
+                        List<android.media.session.MediaController> newControllers) {
+                    boolean playersChanged = false;
 
-            android.media.session.MediaController addressedController = null;
-            if (mMediaController != null)
-                addressedController = mMediaController.getWrappedInstance();
-
-            List<android.media.session.MediaController> currentControllers = getMediaControllers();
-
-            for (android.media.session.MediaController controller : currentControllers) {
-                if (!newControllers.contains(controller)) {
-                    removeMediaPlayerInfo(controller.getPackageName());
-                    if ((controller == addressedController)
-                            && (mCurrBrowsePlayerID == mCurrAddrPlayerID)) {
-                        mCurrBrowsePlayerID = 0;
+                    // Update the current players
+                    for (android.media.session.MediaController controller : newControllers) {
+                        addMediaPlayerController(controller);
+                        playersChanged = true;
                     }
-                    playersChanged = true;
-                }
-            }
 
-            for (android.media.session.MediaController controller : newControllers) {
-                if (!currentControllers.contains(controller)) {
-                    addMediaPlayerController(controller);
-                    playersChanged = true;
-                }
-            }
+                    List<android.media.session.MediaController> currentControllers =
+                            getMediaControllers();
+                    for (android.media.session.MediaController controller : currentControllers) {
+                        if (!newControllers.contains(controller)) {
+                            removeMediaPlayerInfo(controller.getPackageName());
+                            playersChanged = true;
+                        }
+                    }
 
-            if (playersChanged) sendAvailablePlayersChanged();
-
-            int newAddressedID = mCurrAddrPlayerID;
-            android.media.session.MediaController activeController = null;
-            if (newControllers.size() > 0) {
-                android.media.session.MediaController newAddressed = newControllers.get(0);
-                if (newAddressed != addressedController) {
-                    // Find our new player ID
-                    for (Map.Entry<Integer, MediaPlayerInfo> entry :
-                            mMediaPlayerInfoList.entrySet()) {
-                        MediaController c = entry.getValue().getMediaController();
-                        if (c != null && c.getWrappedInstance().equals(newAddressed)) {
-                            newAddressedID = entry.getKey();
-                            break;
+                    if (playersChanged) {
+                        mHandler.sendEmptyMessage(MSG_AVAILABLE_PLAYERS_CHANGED_RSP);
+                        if (newControllers.size() > 0 && (mAddressedMediaPlayer == null)) {
+                            if (DEBUG)
+                                Log.v(TAG,
+                                        "No addressed player but active sessions, taking first.");
+                            setAddressedMediaSessionPackage(newControllers.get(0).getPackageName());
                         }
                     }
                 }
-            } else {
-                // TODO(jamuraa): use media framework to handle addressed player
-                // No active sessions, so we don't have an addresed player.
-                newAddressedID = 0;
-            }
+            };
 
-            if (newAddressedID != mCurrAddrPlayerID) {
-                updateCurrentController(newAddressedID, mCurrBrowsePlayerID);
-                sendAddressedPlayerChanged(newAddressedID);
-            }
-        }
-
-        private void sendAddressedPlayerChanged(int newAddrPlayerID) {
-            if (DEBUG) Log.d(TAG, "sendAddressedPlayerChanged: new PlayerID=" + newAddrPlayerID);
-            mHandler.obtainMessage(MSG_ADDRESSED_PLAYER_CHANGED_RSP, newAddrPlayerID, 0)
-                    .sendToTarget();
-        }
-
-        private void sendAvailablePlayersChanged() {
-            if (DEBUG) Log.d(TAG, "sendAvailablePlayersChanged");
+    private void setAddressedMediaSessionPackage(String packageName) {
+        if (DEBUG) Log.v(TAG, "Setting addressed media session to " + packageName);
+        // Can't set no player, that handled by onActiveSessionsChanged
+        if (packageName == null) return;
+        // No change.
+        if (getPackageName(mCurrAddrPlayerID).equals(packageName)) return;
+        // If the player doesn't exist, we need to add it.
+        if (getMediaPlayerInfo(packageName) == null) {
+            addMediaPlayerPackage(packageName);
             mHandler.sendEmptyMessage(MSG_AVAILABLE_PLAYERS_CHANGED_RSP);
         }
-    };
+        for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
+            if (DEBUG)
+                Log.v(TAG, "Find player id: " + entry.getKey() + " is "
+                                + entry.getValue().getPackageName());
+            if (entry.getValue().getPackageName().equals(packageName)) {
+                int newAddressedID = entry.getKey();
+                updateCurrentController(newAddressedID, mCurrBrowsePlayerID);
+                mHandler.obtainMessage(MSG_ADDRESSED_PLAYER_CHANGED_RSP, newAddressedID, 0)
+                        .sendToTarget();
+                return;
+            }
+        }
+        // We shouldn't ever get here.
+        Log.e(TAG, "Player info for " + packageName + " doesn't exist!");
+    }
+
+    private void setActiveMediaSession(MediaSession.Token token) {
+        android.media.session.MediaController activeController =
+                new android.media.session.MediaController(mContext, token);
+        if (DEBUG) Log.v(TAG, "Set active media session " + activeController.getPackageName());
+        addMediaPlayerController(activeController);
+        setAddressedMediaSessionPackage(activeController.getPackageName());
+    }
 
     private boolean startBrowseService(byte[] bdaddr, String packageName) {
         boolean status = true;
@@ -1771,7 +1695,7 @@
                 }
                 // If there's no controller, the entry is already browsable-only.
             } else {
-                addMediaPlayerBrowsable(mCurrentPlayer.packageName);
+                addMediaPlayerPackage(mCurrentPlayer.packageName);
             }
             mPlayersChanged = true;
             mCurrentBrowser.disconnect();
@@ -1783,11 +1707,12 @@
             Log.d(TAG, "BrowsablePlayerListBuilder: " + mCurrentPlayer.packageName + " FAIL");
             connectNextPlayer();
         }
-
     }
 
     private void startBrowsedPlayer(int browseId) {
+        if (browseId < 0 || browseId >= mBrowsePlayerInfoList.size()) return;
         BrowsePlayerInfo player = mBrowsePlayerInfoList.get(browseId);
+
         Intent intent = new Intent();
         intent.setComponent(new ComponentName(player.packageName, player.serviceClass));
         Log.i(TAG, "Starting service:" + player.packageName + ", " + player.serviceClass);
@@ -1798,9 +1723,26 @@
         }
     }
 
-    /* initializing media player info list and prepare media player response object */
-    private void buildMediaPlayersList() {
-        initMediaPlayersInfoList();
+    /* Initializes list of media players identified from session manager active sessions */
+    private synchronized void initMediaPlayersList() {
+        // Clearing old browsable player's list
+        mMediaPlayerInfoList.clear();
+
+        if (mMediaSessionManager == null) {
+            if (DEBUG) Log.w(TAG, "initMediaPlayersList: no media session manager!");
+            return;
+        }
+
+        List<android.media.session.MediaController> controllers =
+                mMediaSessionManager.getActiveSessions(null);
+        if (DEBUG) Log.v(TAG, "initMediaPlayerInfoList: " + controllers.size() + " controllers");
+        /* Initializing all media players */
+        for (android.media.session.MediaController controller : controllers) {
+            addMediaPlayerController(controller);
+        }
+        if (controllers.size() > 0) {
+            mHandler.sendEmptyMessage(MSG_AVAILABLE_PLAYERS_CHANGED_RSP);
+        }
 
         if (mMediaPlayerInfoList.size() > 0) {
             // Set the first one as the Addressed Player
@@ -1820,30 +1762,35 @@
     }
 
     /** Add (or update) a player to the media player list without a controller */
-    private synchronized void addMediaPlayerBrowsable(String packageName) {
+    private synchronized boolean addMediaPlayerPackage(String packageName) {
         MediaPlayerInfo info = new MediaPlayerInfo(null, AvrcpConstants.PLAYER_TYPE_AUDIO,
                 AvrcpConstants.PLAYER_SUBTYPE_NONE, getPlayStateBytes(null),
                 getFeatureBitMask(packageName), packageName, getAppLabel(packageName));
-        addMediaPlayerInfo(info);
+        return addMediaPlayerInfo(info);
     }
 
     /** Add (or update) a player to the media player list given an active controller */
-    private synchronized void addMediaPlayerController(
+    private synchronized boolean addMediaPlayerController(
             android.media.session.MediaController controller) {
         String packageName = controller.getPackageName();
         MediaPlayerInfo info = new MediaPlayerInfo(MediaController.wrap(controller),
                 AvrcpConstants.PLAYER_TYPE_AUDIO, AvrcpConstants.PLAYER_SUBTYPE_NONE,
                 getPlayStateBytes(controller.getPlaybackState()), getFeatureBitMask(packageName),
                 controller.getPackageName(), getAppLabel(packageName));
-        addMediaPlayerInfo(info);
+        return addMediaPlayerInfo(info);
     }
 
-    private synchronized void addMediaPlayerInfo(MediaPlayerInfo info) {
-        if (DEBUG) Log.d(TAG, info.toString());
+    /** Add or update a player to the media player list given the MediaPlayerInfo object.
+     *  @return true if an item was updated, false if it was added instead
+     */
+    private synchronized boolean addMediaPlayerInfo(MediaPlayerInfo info) {
+        if (DEBUG) Log.d(TAG, "add " + info.toString());
         int updateId = -1;
+        boolean updated = false;
         for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
             if (info.getPackageName().equals(entry.getValue().getPackageName())) {
                 updateId = entry.getKey();
+                updated = true;
                 break;
             }
         }
@@ -1851,8 +1798,11 @@
             // New player
             mLastUsedPlayerID++;
             updateId = mLastUsedPlayerID;
+        } else if (updateId == mCurrAddrPlayerID) {
+            updateCurrentController(mCurrAddrPlayerID, mCurrBrowsePlayerID);
         }
         mMediaPlayerInfoList.put(updateId, info);
+        return updated;
     }
 
     /** Remove all players related to |packageName| from the media player info list */
@@ -1872,27 +1822,6 @@
     }
 
     /*
-     * Builds list of media players identified from session manager by getting the active sessions
-     */
-    private synchronized void initMediaPlayersInfoList() {
-        // Clearing old browsable player's list
-        mMediaPlayerInfoList.clear();
-
-        if (mMediaSessionManager == null) {
-            if (DEBUG) Log.w(TAG, "initMediaPlayersInfoList: no media session manager!");
-            return;
-        }
-
-        List<android.media.session.MediaController> controllers =
-                mMediaSessionManager.getActiveSessions(null);
-        if (DEBUG) Log.v(TAG, "initMediaPlayerInfoList: " + controllers.size() + " controllers");
-        /* Initializing all media players */
-        for (android.media.session.MediaController controller : controllers) {
-            addMediaPlayerController(controller);
-        }
-    }
-
-    /*
      * utility function to get the playback state of any media player through
      * media controller APIs.
      */
@@ -2161,33 +2090,26 @@
         int status = AvrcpConstants.RSP_NO_ERROR;
 
         /* Browsed player is already set */
-        switch (folderObj.mScope) {
-            case AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM:
-                if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) != null) {
-                    mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).getFolderItemsVFS(folderObj);
-                } else {
-                    /* No browsed player set. Browsed player should be set by CT before performing browse.*/
-                    Log.e(TAG, "handleGetFolderItemBrowseResponse: mBrowsedMediaPlayer is null");
-                    status = AvrcpConstants.RSP_INTERNAL_ERR;
-                }
-                break;
-
-            case AvrcpConstants.BTRC_SCOPE_NOW_PLAYING:
-                mAddressedMediaPlayer.getFolderItemsNowPlaying(bdaddr, folderObj, mMediaController);
-                break;
-
-            default:
-                /* invalid scope */
-                Log.e(TAG, "handleGetFolderItemBrowseResponse:invalid scope");
-                status = AvrcpConstants.RSP_INV_SCOPE;
+        if (folderObj.mScope == AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
+            if (mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr) == null) {
+                Log.e(TAG, "handleGetFolderItemBrowseResponse: no browsed player set for "
+                                + Utils.getAddressStringFromByte(bdaddr));
+                getFolderItemsRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, (short) 0,
+                        (byte) 0x00, 0, null, null, null, null, null, null, null, null);
+                return;
+            }
+            mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).getFolderItemsVFS(folderObj);
+            return;
+        }
+        if (folderObj.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) {
+            mAddressedMediaPlayer.getFolderItemsNowPlaying(bdaddr, folderObj, mMediaController);
+            return;
         }
 
-
-        if (status != AvrcpConstants.RSP_NO_ERROR) {
-            getFolderItemsRspNative(bdaddr, status, (short) 0, (byte) 0x00, 0, null, null, null,
-                null, null, null, null, null);
-        }
-
+        /* invalid scope */
+        Log.e(TAG, "handleGetFolderItemBrowseResponse: unknown scope " + folderObj.mScope);
+        getFolderItemsRspNative(bdaddr, AvrcpConstants.RSP_INV_SCOPE, (short) 0, (byte) 0x00, 0,
+                null, null, null, null, null, null, null, null);
     }
 
     /* utility function to update the global values of current Addressed and browsed player */
@@ -2353,7 +2275,17 @@
 
         ProfileService.println(sb, "\nMedia Players:");
         for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
-            ProfileService.println(sb, " #" + entry.getKey() + ": " + entry.getValue());
+            int key = entry.getKey();
+            ProfileService.println(sb, ((mCurrAddrPlayerID == key) ? " *#" : "  #") + entry.getKey()
+                            + ": " + entry.getValue());
+        }
+
+        ProfileService.println(sb, "Passthrough operations: ");
+        for (MediaKeyLog log : mPassthroughLogs) {
+            ProfileService.println(sb, "  " + log);
+        }
+        for (MediaKeyLog log : mPassthroughPending) {
+            ProfileService.println(sb, "  " + log);
         }
     }
 
@@ -2386,7 +2318,7 @@
         public BrowsedMediaPlayer getBrowsedMediaPlayer(byte[] bdaddr) {
             BrowsedMediaPlayer mediaPlayer;
             String bdaddrStr = new String(bdaddr);
-            if(connList.containsKey(bdaddrStr)){
+            if (connList.containsKey(bdaddrStr)) {
                 mediaPlayer = connList.get(bdaddrStr);
             } else {
                 mediaPlayer = new BrowsedMediaPlayer(bdaddr, mContext, mMediaInterface);
@@ -2398,7 +2330,7 @@
         // clears the details pertaining to passed bdaddres
         public boolean clearBrowsedMediaPlayer(byte[] bdaddr) {
             String bdaddrStr = new String(bdaddr);
-            if(connList.containsKey(bdaddrStr)) {
+            if (connList.containsKey(bdaddrStr)) {
                 connList.remove(bdaddrStr);
                 return true;
             }
@@ -2502,7 +2434,7 @@
             }
         }
 
-        public void addrPlayerChangedRsp(byte[] address, int type, int playerId, int uidCounter) {
+        public void addrPlayerChangedRsp(int type, int playerId, int uidCounter) {
             if (!registerNotificationRspAddrPlayerChangedNative(type, playerId, sUIDCounter)) {
                 Log.e(TAG, "registerNotificationRspAddrPlayerChangedNative failed!");
             }
@@ -2538,6 +2470,188 @@
         return mAvrcpBrowseManager;
     }
 
+    /* PASSTHROUGH COMMAND MANAGEMENT */
+
+    void handlePassthroughCmd(int op, int state) {
+        int code = avrcpPassthroughToKeyCode(op);
+        if (code == KeyEvent.KEYCODE_UNKNOWN) {
+            Log.w(TAG, "Ignoring passthrough of unknown key " + op + " state " + state);
+            return;
+        }
+        int action = KeyEvent.ACTION_DOWN;
+        if (state == AvrcpConstants.KEY_STATE_RELEASE) action = KeyEvent.ACTION_UP;
+        KeyEvent event = new KeyEvent(action, code);
+        if (!KeyEvent.isMediaKey(code)) {
+            Log.w(TAG, "Passthrough non-media key " + op + " (code " + code + ") state " + state);
+        }
+        mMediaSessionManager.dispatchMediaKeyEvent(event);
+        addKeyPending(event);
+    }
+
+    private int avrcpPassthroughToKeyCode(int operation) {
+        switch (operation) {
+            case BluetoothAvrcp.PASSTHROUGH_ID_UP:
+                return KeyEvent.KEYCODE_DPAD_UP;
+            case BluetoothAvrcp.PASSTHROUGH_ID_DOWN:
+                return KeyEvent.KEYCODE_DPAD_DOWN;
+            case BluetoothAvrcp.PASSTHROUGH_ID_LEFT:
+                return KeyEvent.KEYCODE_DPAD_LEFT;
+            case BluetoothAvrcp.PASSTHROUGH_ID_RIGHT:
+                return KeyEvent.KEYCODE_DPAD_RIGHT;
+            case BluetoothAvrcp.PASSTHROUGH_ID_RIGHT_UP:
+                return KeyEvent.KEYCODE_DPAD_UP_RIGHT;
+            case BluetoothAvrcp.PASSTHROUGH_ID_RIGHT_DOWN:
+                return KeyEvent.KEYCODE_DPAD_DOWN_RIGHT;
+            case BluetoothAvrcp.PASSTHROUGH_ID_LEFT_UP:
+                return KeyEvent.KEYCODE_DPAD_UP_LEFT;
+            case BluetoothAvrcp.PASSTHROUGH_ID_LEFT_DOWN:
+                return KeyEvent.KEYCODE_DPAD_DOWN_LEFT;
+            case BluetoothAvrcp.PASSTHROUGH_ID_0:
+                return KeyEvent.KEYCODE_NUMPAD_0;
+            case BluetoothAvrcp.PASSTHROUGH_ID_1:
+                return KeyEvent.KEYCODE_NUMPAD_1;
+            case BluetoothAvrcp.PASSTHROUGH_ID_2:
+                return KeyEvent.KEYCODE_NUMPAD_2;
+            case BluetoothAvrcp.PASSTHROUGH_ID_3:
+                return KeyEvent.KEYCODE_NUMPAD_3;
+            case BluetoothAvrcp.PASSTHROUGH_ID_4:
+                return KeyEvent.KEYCODE_NUMPAD_4;
+            case BluetoothAvrcp.PASSTHROUGH_ID_5:
+                return KeyEvent.KEYCODE_NUMPAD_5;
+            case BluetoothAvrcp.PASSTHROUGH_ID_6:
+                return KeyEvent.KEYCODE_NUMPAD_6;
+            case BluetoothAvrcp.PASSTHROUGH_ID_7:
+                return KeyEvent.KEYCODE_NUMPAD_7;
+            case BluetoothAvrcp.PASSTHROUGH_ID_8:
+                return KeyEvent.KEYCODE_NUMPAD_8;
+            case BluetoothAvrcp.PASSTHROUGH_ID_9:
+                return KeyEvent.KEYCODE_NUMPAD_9;
+            case BluetoothAvrcp.PASSTHROUGH_ID_DOT:
+                return KeyEvent.KEYCODE_NUMPAD_DOT;
+            case BluetoothAvrcp.PASSTHROUGH_ID_ENTER:
+                return KeyEvent.KEYCODE_NUMPAD_ENTER;
+            case BluetoothAvrcp.PASSTHROUGH_ID_CLEAR:
+                return KeyEvent.KEYCODE_CLEAR;
+            case BluetoothAvrcp.PASSTHROUGH_ID_CHAN_UP:
+                return KeyEvent.KEYCODE_CHANNEL_UP;
+            case BluetoothAvrcp.PASSTHROUGH_ID_CHAN_DOWN:
+                return KeyEvent.KEYCODE_CHANNEL_DOWN;
+            case BluetoothAvrcp.PASSTHROUGH_ID_PREV_CHAN:
+                return KeyEvent.KEYCODE_LAST_CHANNEL;
+            case BluetoothAvrcp.PASSTHROUGH_ID_INPUT_SEL:
+                return KeyEvent.KEYCODE_TV_INPUT;
+            case BluetoothAvrcp.PASSTHROUGH_ID_DISP_INFO:
+                return KeyEvent.KEYCODE_INFO;
+            case BluetoothAvrcp.PASSTHROUGH_ID_HELP:
+                return KeyEvent.KEYCODE_HELP;
+            case BluetoothAvrcp.PASSTHROUGH_ID_PAGE_UP:
+                return KeyEvent.KEYCODE_PAGE_UP;
+            case BluetoothAvrcp.PASSTHROUGH_ID_PAGE_DOWN:
+                return KeyEvent.KEYCODE_PAGE_DOWN;
+            case BluetoothAvrcp.PASSTHROUGH_ID_POWER:
+                return KeyEvent.KEYCODE_POWER;
+            case BluetoothAvrcp.PASSTHROUGH_ID_VOL_UP:
+                return KeyEvent.KEYCODE_VOLUME_UP;
+            case BluetoothAvrcp.PASSTHROUGH_ID_VOL_DOWN:
+                return KeyEvent.KEYCODE_VOLUME_DOWN;
+            case BluetoothAvrcp.PASSTHROUGH_ID_MUTE:
+                return KeyEvent.KEYCODE_MUTE;
+            case BluetoothAvrcp.PASSTHROUGH_ID_PLAY:
+                return KeyEvent.KEYCODE_MEDIA_PLAY;
+            case BluetoothAvrcp.PASSTHROUGH_ID_STOP:
+                return KeyEvent.KEYCODE_MEDIA_STOP;
+            case BluetoothAvrcp.PASSTHROUGH_ID_PAUSE:
+                return KeyEvent.KEYCODE_MEDIA_PAUSE;
+            case BluetoothAvrcp.PASSTHROUGH_ID_RECORD:
+                return KeyEvent.KEYCODE_MEDIA_RECORD;
+            case BluetoothAvrcp.PASSTHROUGH_ID_REWIND:
+                return KeyEvent.KEYCODE_MEDIA_REWIND;
+            case BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR:
+                return KeyEvent.KEYCODE_MEDIA_FAST_FORWARD;
+            case BluetoothAvrcp.PASSTHROUGH_ID_EJECT:
+                return KeyEvent.KEYCODE_MEDIA_EJECT;
+            case BluetoothAvrcp.PASSTHROUGH_ID_FORWARD:
+                return KeyEvent.KEYCODE_MEDIA_NEXT;
+            case BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD:
+                return KeyEvent.KEYCODE_MEDIA_PREVIOUS;
+            case BluetoothAvrcp.PASSTHROUGH_ID_F1:
+                return KeyEvent.KEYCODE_F1;
+            case BluetoothAvrcp.PASSTHROUGH_ID_F2:
+                return KeyEvent.KEYCODE_F2;
+            case BluetoothAvrcp.PASSTHROUGH_ID_F3:
+                return KeyEvent.KEYCODE_F3;
+            case BluetoothAvrcp.PASSTHROUGH_ID_F4:
+                return KeyEvent.KEYCODE_F4;
+            case BluetoothAvrcp.PASSTHROUGH_ID_F5:
+                return KeyEvent.KEYCODE_F5;
+            // Fallthrough for all unknown key mappings
+            case BluetoothAvrcp.PASSTHROUGH_ID_SELECT:
+            case BluetoothAvrcp.PASSTHROUGH_ID_ROOT_MENU:
+            case BluetoothAvrcp.PASSTHROUGH_ID_SETUP_MENU:
+            case BluetoothAvrcp.PASSTHROUGH_ID_CONT_MENU:
+            case BluetoothAvrcp.PASSTHROUGH_ID_FAV_MENU:
+            case BluetoothAvrcp.PASSTHROUGH_ID_EXIT:
+            case BluetoothAvrcp.PASSTHROUGH_ID_SOUND_SEL:
+            case BluetoothAvrcp.PASSTHROUGH_ID_ANGLE:
+            case BluetoothAvrcp.PASSTHROUGH_ID_SUBPICT:
+            case BluetoothAvrcp.PASSTHROUGH_ID_VENDOR:
+            default:
+                return KeyEvent.KEYCODE_UNKNOWN;
+        }
+    }
+
+    private void addKeyPending(KeyEvent event) {
+        synchronized (mPassthroughPending) {
+            mPassthroughPending.add(new MediaKeyLog(System.currentTimeMillis(), event));
+        }
+    }
+
+    private void recordKeyDispatched(KeyEvent event, String packageName) {
+        long time = System.currentTimeMillis();
+        Log.v(TAG, "recordKeyDispatched: " + event + " dispatched to " + packageName);
+        synchronized (mPassthroughPending) {
+            Iterator<MediaKeyLog> pending = mPassthroughPending.iterator();
+            while (pending.hasNext()) {
+                MediaKeyLog log = pending.next();
+                if (log.addDispatch(time, event, packageName)) {
+                    mPassthroughDispatched++;
+                    mPassthroughLogs.add(log);
+                    pending.remove();
+                    return;
+                }
+            }
+        }
+    }
+
+    private final MediaSessionManager.Callback mButtonDispatchCallback =
+            new MediaSessionManager.Callback() {
+                @Override
+                public void onMediaKeyEventDispatched(KeyEvent event, MediaSession.Token token) {
+                    // Get the package name
+                    android.media.session.MediaController controller =
+                            new android.media.session.MediaController(mContext, token);
+                    String targetPackage = controller.getPackageName();
+                    recordKeyDispatched(event, targetPackage);
+                }
+
+                @Override
+                public void onMediaKeyEventDispatched(KeyEvent event, ComponentName receiver) {
+                    recordKeyDispatched(event, receiver.getPackageName());
+                }
+
+                @Override
+                public void onAddressedPlayerChanged(MediaSession.Token token) {
+                    setActiveMediaSession(token);
+                }
+
+                @Override
+                public void onAddressedPlayerChanged(ComponentName receiver) {
+                    // We only get this if there isn't an active media session.
+                    // We can still get a passthrough.
+                    setAddressedMediaSessionPackage(receiver.getPackageName());
+                }
+            };
+
     // Do not modify without updating the HAL bt_rc.h files.
 
     // match up with btrc_play_status_t enum of bt_rc.h
diff --git a/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java b/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
index 93bea9d..0311280 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
@@ -20,6 +20,8 @@
 
 import java.util.List;
 import java.util.Arrays;
+import java.util.ArrayDeque;
+import java.util.Collection;
 
 /*************************************************************************************************
  * Helper classes used for callback/response of browsing commands:-
@@ -341,3 +343,65 @@
         mAttrValues = null; /* array of attr values */
     }
 }
+
+/** A queue that evicts the first element when you add an element to the end when it reaches a
+ * maximum size.
+ * This is useful for keeping a FIFO queue of items where the items drop off the front, i.e. a log
+ * with a maximum size.
+ */
+class EvictingQueue<E> extends ArrayDeque<E> {
+    private int mMaxSize;
+
+    public EvictingQueue(int maxSize) {
+        super();
+        mMaxSize = maxSize;
+    }
+
+    public EvictingQueue(int maxSize, int initialElements) {
+        super(initialElements);
+        mMaxSize = maxSize;
+    }
+
+    public EvictingQueue(int maxSize, Collection<? extends E> c) {
+        super(c);
+        mMaxSize = maxSize;
+    }
+
+    @Override
+    public boolean add(E e) {
+        if (super.size() == mMaxSize) {
+            super.remove();
+        }
+        return super.add(e);
+    }
+
+    @Override
+    public void addFirst(E e) {
+        if (super.size() == mMaxSize) return;
+        super.addFirst(e);
+    }
+
+    @Override
+    public void addLast(E e) {
+        add(e);
+    }
+
+    @Override
+    public boolean offer(E e) {
+        return offerLast(e);
+    }
+
+    @Override
+    public boolean offerFirst(E e) {
+        if (super.size() == mMaxSize) return false;
+        return super.offerFirst(e);
+    }
+
+    @Override
+    public boolean offerLast(E e) {
+        if (super.size() == mMaxSize) {
+            super.remove();
+        }
+        return super.offerLast(e);
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java b/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
index 9c40a96..71493b6 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
@@ -41,7 +41,7 @@
     public void getTotalNumOfItemsRsp(byte[] address, int rspStatus, int uidCounter,
         int numItems);
 
-    public void addrPlayerChangedRsp(byte[] address, int type, int playerId, int uidCounter);
+    public void addrPlayerChangedRsp(int type, int playerId, int uidCounter);
 
     public void avalPlayerChangedRsp(byte[] address, int type);
 
diff --git a/tests/src/com/android/bluetooth/avrcp/AddressedMediaPlayerTest.java b/tests/src/com/android/bluetooth/avrcp/AddressedMediaPlayerTest.java
deleted file mode 100644
index 81773a4..0000000
--- a/tests/src/com/android/bluetooth/avrcp/AddressedMediaPlayerTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.android.bluetooth.avrcp;
-
-import android.bluetooth.BluetoothAvrcp;
-import android.media.session.MediaSession;
-import android.media.session.MediaSession.QueueItem;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.os.Bundle;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Arrays;
-import java.util.ArrayList;
-
-import static org.mockito.Mockito.*;
-
-public class AddressedMediaPlayerTest extends AndroidTestCase {
-
-    public void testHandlePassthroughCmd() {
-        MediaController mockController = mock(com.android.bluetooth.avrcp.MediaController.class);
-        MediaController.TransportControls mockTransport = mock(MediaController.TransportControls.class);
-        AvrcpMediaRspInterface mockRspInterface = mock(AvrcpMediaRspInterface.class);
-
-        when(mockController.getTransportControls()).thenReturn(mockTransport);
-        AddressedMediaPlayer myMediaPlayer = new AddressedMediaPlayer(mockRspInterface);
-
-
-        // Test rewind
-        myMediaPlayer.handlePassthroughCmd(BluetoothAvrcp.PASSTHROUGH_ID_REWIND,
-                                           AvrcpConstants.KEY_STATE_PRESS,
-                                           null,
-                                           mockController);
-        verify(mockTransport).rewind();
-
-        // Test fast forward
-        myMediaPlayer.handlePassthroughCmd(BluetoothAvrcp.PASSTHROUGH_ID_FAST_FOR,
-                                           AvrcpConstants.KEY_STATE_PRESS,
-                                           null,
-                                           mockController);
-        verify(mockTransport).fastForward();
-
-        // Test play
-        myMediaPlayer.handlePassthroughCmd(BluetoothAvrcp.PASSTHROUGH_ID_PLAY,
-                                           AvrcpConstants.KEY_STATE_PRESS,
-                                           null,
-                                           mockController);
-        verify(mockTransport).play();
-
-        // Test pause
-        myMediaPlayer.handlePassthroughCmd(BluetoothAvrcp.PASSTHROUGH_ID_PAUSE,
-                                           AvrcpConstants.KEY_STATE_PRESS,
-                                           null,
-                                           mockController);
-        verify(mockTransport).pause();
-
-        // Test stop
-        myMediaPlayer.handlePassthroughCmd(BluetoothAvrcp.PASSTHROUGH_ID_STOP,
-                                           AvrcpConstants.KEY_STATE_PRESS,
-                                           null,
-                                           mockController);
-        verify(mockTransport).stop();
-
-        // Test skip to next
-        myMediaPlayer.handlePassthroughCmd(BluetoothAvrcp.PASSTHROUGH_ID_FORWARD,
-                                           AvrcpConstants.KEY_STATE_PRESS,
-                                           null,
-                                           mockController);
-        verify(mockTransport).skipToNext();
-
-        // Test skip backwards
-        myMediaPlayer.handlePassthroughCmd(BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD,
-                                           AvrcpConstants.KEY_STATE_PRESS,
-                                           null,
-                                           mockController);
-        verify(mockTransport).skipToPrevious();
-
-        // Test invalid key
-        myMediaPlayer.handlePassthroughCmd(0xFF,
-                                           AvrcpConstants.KEY_STATE_PRESS,
-                                           null,
-                                           mockController);
-        verifyNoMoreInteractions(mockTransport);
-
-        // Test key release
-        myMediaPlayer.handlePassthroughCmd(BluetoothAvrcp.PASSTHROUGH_ID_PLAY,
-                                           AvrcpConstants.KEY_STATE_RELEASE,
-                                           null,
-                                           mockController);
-        verifyNoMoreInteractions(mockTransport);
-    }
-}