Merge changes from topic "mediarouter_fix"

* changes:
  AudioPlayerStateMonitor: fix NPE
  Revert "Revert "Fix overactive media routing""
diff --git a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java b/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
deleted file mode 100644
index 791ee82..0000000
--- a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import android.content.Context;
-import android.media.AudioManager.AudioPlaybackCallback;
-import android.media.AudioPlaybackConfiguration;
-import android.media.IAudioService;
-import android.media.IPlaybackConfigDispatcher;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.IntArray;
-import android.util.Log;
-import android.util.SparseArray;
-
-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} 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.
-     */
-    interface OnAudioPlaybackStartedListener {
-        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 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();
-
-    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;
-        try {
-            audioService.registerPlaybackCallback(this);
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Failed to register playback callback", e);
-        }
-    }
-
-    /**
-     * Called when the {@link AudioPlaybackConfiguration} is updated.
-     * <p>If an app starts audio playback, the app's local media session will be the media button
-     * session. If the app has multiple media sessions, the playback active local session will be
-     * picked.
-     *
-     * @param configs List of the current audio playback configuration
-     */
-    @Override
-    public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
-            boolean flush) {
-        if (flush) {
-            Binder.flushPendingCommands();
-        }
-        final long token = Binder.clearCallingIdentity();
-        try {
-            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
-                    // (i.e. playback from the SoundPool class which is only for sound effects)
-                    // playback.
-                    // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
-                    // specific audio/video players.
-                    if (!config.isActive() || config.getPlayerType()
-                            == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
-                        continue;
-                    }
-
-                    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));
-                        }
-                        // New active audio playback.
-                        newActiveAudioPlaybackClientUids.add(config.getClientUid());
-                        int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid());
-                        if (index == 0) {
-                            // It's the lastly played music app already. Skip updating.
-                            continue;
-                        } else if (index > 0) {
-                            mSortedAudioPlaybackClientUids.remove(index);
-                        }
-                        mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
-                    }
-                }
-                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) {
-                for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) {
-                    listener.onAudioPlaybackStarted(uid);
-                }
-            }
-            mAudioPlaybackStates.clear();
-            for (AudioPlaybackConfiguration config : configs) {
-                mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState());
-            }
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * 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.
-     */
-    public IntArray getSortedAudioPlaybackClientUids() {
-        IntArray sortedAudioPlaybackClientUids = new IntArray();
-        synchronized (mLock) {
-            sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
-        }
-        return sortedAudioPlaybackClientUids;
-    }
-
-    /**
-     * Returns if the audio playback is active for the uid.
-     */
-    public boolean isPlaybackActive(int uid) {
-        synchronized (mLock) {
-            return mActiveAudioPlaybackClientUids.contains(uid);
-        }
-    }
-
-    /**
-     * Cleans up the sorted list of audio playback client UIDs with given {@param
-     * mediaButtonSessionUid}.
-     * <p>UIDs whose audio playback started after the media button session's audio playback
-     * cannot be the lastly played media app. So they won't needed anymore.
-     *
-     * @param mediaButtonSessionUid UID of the media button session.
-     */
-    public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
-        synchronized (mLock) {
-            int userId = UserHandle.getUserId(mediaButtonSessionUid);
-            for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
-                if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
-                    break;
-                }
-                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
-                    // shouldn't be affected by cleaning up.
-                    mSortedAudioPlaybackClientUids.remove(i);
-                }
-            }
-        }
-    }
-
-    /**
-     * Dumps {@link AudioPlaybackMonitor}.
-     */
-    public void dump(PrintWriter pw, String prefix) {
-        synchronized (mLock) {
-            pw.println(prefix + "Audio playback (lastly played comes first)");
-            String indent = prefix + "  ";
-            for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
-                int uid = mSortedAudioPlaybackClientUids.get(i);
-                pw.print(indent + "uid=" + uid + " packages=");
-                String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
-                if (packages != null && packages.length > 0) {
-                    for (int j = 0; j < packages.length; j++) {
-                        pw.print(packages[j] + " ");
-                    }
-                }
-                pw.println();
-            }
-        }
-    }
-
-    private boolean isActiveState(Integer state) {
-        return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
-    }
-}
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
new file mode 100644
index 0000000..be223f1
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioPlaybackConfiguration;
+import android.media.IAudioService;
+import android.media.IPlaybackConfigDispatcher;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IntArray;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Monitors the state changes of audio players.
+ */
+class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
+    private static boolean DEBUG = MediaSessionService.DEBUG;
+    private static String TAG = "AudioPlayerStateMonitor";
+
+    private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
+
+    /**
+     * Called when the state of audio player is changed.
+     */
+    interface OnAudioPlayerStateChangedListener {
+        void onAudioPlayerStateChanged(
+                int uid, int prevState, @Nullable AudioPlaybackConfiguration config);
+    }
+
+    private final static class MessageHandler extends Handler {
+        private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1;
+
+        private final OnAudioPlayerStateChangedListener mListsner;
+
+        public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) {
+            super(looper);
+            mListsner = listener;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_AUDIO_PLAYER_STATE_CHANGED:
+                    mListsner.onAudioPlayerStateChanged(
+                            msg.arg1, msg.arg2, (AudioPlaybackConfiguration) msg.obj);
+                    break;
+            }
+        }
+
+        public void sendAudioPlayerStateChangedMessage(int uid, int prevState,
+                AudioPlaybackConfiguration config) {
+            obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget();
+        }
+    }
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap =
+            new HashMap<>();
+    @GuardedBy("mLock")
+    private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>();
+    @GuardedBy("mLock")
+    private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>();
+    // 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.
+    @GuardedBy("mLock")
+    private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
+
+    @GuardedBy("mLock")
+    private boolean mRegisteredToAudioService;
+
+    static AudioPlayerStateMonitor getInstance() {
+        return sInstance;
+    }
+
+    private AudioPlayerStateMonitor() {
+    }
+
+    /**
+     * Called when the {@link AudioPlaybackConfiguration} is updated.
+     * <p>If an app starts audio playback, the app's local media session will be the media button
+     * session. If the app has multiple media sessions, the playback active local session will be
+     * picked.
+     *
+     * @param configs List of the current audio playback configuration
+     */
+    @Override
+    public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
+            boolean flush) {
+        if (flush) {
+            Binder.flushPendingCommands();
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates);
+            final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid =
+                    new HashMap<>(mAudioPlayersForUid);
+            synchronized (mLock) {
+                mAudioPlayerStates.clear();
+                mAudioPlayersForUid.clear();
+                for (AudioPlaybackConfiguration config : configs) {
+                    int pii = config.getPlayerInterfaceId();
+                    int uid = config.getClientUid();
+                    mAudioPlayerStates.put(pii, config.getPlayerState());
+                    HashSet<Integer> players = mAudioPlayersForUid.get(uid);
+                    if (players == null) {
+                        players = new HashSet<Integer>();
+                        players.add(pii);
+                        mAudioPlayersForUid.put(uid, players);
+                    } else {
+                        players.add(pii);
+                    }
+                }
+                for (AudioPlaybackConfiguration config : configs) {
+                    if (!config.isActive()) {
+                        continue;
+                    }
+
+                    int uid = config.getClientUid();
+                    if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Found a new active media playback. " +
+                                    AudioPlaybackConfiguration.toLogFriendlyString(config));
+                        }
+                        // New active audio playback.
+                        int index = mSortedAudioPlaybackClientUids.indexOf(uid);
+                        if (index == 0) {
+                            // It's the lastly played music app already. Skip updating.
+                            continue;
+                        } else if (index > 0) {
+                            mSortedAudioPlaybackClientUids.remove(index);
+                        }
+                        mSortedAudioPlaybackClientUids.add(0, uid);
+                    }
+                }
+                // Notify the change of audio player states.
+                for (AudioPlaybackConfiguration config : configs) {
+                    final Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId());
+                    final int prevStateInt =
+                            (prevState == null) ? AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN :
+                                prevState.intValue();
+                    if (prevStateInt != config.getPlayerState()) {
+                        sendAudioPlayerStateChangedMessageLocked(
+                                config.getClientUid(), prevStateInt, config);
+                    }
+                }
+                for (Integer prevUid : prevAudioPlayersForUid.keySet()) {
+                    // If all players for prevUid is removed, notify the prev state was
+                    // PLAYER_STATE_STARTED only when there were a player whose state was
+                    // PLAYER_STATE_STARTED, otherwise any inactive state is okay to notify.
+                    if (!mAudioPlayersForUid.containsKey(prevUid)) {
+                        Set<Integer> prevPlayers = prevAudioPlayersForUid.get(prevUid);
+                        int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN;
+                        for (int pii : prevPlayers) {
+                            Integer state = prevAudioPlayerStates.get(pii);
+                            if (state == null) {
+                                continue;
+                            }
+                            if (state == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                                prevState = state;
+                                break;
+                            } else if (prevState
+                                    == AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN) {
+                                prevState = state;
+                            }
+                        }
+                        sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Registers OnAudioPlayerStateChangedListener.
+     */
+    public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) {
+        synchronized (mLock) {
+            mListenerMap.put(listener, new MessageHandler((handler == null) ?
+                    Looper.myLooper() : handler.getLooper(), listener));
+        }
+    }
+
+    /**
+     * Unregisters OnAudioPlayerStateChangedListener.
+     */
+    public void unregisterListener(OnAudioPlayerStateChangedListener listener) {
+        synchronized (mLock) {
+            mListenerMap.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.
+     */
+    public IntArray getSortedAudioPlaybackClientUids() {
+        IntArray sortedAudioPlaybackClientUids = new IntArray();
+        synchronized (mLock) {
+            sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
+        }
+        return sortedAudioPlaybackClientUids;
+    }
+
+    /**
+     * Returns if the audio playback is active for the uid.
+     */
+    public boolean isPlaybackActive(int uid) {
+        synchronized (mLock) {
+            Set<Integer> players = mAudioPlayersForUid.get(uid);
+            if (players == null) {
+                return false;
+            }
+            for (Integer pii : players) {
+                if (isActiveState(mAudioPlayerStates.get(pii))) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Cleans up the sorted list of audio playback client UIDs with given {@param
+     * mediaButtonSessionUid}.
+     * <p>UIDs whose audio playback are inactive and have started before the media button session's
+     * audio playback cannot be the lastly played media app. So they won't needed anymore.
+     *
+     * @param mediaButtonSessionUid UID of the media button session.
+     */
+    public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
+        synchronized (mLock) {
+            int userId = UserHandle.getUserId(mediaButtonSessionUid);
+            for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
+                if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
+                    break;
+                }
+                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
+                    // shouldn't be affected by cleaning up.
+                    mSortedAudioPlaybackClientUids.remove(i);
+                }
+            }
+        }
+    }
+
+    /**
+     * Dumps {@link AudioPlayerStateMonitor}.
+     */
+    public void dump(Context context, PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            pw.println(prefix + "Audio playback (lastly played comes first)");
+            String indent = prefix + "  ";
+            for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
+                int uid = mSortedAudioPlaybackClientUids.get(i);
+                pw.print(indent + "uid=" + uid + " packages=");
+                String[] packages = context.getPackageManager().getPackagesForUid(uid);
+                if (packages != null && packages.length > 0) {
+                    for (int j = 0; j < packages.length; j++) {
+                        pw.print(packages[j] + " ");
+                    }
+                }
+                pw.println();
+            }
+        }
+    }
+
+    public void registerSelfIntoAudioServiceIfNeeded(IAudioService audioService) {
+        synchronized (mLock) {
+            try {
+                if (!mRegisteredToAudioService) {
+                    audioService.registerPlaybackCallback(this);
+                    mRegisteredToAudioService = true;
+                }
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Failed to register playback callback", e);
+                mRegisteredToAudioService = false;
+            }
+        }
+    }
+
+    private void sendAudioPlayerStateChangedMessageLocked(
+            final int uid, final int prevState, final AudioPlaybackConfiguration config) {
+        for (MessageHandler messageHandler : mListenerMap.values()) {
+            messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config);
+        }
+    }
+
+    private static 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 1cfd5f0..3c9e1d4 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -19,12 +19,14 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.Watchdog;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
 import android.media.IAudioRoutesObserver;
@@ -96,7 +98,8 @@
     private int mCurrentUserId = -1;
     private boolean mGlobalBluetoothA2dpOn = false;
     private final IAudioService mAudioService;
-    private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
+    private final Handler mHandler = new Handler();
     private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
 
     public MediaRouterService(Context context) {
@@ -106,31 +109,57 @@
         mAudioService = IAudioService.Stub.asInterface(
                 ServiceManager.getService(Context.AUDIO_SERVICE));
 
-        mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(context, mAudioService);
-        mAudioPlaybackMonitor.registerOnAudioPlayerActiveStateChangedListener(
-                new AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener() {
+        mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+        mAudioPlayerStateMonitor.registerListener(
+                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
+            static final long WAIT_MS = 500;
+            final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    restoreBluetoothA2dp();
+                }
+            };
+
             @Override
-            public void onAudioPlayerActiveStateChanged(int uid, boolean active) {
+            public void onAudioPlayerStateChanged(
+                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+                int restoreUid = -1;
+                boolean active = config == null ? false : config.isActive();
                 if (active) {
-                    restoreRoute(uid);
+                    restoreUid = uid;
+                } else if (prevState != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    // Noting to do if the prev state is not an active state.
+                    return;
                 } else {
                     IntArray sortedAudioPlaybackClientUids =
-                            mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
-                    boolean restored = false;
-                    for (int i = 0; i < sortedAudioPlaybackClientUids.size(); i++) {
-                        if (mAudioPlaybackMonitor.isPlaybackActive(
+                            mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
+                    for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) {
+                        if (mAudioPlayerStateMonitor.isPlaybackActive(
                                 sortedAudioPlaybackClientUids.get(i))) {
-                            restoreRoute(sortedAudioPlaybackClientUids.get(i));
-                            restored = true;
+                            restoreUid = sortedAudioPlaybackClientUids.get(i);
                             break;
                         }
                     }
-                    if (!restored) {
-                        restoreBluetoothA2dp();
+                }
+
+                mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
+                if (restoreUid >= 0) {
+                    restoreRoute(restoreUid);
+                    if (DEBUG) {
+                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+                                + " active " + active + " restoring " + restoreUid);
+                    }
+                } else {
+                    mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
+                    if (DEBUG) {
+                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+                                + " active " + active + " delaying");
                     }
                 }
             }
-        });
+        }, mHandler);
+        mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
+
         AudioRoutesInfo audioRoutes = null;
         try {
             audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
@@ -261,9 +290,14 @@
 
         final long token = Binder.clearCallingIdentity();
         try {
+            ClientRecord clientRecord;
             synchronized (mLock) {
-                return isPlaybackActiveLocked(client);
+                clientRecord = mAllClientRecords.get(client.asBinder());
             }
+            if (clientRecord != null) {
+                return mAudioPlayerStateMonitor.isPlaybackActive(clientRecord.mUid);
+            }
+            return false;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -480,14 +514,6 @@
         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();
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index aa65244..f6a81d0 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.media;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
@@ -31,7 +32,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.media.AudioManager;
-import android.media.AudioManagerInternal;
+import android.media.AudioPlaybackConfiguration;
 import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
@@ -68,7 +69,6 @@
 import android.view.ViewConfiguration;
 
 import com.android.internal.util.DumpUtils;
-import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.Watchdog;
 import com.android.server.Watchdog.Monitor;
@@ -104,7 +104,6 @@
 
     private KeyguardManager mKeyguardManager;
     private IAudioService mAudioService;
-    private AudioManagerInternal mAudioManagerInternal;
     private ContentResolver mContentResolver;
     private SettingsObserver mSettingsObserver;
     private INotificationManager mNotificationManager;
@@ -114,7 +113,7 @@
     // It's always not null after the MediaSessionService is started.
     private FullUserRecord mCurrentFullUserRecord;
     private MediaSessionRecord mGlobalPrioritySession;
-    private AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
 
     // Used to notify system UI when remote volume was changed. TODO find a
     // better way to handle this.
@@ -137,11 +136,16 @@
         mKeyguardManager =
                 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
         mAudioService = getAudioService();
-        mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(getContext(), mAudioService);
-        mAudioPlaybackMonitor.registerOnAudioPlaybackStartedListener(
-                new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
+        mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+        mAudioPlayerStateMonitor.registerListener(
+                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
             @Override
-            public void onAudioPlaybackStarted(int uid) {
+            public void onAudioPlayerStateChanged(
+                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+                if (config == null || !config.isActive() || config.getPlayerType()
+                        == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+                    return;
+                }
                 synchronized (mLock) {
                     FullUserRecord user =
                             getFullUserRecordLocked(UserHandle.getUserId(uid));
@@ -150,8 +154,8 @@
                     }
                 }
             }
-        });
-        mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
+        }, null /* handler */);
+        mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
         mContentResolver = getContext().getContentResolver();
         mSettingsObserver = new SettingsObserver();
         mSettingsObserver.observe();
@@ -650,7 +654,7 @@
 
         public FullUserRecord(int fullUserId) {
             mFullUserId = fullUserId;
-            mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);
+            mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
             // Restore the remembered media button receiver before the boot.
             String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
                     Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
@@ -1309,7 +1313,7 @@
                 for (int i = 0; i < count; i++) {
                     mUserRecords.valueAt(i).dumpLocked(pw, "");
                 }
-                mAudioPlaybackMonitor.dump(pw, "");
+                mAudioPlayerStateMonitor.dump(getContext(), pw, "");
             }
         }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index d9fe72e..719ec36 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -75,7 +75,7 @@
      */
     private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
 
-    private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
 
     /**
@@ -84,7 +84,6 @@
      */
     private MediaSessionRecord mMediaButtonSession;
 
-    private MediaSessionRecord mCachedDefault;
     private MediaSessionRecord mCachedVolumeDefault;
 
     /**
@@ -93,8 +92,8 @@
     private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists =
             new SparseArray<>();
 
-    MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) {
-        mAudioPlaybackMonitor = monitor;
+    MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) {
+        mAudioPlayerStateMonitor = monitor;
         mOnMediaButtonSessionChangedListener = listener;
     }
 
@@ -187,13 +186,13 @@
         if (DEBUG) {
             Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
         }
-        IntArray audioPlaybackUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
+        IntArray audioPlaybackUids = mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
         for (int i = 0; i < audioPlaybackUids.size(); i++) {
             MediaSessionRecord mediaButtonSession =
                     findMediaButtonSession(audioPlaybackUids.get(i));
             if (mediaButtonSession != null) {
                 // Found the media button session.
-                mAudioPlaybackMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
+                mAudioPlayerStateMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
                 if (mMediaButtonSession != mediaButtonSession) {
                     updateMediaButtonSession(mediaButtonSession);
                 }
@@ -216,7 +215,7 @@
         for (MediaSessionRecord session : mSessions) {
             if (uid == session.getUid()) {
                 if (session.getPlaybackState() != null && session.isPlaybackActive() ==
-                        mAudioPlaybackMonitor.isPlaybackActive(session.getUid())) {
+                        mAudioPlayerStateMonitor.isPlaybackActive(session.getUid())) {
                     // If there's a media session whose PlaybackState matches
                     // the audio playback state, return it immediately.
                     return session;
@@ -376,7 +375,6 @@
     }
 
     private void clearCache(int userId) {
-        mCachedDefault = null;
         mCachedVolumeDefault = null;
         mCachedActiveLists.remove(userId);
         // mCachedActiveLists may also include the list of sessions for UserHandle.USER_ALL,