Merge "Fix overactive media routing"
This commit is contained in:
committed by
Android (Google) Code Review
commit
1dbf85589e
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -84,7 +84,6 @@ class MediaSessionStack {
|
||||
*/
|
||||
private MediaSessionRecord mMediaButtonSession;
|
||||
|
||||
private MediaSessionRecord mCachedDefault;
|
||||
private MediaSessionRecord mCachedVolumeDefault;
|
||||
|
||||
/**
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user