Loading services/core/java/com/android/server/media/AudioPlaybackMonitor.java→services/core/java/com/android/server/media/AudioPlayerStateMonitor.java +324 −0 Original line number Diff line number Diff line Loading @@ -16,20 +16,23 @@ package com.android.server.media; import android.annotation.Nullable; 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.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 android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.HashMap; import java.util.List; Loading @@ -37,58 +40,70 @@ 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}. * Monitors the state changes of audio players. */ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub { private static boolean DEBUG = MediaSessionService.DEBUG; private static String TAG = "AudioPlaybackMonitor"; private static String TAG = "AudioPlayerStateMonitor"; private static AudioPlaybackMonitor sInstance; private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor(); /** * Called when audio playback is started for a given UID. * Called when the state of audio player is changed. */ interface OnAudioPlaybackStartedListener { void onAudioPlaybackStarted(int uid); interface OnAudioPlayerStateChangedListener { void onAudioPlayerStateChanged( int uid, int prevState, @Nullable AudioPlaybackConfiguration config); } /** * Called when audio player state is changed. */ interface OnAudioPlayerActiveStateChangedListener { void onAudioPlayerActiveStateChanged(int uid, boolean active); 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; } 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<>(); @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(); static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) { if (sInstance == null) { sInstance = new AudioPlaybackMonitor(context, audioService); } @GuardedBy("mLock") private boolean mRegisteredToAudioService; static AudioPlayerStateMonitor getInstance() { 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); } private AudioPlayerStateMonitor() { } /** Loading @@ -107,69 +122,78 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { } final long token = Binder.clearCallingIdentity(); try { List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>(); List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners; List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners; final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates); final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid = new HashMap<>(mAudioPlayersForUid); synchronized (mLock) { // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids, // and find newly activated audio playbacks. mActiveAudioPlaybackClientUids.clear(); mAudioPlayerStates.clear(); mAudioPlayersForUid.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) { 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; } mActiveAudioPlaybackClientUids.add(config.getClientUid()); Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId()); if (!isActiveState(oldState)) { 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. newActiveAudioPlaybackClientUids.add(config.getClientUid()); int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid()); 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, config.getClientUid()); } mSortedAudioPlaybackClientUids.add(0, uid); } audioPlayerActiveStateChangedListeners = new ArrayList<>( mAudioPlayerActiveStateChangedListeners); audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners); } // Notify the change of audio playback states. // Notify the change of audio player 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); Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId()); if (prevState == null || prevState != config.getPlayerState()) { sendAudioPlayerStateChangedMessageLocked( config.getClientUid(), prevState, 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> players = mAudioPlayersForUid.get(prevUid); int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN; for (int pii : players) { 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; } } // Notify the start of audio playback for (int uid : newActiveAudioPlaybackClientUids) { for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) { listener.onAudioPlaybackStarted(uid); sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null); } } mAudioPlaybackStates.clear(); for (AudioPlaybackConfiguration config : configs) { mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState()); } } finally { Binder.restoreCallingIdentity(token); Loading @@ -177,40 +201,21 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { } /** * Registers OnAudioPlaybackStartedListener. * Registers OnAudioPlayerStateChangedListener. */ public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) { public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) { synchronized (mLock) { mAudioPlaybackStartedListeners.add(listener); mListenerMap.put(listener, new MessageHandler((handler == null) ? Looper.myLooper() : handler.getLooper(), listener)); } } /** * Unregisters OnAudioPlaybackStartedListener. * Unregisters OnAudioPlayerStateChangedListener. */ public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) { public void unregisterListener(OnAudioPlayerStateChangedListener 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); mListenerMap.remove(listener); } } Loading @@ -231,15 +236,24 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { */ public boolean isPlaybackActive(int uid) { synchronized (mLock) { return mActiveAudioPlaybackClientUids.contains(uid); 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 started after the media button session's audio playback * cannot be the lastly played media app. So they won't needed anymore. * <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. */ Loading @@ -263,16 +277,16 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { } /** * Dumps {@link AudioPlaybackMonitor}. * Dumps {@link AudioPlayerStateMonitor}. */ public void dump(PrintWriter pw, String prefix) { 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 = mContext.getPackageManager().getPackagesForUid(uid); String[] packages = context.getPackageManager().getPackagesForUid(uid); if (packages != null && packages.length > 0) { for (int j = 0; j < packages.length; j++) { pw.print(packages[j] + " "); Loading @@ -283,7 +297,28 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { } } private boolean isActiveState(Integer state) { 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); } } services/core/java/com/android/server/media/MediaRouterService.java +50 −24 Original line number Diff line number Diff line Loading @@ -19,12 +19,14 @@ package com.android.server.media; 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; Loading Loading @@ -96,7 +98,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub 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) { Loading @@ -106,31 +109,57 @@ public final class MediaRouterService extends IMediaRouterService.Stub 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 onAudioPlayerActiveStateChanged(int uid, boolean active) { public void run() { restoreBluetoothA2dp(); } }; @Override 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() { Loading Loading @@ -261,9 +290,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub 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); } Loading Loading @@ -480,14 +514,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub 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(); Loading services/core/java/com/android/server/media/MediaSessionService.java +16 −12 File changed.Preview size limit exceeded, changes collapsed. Show changes services/core/java/com/android/server/media/MediaSessionStack.java +6 −8 Original line number Diff line number Diff line Loading @@ -75,7 +75,7 @@ class MediaSessionStack { */ private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>(); private final AudioPlaybackMonitor mAudioPlaybackMonitor; private final AudioPlayerStateMonitor mAudioPlayerStateMonitor; private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener; /** Loading @@ -84,7 +84,6 @@ class MediaSessionStack { */ private MediaSessionRecord mMediaButtonSession; private MediaSessionRecord mCachedDefault; private MediaSessionRecord mCachedVolumeDefault; /** Loading @@ -93,8 +92,8 @@ class MediaSessionStack { private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists = new SparseArray<>(); MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) { mAudioPlaybackMonitor = monitor; MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) { mAudioPlayerStateMonitor = monitor; mOnMediaButtonSessionChangedListener = listener; } Loading Loading @@ -187,13 +186,13 @@ class MediaSessionStack { 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); } Loading @@ -216,7 +215,7 @@ class MediaSessionStack { 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; Loading Loading @@ -376,7 +375,6 @@ class MediaSessionStack { } private void clearCache(int userId) { mCachedDefault = null; mCachedVolumeDefault = null; mCachedActiveLists.remove(userId); // mCachedActiveLists may also include the list of sessions for UserHandle.USER_ALL, Loading Loading
services/core/java/com/android/server/media/AudioPlaybackMonitor.java→services/core/java/com/android/server/media/AudioPlayerStateMonitor.java +324 −0 Original line number Diff line number Diff line Loading @@ -16,20 +16,23 @@ package com.android.server.media; import android.annotation.Nullable; 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.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 android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.HashMap; import java.util.List; Loading @@ -37,58 +40,70 @@ 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}. * Monitors the state changes of audio players. */ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub { private static boolean DEBUG = MediaSessionService.DEBUG; private static String TAG = "AudioPlaybackMonitor"; private static String TAG = "AudioPlayerStateMonitor"; private static AudioPlaybackMonitor sInstance; private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor(); /** * Called when audio playback is started for a given UID. * Called when the state of audio player is changed. */ interface OnAudioPlaybackStartedListener { void onAudioPlaybackStarted(int uid); interface OnAudioPlayerStateChangedListener { void onAudioPlayerStateChanged( int uid, int prevState, @Nullable AudioPlaybackConfiguration config); } /** * Called when audio player state is changed. */ interface OnAudioPlayerActiveStateChangedListener { void onAudioPlayerActiveStateChanged(int uid, boolean active); 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; } 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<>(); @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(); static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) { if (sInstance == null) { sInstance = new AudioPlaybackMonitor(context, audioService); } @GuardedBy("mLock") private boolean mRegisteredToAudioService; static AudioPlayerStateMonitor getInstance() { 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); } private AudioPlayerStateMonitor() { } /** Loading @@ -107,69 +122,78 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { } final long token = Binder.clearCallingIdentity(); try { List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>(); List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners; List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners; final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates); final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid = new HashMap<>(mAudioPlayersForUid); synchronized (mLock) { // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids, // and find newly activated audio playbacks. mActiveAudioPlaybackClientUids.clear(); mAudioPlayerStates.clear(); mAudioPlayersForUid.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) { 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; } mActiveAudioPlaybackClientUids.add(config.getClientUid()); Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId()); if (!isActiveState(oldState)) { 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. newActiveAudioPlaybackClientUids.add(config.getClientUid()); int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid()); 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, config.getClientUid()); } mSortedAudioPlaybackClientUids.add(0, uid); } audioPlayerActiveStateChangedListeners = new ArrayList<>( mAudioPlayerActiveStateChangedListeners); audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners); } // Notify the change of audio playback states. // Notify the change of audio player 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); Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId()); if (prevState == null || prevState != config.getPlayerState()) { sendAudioPlayerStateChangedMessageLocked( config.getClientUid(), prevState, 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> players = mAudioPlayersForUid.get(prevUid); int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN; for (int pii : players) { 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; } } // Notify the start of audio playback for (int uid : newActiveAudioPlaybackClientUids) { for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) { listener.onAudioPlaybackStarted(uid); sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null); } } mAudioPlaybackStates.clear(); for (AudioPlaybackConfiguration config : configs) { mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState()); } } finally { Binder.restoreCallingIdentity(token); Loading @@ -177,40 +201,21 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { } /** * Registers OnAudioPlaybackStartedListener. * Registers OnAudioPlayerStateChangedListener. */ public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) { public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) { synchronized (mLock) { mAudioPlaybackStartedListeners.add(listener); mListenerMap.put(listener, new MessageHandler((handler == null) ? Looper.myLooper() : handler.getLooper(), listener)); } } /** * Unregisters OnAudioPlaybackStartedListener. * Unregisters OnAudioPlayerStateChangedListener. */ public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) { public void unregisterListener(OnAudioPlayerStateChangedListener 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); mListenerMap.remove(listener); } } Loading @@ -231,15 +236,24 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { */ public boolean isPlaybackActive(int uid) { synchronized (mLock) { return mActiveAudioPlaybackClientUids.contains(uid); 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 started after the media button session's audio playback * cannot be the lastly played media app. So they won't needed anymore. * <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. */ Loading @@ -263,16 +277,16 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { } /** * Dumps {@link AudioPlaybackMonitor}. * Dumps {@link AudioPlayerStateMonitor}. */ public void dump(PrintWriter pw, String prefix) { 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 = mContext.getPackageManager().getPackagesForUid(uid); String[] packages = context.getPackageManager().getPackagesForUid(uid); if (packages != null && packages.length > 0) { for (int j = 0; j < packages.length; j++) { pw.print(packages[j] + " "); Loading @@ -283,7 +297,28 @@ class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { } } private boolean isActiveState(Integer state) { 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); } }
services/core/java/com/android/server/media/MediaRouterService.java +50 −24 Original line number Diff line number Diff line Loading @@ -19,12 +19,14 @@ package com.android.server.media; 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; Loading Loading @@ -96,7 +98,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub 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) { Loading @@ -106,31 +109,57 @@ public final class MediaRouterService extends IMediaRouterService.Stub 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 onAudioPlayerActiveStateChanged(int uid, boolean active) { public void run() { restoreBluetoothA2dp(); } }; @Override 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() { Loading Loading @@ -261,9 +290,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub 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); } Loading Loading @@ -480,14 +514,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub 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(); Loading
services/core/java/com/android/server/media/MediaSessionService.java +16 −12 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/core/java/com/android/server/media/MediaSessionStack.java +6 −8 Original line number Diff line number Diff line Loading @@ -75,7 +75,7 @@ class MediaSessionStack { */ private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>(); private final AudioPlaybackMonitor mAudioPlaybackMonitor; private final AudioPlayerStateMonitor mAudioPlayerStateMonitor; private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener; /** Loading @@ -84,7 +84,6 @@ class MediaSessionStack { */ private MediaSessionRecord mMediaButtonSession; private MediaSessionRecord mCachedDefault; private MediaSessionRecord mCachedVolumeDefault; /** Loading @@ -93,8 +92,8 @@ class MediaSessionStack { private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists = new SparseArray<>(); MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) { mAudioPlaybackMonitor = monitor; MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) { mAudioPlayerStateMonitor = monitor; mOnMediaButtonSessionChangedListener = listener; } Loading Loading @@ -187,13 +186,13 @@ class MediaSessionStack { 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); } Loading @@ -216,7 +215,7 @@ class MediaSessionStack { 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; Loading Loading @@ -376,7 +375,6 @@ class MediaSessionStack { } private void clearCache(int userId) { mCachedDefault = null; mCachedVolumeDefault = null; mCachedActiveLists.remove(userId); // mCachedActiveLists may also include the list of sessions for UserHandle.USER_ALL, Loading