From 18341301443d0c8e1bd4fc1ba9a7c909593eb918 Mon Sep 17 00:00:00 2001 From: Sungsoo Lim Date: Wed, 18 Oct 2017 14:54:59 +0900 Subject: [PATCH] Fix overactive media routing For MediaRouterService, - Delay route restoration to BT for 0.5s in case the app plays more audio For AudioPlaybackMonitor, - Rename to AudioPlayerStateMonitor. - Combine two listeners into one. - Take a handler for the looper of the callback methods. - Move the registration of playback callback into audio service from the constructor to the caller. Bug: 65376604 Test: manual tests as follows 1. Checked that the lastly played app receives the media key events although the app's media session doesn't report its playback state. 2. Checked that the lastly played app receives the media key events although the app's media session is released. 3. Confirmed that this mitigates the issue. Change-Id: I52e3719cd962f5d92c2f0e547309ce1314cc0926 --- .../android/server/media/AudioPlaybackMonitor.java | 289 ------------------ .../server/media/AudioPlayerStateMonitor.java | 324 +++++++++++++++++++++ .../android/server/media/MediaRouterService.java | 74 +++-- .../android/server/media/MediaSessionService.java | 28 +- .../android/server/media/MediaSessionStack.java | 14 +- 5 files changed, 396 insertions(+), 333 deletions(-) delete mode 100644 services/core/java/com/android/server/media/AudioPlaybackMonitor.java create mode 100644 services/core/java/com/android/server/media/AudioPlayerStateMonitor.java 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 791ee821b357..000000000000 --- 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 mAudioPlaybackStartedListeners - = new ArrayList<>(); - private final List - mAudioPlayerActiveStateChangedListeners = new ArrayList<>(); - private final Map mAudioPlaybackStates = new HashMap<>(); - private final Set 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. - *

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 configs, - boolean flush) { - if (flush) { - Binder.flushPendingCommands(); - } - final long token = Binder.clearCallingIdentity(); - try { - List newActiveAudioPlaybackClientUids = new ArrayList<>(); - List audioPlayerActiveStateChangedListeners; - List 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}. - *

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 000000000000..110f26d5eddc --- /dev/null +++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java @@ -0,0 +1,324 @@ +/* + * 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 mListenerMap = + new HashMap<>(); + @GuardedBy("mLock") + private final Map mAudioPlayerStates = new HashMap<>(); + @GuardedBy("mLock") + private final Map> 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. + *

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 configs, + boolean flush) { + if (flush) { + Binder.flushPendingCommands(); + } + final long token = Binder.clearCallingIdentity(); + try { + final Map prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates); + final Map> 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 players = mAudioPlayersForUid.get(uid); + if (players == null) { + players = new HashSet(); + 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) { + 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 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; + } + } + 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 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}. + *

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 1cfd5f02e810..3c9e1d456c6f 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 @@ 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; @@ -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) { @@ -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 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 @@ 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); } @@ -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(); diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index aa652445c45a..f6a81d07277a 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.PackageManager; 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.KeyEvent; 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 @@ public class MediaSessionService extends SystemService implements Monitor { private KeyguardManager mKeyguardManager; private IAudioService mAudioService; - private AudioManagerInternal mAudioManagerInternal; private ContentResolver mContentResolver; private SettingsObserver mSettingsObserver; private INotificationManager mNotificationManager; @@ -114,7 +113,7 @@ public class MediaSessionService extends SystemService implements Monitor { // 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 @@ public class MediaSessionService extends SystemService implements Monitor { 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 @@ public class MediaSessionService extends SystemService implements Monitor { } } } - }); - mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); + }, null /* handler */); + mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService); mContentResolver = getContext().getContentResolver(); mSettingsObserver = new SettingsObserver(); mSettingsObserver.observe(); @@ -650,7 +654,7 @@ public class MediaSessionService extends SystemService implements Monitor { 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 @@ public class MediaSessionService extends SystemService implements Monitor { 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 d9fe72e0c8bb..719ec362e6e8 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 @@ class MediaSessionStack { */ private final List mSessions = new ArrayList(); - private final AudioPlaybackMonitor mAudioPlaybackMonitor; + private final AudioPlayerStateMonitor mAudioPlayerStateMonitor; private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener; /** @@ -84,7 +84,6 @@ class MediaSessionStack { */ private MediaSessionRecord mMediaButtonSession; - private MediaSessionRecord mCachedDefault; private MediaSessionRecord mCachedVolumeDefault; /** @@ -93,8 +92,8 @@ class MediaSessionStack { private final SparseArray> mCachedActiveLists = new SparseArray<>(); - MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) { - mAudioPlaybackMonitor = monitor; + MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) { + mAudioPlayerStateMonitor = monitor; mOnMediaButtonSessionChangedListener = listener; } @@ -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); } @@ -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; @@ -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, -- 2.11.0