Restore the selected route when an app starts to make sound

Limitation: When two apps make sound simultaneously while one uses a BT
route and the other uses the default route, switching activities between
the two different apps will not change the BT A2DP status until one of
the apps changes its playback status to inactive.

Bug: 32958908, Bug: 35567695, Bug: 33186346
Test: Passed MediaRouter/MediaSession test, and manually varified that
      the b/33186346 is fixed.
Change-Id: I1da97dcb74fa2095425b311ad62cdd00f8118cea
(cherry picked from commit b3658569e9a87afe1b3168837cc282235fd4d1f8)
diff --git a/media/java/android/media/IMediaRouterClient.aidl b/media/java/android/media/IMediaRouterClient.aidl
index 9640dcb..08344f1 100644
--- a/media/java/android/media/IMediaRouterClient.aidl
+++ b/media/java/android/media/IMediaRouterClient.aidl
@@ -21,4 +21,5 @@
  */
 oneway interface IMediaRouterClient {
     void onStateChanged();
+    void onRestoreRoute();
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index f8f5fdf..3308fc9 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -27,6 +27,7 @@
     void unregisterClient(IMediaRouterClient client);
 
     MediaRouterClientState getState(IMediaRouterClient client);
+    boolean isPlaybackActive(IMediaRouterClient client);
 
     void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan);
     void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit);
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 8caba81..80a0315 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -88,6 +88,7 @@
         RouteInfo mBluetoothA2dpRoute;
 
         RouteInfo mSelectedRoute;
+        RouteInfo mSystemAudioRoute;
 
         final boolean mCanConfigureWifiDisplays;
         boolean mActivelyScanningWifiDisplays;
@@ -149,6 +150,7 @@
             }
 
             addRouteStatic(mDefaultAudioVideo);
+            mSystemAudioRoute = mDefaultAudioVideo;
 
             // This will select the active wifi display route if there is one.
             updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
@@ -197,8 +199,8 @@
                 } else {
                     name = com.android.internal.R.string.default_audio_route_name;
                 }
-                sStatic.mDefaultAudioVideo.mNameResId = name;
-                dispatchRouteChanged(sStatic.mDefaultAudioVideo);
+                mDefaultAudioVideo.mNameResId = name;
+                dispatchRouteChanged(mDefaultAudioVideo);
                 updated = true;
             }
 
@@ -207,22 +209,28 @@
             if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
                 mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
                 if (mCurAudioRoutesInfo.bluetoothName != null) {
-                    if (sStatic.mBluetoothA2dpRoute == null) {
-                        final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
+                    if (mBluetoothA2dpRoute == null) {
+                        // BT connected
+                        final RouteInfo info = new RouteInfo(mSystemCategory);
                         info.mName = mCurAudioRoutesInfo.bluetoothName;
-                        info.mDescription = sStatic.mResources.getText(
+                        info.mDescription = mResources.getText(
                                 com.android.internal.R.string.bluetooth_a2dp_audio_route_name);
                         info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
                         info.mDeviceType = RouteInfo.DEVICE_TYPE_BLUETOOTH;
-                        sStatic.mBluetoothA2dpRoute = info;
-                        addRouteStatic(sStatic.mBluetoothA2dpRoute);
+                        mBluetoothA2dpRoute = info;
+                        addRouteStatic(mBluetoothA2dpRoute);
+                        mSystemAudioRoute = mBluetoothA2dpRoute;
+                        selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false);
                     } else {
-                        sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.bluetoothName;
-                        dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
+                        mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.bluetoothName;
+                        dispatchRouteChanged(mBluetoothA2dpRoute);
                     }
-                } else if (sStatic.mBluetoothA2dpRoute != null) {
-                    removeRouteStatic(sStatic.mBluetoothA2dpRoute);
-                    sStatic.mBluetoothA2dpRoute = null;
+                } else if (mBluetoothA2dpRoute != null) {
+                    // BT disconnected
+                    removeRouteStatic(mBluetoothA2dpRoute);
+                    mBluetoothA2dpRoute = null;
+                    mSystemAudioRoute = mDefaultAudioVideo;
+                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mSystemAudioRoute, false);
                 }
                 updated = true;
             }
@@ -230,11 +238,13 @@
             if (mBluetoothA2dpRoute != null) {
                 final boolean a2dpEnabled = isBluetoothA2dpOn();
                 if (mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
-                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
+                    // A2DP off
+                    mSystemAudioRoute = mDefaultAudioVideo;
                     updated = true;
                 } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
                         a2dpEnabled) {
-                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false);
+                    // A2DP on or BT connected
+                    mSystemAudioRoute = mBluetoothA2dpRoute;
                     updated = true;
                 }
             }
@@ -471,7 +481,7 @@
         }
 
         RouteInfo makeGlobalRoute(MediaRouterClientState.RouteInfo globalRoute) {
-            RouteInfo route = new RouteInfo(sStatic.mSystemCategory);
+            RouteInfo route = new RouteInfo(mSystemCategory);
             route.mGlobalRouteId = globalRoute.id;
             route.mName = globalRoute.name;
             route.mDescription = globalRoute.description;
@@ -567,6 +577,17 @@
             return null;
         }
 
+        boolean isPlaybackActive() {
+            if (mClient != null) {
+                try {
+                    return mMediaRouterService.isPlaybackActive(mClient);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to retrieve playback active state.", ex);
+                }
+            }
+            return false;
+        }
+
         final class Client extends IMediaRouterClient.Stub {
             @Override
             public void onStateChanged() {
@@ -579,6 +600,19 @@
                     }
                 });
             }
+
+            @Override
+            public void onRestoreRoute() {
+                if ((mSelectedRoute != mDefaultAudioVideo && mSelectedRoute != mBluetoothA2dpRoute)
+                        || mSelectedRoute == mSystemAudioRoute) {
+                    return;
+                }
+                try {
+                    sStatic.mAudioService.setBluetoothA2dpOn(mSelectedRoute == mBluetoothA2dpRoute);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error changing Bluetooth A2DP state", e);
+                }
+            }
         }
     }
 
@@ -909,7 +943,12 @@
         Log.v(TAG, "Selecting route: " + route);
         assert(route != null);
         final RouteInfo oldRoute = sStatic.mSelectedRoute;
-        if (oldRoute == route) return;
+        boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo
+                || oldRoute == sStatic.mBluetoothA2dpRoute);
+        if (oldRoute == route
+                && (!wasDefaultOrBluetoothRoute || oldRoute == sStatic.mSystemAudioRoute)) {
+            return;
+        }
         if (!route.matchesTypes(types)) {
             Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
                     typesToString(route.getSupportedTypes()) + " into route types " +
@@ -918,8 +957,8 @@
         }
 
         final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
-        if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
-                (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
+        if (sStatic.isPlaybackActive() && btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0
+                && (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
             try {
                 sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
                 // TODO: Remove the following logging when no longer needed.
diff --git a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java b/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
index c6dc11c..f6f7676 100644
--- a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
@@ -31,17 +31,22 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
- * Monitors changes in audio playback and notify the newly started audio playback through the
- * {@link OnAudioPlaybackStartedListener}.
+ * Monitors changes in audio playback, and notify the newly started audio playback through the
+ * {@link OnAudioPlaybackStartedListener} and the activeness change through the
+ * {@link OnAudioPlaybackActiveStateListener}.
  */
 class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub {
     private static boolean DEBUG = MediaSessionService.DEBUG;
     private static String TAG = "AudioPlaybackMonitor";
 
+    private static AudioPlaybackMonitor sInstance;
+
     /**
      * Called when audio playback is started for a given UID.
      */
@@ -49,22 +54,36 @@
         void onAudioPlaybackStarted(int uid);
     }
 
+    /**
+     * Called when audio player state is changed.
+     */
+    interface OnAudioPlayerActiveStateChangedListener {
+        void onAudioPlayerActiveStateChanged(int uid, boolean active);
+    }
+
     private final Object mLock = new Object();
     private final Context mContext;
-    private final OnAudioPlaybackStartedListener mListener;
-
-    private Set<Integer> mActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>();
-    private Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
+    private final List<OnAudioPlaybackStartedListener> mAudioPlaybackStartedListeners
+            = new ArrayList<>();
+    private final List<OnAudioPlayerActiveStateChangedListener>
+            mAudioPlayerActiveStateChangedListeners = new ArrayList<>();
+    private final Map<Integer, Integer> mAudioPlaybackStates = new HashMap<>();
+    private final Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
 
     // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
     // The UID whose audio playback becomes active at the last comes first.
     // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
     private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
 
-    AudioPlaybackMonitor(Context context, IAudioService audioService,
-            OnAudioPlaybackStartedListener listener) {
+    static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) {
+        if (sInstance == null) {
+            sInstance = new AudioPlaybackMonitor(context, audioService);
+        }
+        return sInstance;
+    }
+
+    private AudioPlaybackMonitor(Context context, IAudioService audioService) {
         mContext = context;
-        mListener = listener;
         try {
             audioService.registerPlaybackCallback(this);
         } catch (RemoteException e) {
@@ -84,9 +103,12 @@
     public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
         final long token = Binder.clearCallingIdentity();
         try {
-            Set<Integer> newActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>();
             List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>();
+            List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners;
+            List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners;
             synchronized (mLock) {
+                // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids,
+                // and find newly activated audio playbacks.
                 mActiveAudioPlaybackClientUids.clear();
                 for (AudioPlaybackConfiguration config : configs) {
                     // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL
@@ -94,16 +116,14 @@
                     // playback.
                     // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
                     // specific audio/video players.
-                    if (!config.isActive()
-                            || config.getPlayerType()
+                    if (!config.isActive() || config.getPlayerType()
                             == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
                         continue;
                     }
-                    mActiveAudioPlaybackClientUids.add(config.getClientUid());
 
-                    newActiveAudioPlaybackPlayerInterfaceIds.add(config.getPlayerInterfaceId());
-                    if (!mActiveAudioPlaybackPlayerInterfaceIds.contains(
-                            config.getPlayerInterfaceId())) {
+                    mActiveAudioPlaybackClientUids.add(config.getClientUid());
+                    Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId());
+                    if (!isActiveState(oldState)) {
                         if (DEBUG) {
                             Log.d(TAG, "Found a new active media playback. " +
                                     AudioPlaybackConfiguration.toLogFriendlyString(config));
@@ -120,11 +140,32 @@
                         mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
                     }
                 }
-                mActiveAudioPlaybackPlayerInterfaceIds.clear();
-                mActiveAudioPlaybackPlayerInterfaceIds = newActiveAudioPlaybackPlayerInterfaceIds;
+                audioPlayerActiveStateChangedListeners = new ArrayList<>(
+                        mAudioPlayerActiveStateChangedListeners);
+                audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners);
             }
+            // Notify the change of audio playback states.
+            for (AudioPlaybackConfiguration config : configs) {
+                boolean wasActive = isActiveState(
+                        mAudioPlaybackStates.get(config.getPlayerInterfaceId()));
+                boolean isActive = config.isActive();
+                if (wasActive != isActive) {
+                    for (OnAudioPlayerActiveStateChangedListener listener
+                            : audioPlayerActiveStateChangedListeners) {
+                        listener.onAudioPlayerActiveStateChanged(config.getClientUid(),
+                                isActive);
+                    }
+                }
+            }
+            // Notify the start of audio playback
             for (int uid : newActiveAudioPlaybackClientUids) {
-                mListener.onAudioPlaybackStarted(uid);
+                for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) {
+                    listener.onAudioPlaybackStarted(uid);
+                }
+            }
+            mAudioPlaybackStates.clear();
+            for (AudioPlaybackConfiguration config : configs) {
+                mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState());
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -132,6 +173,44 @@
     }
 
     /**
+     * Registers OnAudioPlaybackStartedListener.
+     */
+    public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
+        synchronized (mLock) {
+            mAudioPlaybackStartedListeners.add(listener);
+        }
+    }
+
+    /**
+     * Unregisters OnAudioPlaybackStartedListener.
+     */
+    public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
+        synchronized (mLock) {
+            mAudioPlaybackStartedListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Registers OnAudioPlayerActiveStateChangedListener.
+     */
+    public void registerOnAudioPlayerActiveStateChangedListener(
+            OnAudioPlayerActiveStateChangedListener listener) {
+        synchronized (mLock) {
+            mAudioPlayerActiveStateChangedListeners.add(listener);
+        }
+    }
+
+    /**
+     * Unregisters OnAudioPlayerActiveStateChangedListener.
+     */
+    public void unregisterOnAudioPlayerActiveStateChangedListener(
+            OnAudioPlayerActiveStateChangedListener listener) {
+        synchronized (mLock) {
+            mAudioPlayerActiveStateChangedListeners.remove(listener);
+        }
+    }
+
+    /**
      * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
      * audio/video) The UID whose audio playback becomes active at the last comes first.
      */
@@ -167,7 +246,8 @@
                 if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
                     break;
                 }
-                if (userId == UserHandle.getUserId(mSortedAudioPlaybackClientUids.get(i))) {
+                int uid = mSortedAudioPlaybackClientUids.get(i);
+                if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
                     // Clean up unnecessary UIDs.
                     // It doesn't need to be managed profile aware because it's just to prevent
                     // the list from increasing indefinitely. The media button session updating
@@ -198,4 +278,8 @@
             }
         }
     }
+
+    private boolean isActiveState(Integer state) {
+        return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
+    }
 }
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index d1fa5ef..5ba8f48 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -18,6 +18,7 @@
 
 import com.android.internal.util.DumpUtils;
 import com.android.server.Watchdog;
+import com.android.server.media.AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -26,7 +27,10 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
+import android.media.IAudioRoutesObserver;
+import android.media.IAudioService;
 import android.media.IMediaRouterClient;
 import android.media.IMediaRouterService;
 import android.media.MediaRouter;
@@ -39,9 +43,12 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -89,10 +96,54 @@
     private final ArrayMap<IBinder, ClientRecord> mAllClientRecords =
             new ArrayMap<IBinder, ClientRecord>();
     private int mCurrentUserId = -1;
+    private boolean mHasBluetoothRoute = false;
+    private final IAudioService mAudioService;
+    private final AudioPlaybackMonitor mAudioPlaybackMonitor;
 
     public MediaRouterService(Context context) {
         mContext = context;
         Watchdog.getInstance().addMonitor(this);
+
+        mAudioService = IAudioService.Stub.asInterface(
+                ServiceManager.getService(Context.AUDIO_SERVICE));
+
+        mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(context, mAudioService);
+        mAudioPlaybackMonitor.registerOnAudioPlayerActiveStateChangedListener(
+                new AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener() {
+            @Override
+            public void onAudioPlayerActiveStateChanged(int uid, boolean active) {
+                if (active) {
+                    restoreRoute(uid);
+                } else {
+                    IntArray sortedAudioPlaybackClientUids =
+                            mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
+                    boolean restored = false;
+                    for (int i = 0; i < sortedAudioPlaybackClientUids.size(); i++) {
+                        if (mAudioPlaybackMonitor.isPlaybackActive(
+                                sortedAudioPlaybackClientUids.get(i))) {
+                            restoreRoute(sortedAudioPlaybackClientUids.get(i));
+                            restored = true;
+                            break;
+                        }
+                    }
+                    if (!restored) {
+                        restoreBluetoothA2dp();
+                    }
+                }
+            }
+        });
+        AudioRoutesInfo audioRoutes = null;
+        try {
+            audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
+                @Override
+                public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
+                    mHasBluetoothRoute = newRoutes.bluetoothName != null;
+                }
+            });
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException in the audio service.");
+        }
+        mHasBluetoothRoute = (audioRoutes != null && audioRoutes.bluetoothName != null);
     }
 
     public void systemRunning() {
@@ -135,7 +186,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                registerClientLocked(client, pid, packageName, resolvedUserId, trusted);
+                registerClientLocked(client, uid, pid, packageName, resolvedUserId, trusted);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -178,6 +229,23 @@
 
     // Binder call
     @Override
+    public boolean isPlaybackActive(IMediaRouterClient client) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                return isPlaybackActiveLocked(client);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
     public void setDiscoveryRequest(IMediaRouterClient client,
             int routeTypes, boolean activeScan) {
         if (client == null) {
@@ -276,6 +344,36 @@
         }
     }
 
+    void restoreBluetoothA2dp() {
+        try {
+            mAudioService.setBluetoothA2dpOn(mHasBluetoothRoute);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
+        }
+    }
+
+    void restoreRoute(int uid) {
+        ClientRecord clientRecord = null;
+        UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid));
+        if (userRecord.mClientRecords != null) {
+            for (ClientRecord cr : userRecord.mClientRecords) {
+                if (validatePackageName(uid, cr.mPackageName)) {
+                    clientRecord = cr;
+                    break;
+                }
+            }
+        }
+        if (clientRecord != null) {
+            try {
+                clientRecord.mClient.onRestoreRoute();
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to call onRestoreRoute. Client probably died.");
+            }
+        } else {
+            restoreBluetoothA2dp();
+        }
+    }
+
     void switchUser() {
         synchronized (mLock) {
             int userId = ActivityManager.getCurrentUser();
@@ -304,7 +402,7 @@
     }
 
     private void registerClientLocked(IMediaRouterClient client,
-            int pid, String packageName, int userId, boolean trusted) {
+            int uid, int pid, String packageName, int userId, boolean trusted) {
         final IBinder binder = client.asBinder();
         ClientRecord clientRecord = mAllClientRecords.get(binder);
         if (clientRecord == null) {
@@ -314,7 +412,7 @@
                 userRecord = new UserRecord(userId);
                 newUser = true;
             }
-            clientRecord = new ClientRecord(userRecord, client, pid, packageName, trusted);
+            clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted);
             try {
                 binder.linkToDeath(clientRecord, 0);
             } catch (RemoteException ex) {
@@ -350,6 +448,14 @@
         return null;
     }
 
+    private boolean isPlaybackActiveLocked(IMediaRouterClient client) {
+        ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
+        if (clientRecord != null) {
+            return mAudioPlaybackMonitor.isPlaybackActive(clientRecord.mUid);
+        }
+        return false;
+    }
+
     private void setDiscoveryRequestLocked(IMediaRouterClient client,
             int routeTypes, boolean activeScan) {
         final IBinder binder = client.asBinder();
@@ -489,6 +595,7 @@
     final class ClientRecord implements DeathRecipient {
         public final UserRecord mUserRecord;
         public final IMediaRouterClient mClient;
+        public final int mUid;
         public final int mPid;
         public final String mPackageName;
         public final boolean mTrusted;
@@ -498,9 +605,10 @@
         public String mSelectedRouteId;
 
         public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
-                int pid, String packageName, boolean trusted) {
+                int uid, int pid, String packageName, boolean trusted) {
             mUserRecord = userRecord;
             mClient = client;
+            mUid = uid;
             mPid = pid;
             mPackageName = packageName;
             mTrusted = trusted;
@@ -997,7 +1105,7 @@
                     try {
                         mTempClients.get(i).onStateChanged();
                     } catch (RemoteException ex) {
-                        // ignore errors, client probably died
+                        Slog.w(TAG, "Failed to call onStateChanged. Client probably died.");
                     }
                 }
             } finally {
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 38c6157..adb50f0 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -138,19 +138,20 @@
         mKeyguardManager =
                 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
         mAudioService = getAudioService();
-        mAudioPlaybackMonitor = new AudioPlaybackMonitor(getContext(), mAudioService,
+        mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(getContext(), mAudioService);
+        mAudioPlaybackMonitor.registerOnAudioPlaybackStartedListener(
                 new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
-                    @Override
-                    public void onAudioPlaybackStarted(int uid) {
-                        synchronized (mLock) {
-                            FullUserRecord user =
-                                    getFullUserRecordLocked(UserHandle.getUserId(uid));
-                            if (user != null) {
-                                user.mPriorityStack.updateMediaButtonSessionIfNeeded();
-                            }
-                        }
+            @Override
+            public void onAudioPlaybackStarted(int uid) {
+                synchronized (mLock) {
+                    FullUserRecord user =
+                            getFullUserRecordLocked(UserHandle.getUserId(uid));
+                    if (user != null) {
+                        user.mPriorityStack.updateMediaButtonSessionIfNeeded();
                     }
-                });
+                }
+            }
+        });
         mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
         mContentResolver = getContext().getContentResolver();
         mSettingsObserver = new SettingsObserver();