Merge "Introduce SessionPriorityList into MCS"
This commit is contained in:
committed by
Android (Google) Code Review
commit
44ecb68a09
@@ -262,7 +262,7 @@ public class MediaSession2 implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whehther the playback is active (i.e. playing something)
|
||||
* Returns whether the playback is active (i.e. playing something)
|
||||
*
|
||||
* @return {@code true} if the playback active, {@code false} otherwise.
|
||||
*/
|
||||
|
||||
@@ -54,7 +54,6 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A system service that manages {@link android.media.MediaSession2} creations
|
||||
@@ -281,6 +280,16 @@ public class MediaCommunicationService extends SystemService {
|
||||
session.close();
|
||||
}
|
||||
|
||||
void onSessionPlaybackStateChanged(Session2Record session, boolean promotePriority) {
|
||||
FullUserRecord user = session.getFullUser();
|
||||
if (user == null || !user.containsSession(session)) {
|
||||
Log.d(TAG, "Unknown session changed playback state. Ignoring.");
|
||||
return;
|
||||
}
|
||||
user.onPlaybackStateChanged(session, promotePriority);
|
||||
}
|
||||
|
||||
|
||||
static boolean isMediaSessionKey(int keyCode) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
||||
@@ -520,26 +529,20 @@ public class MediaCommunicationService extends SystemService {
|
||||
|
||||
final class FullUserRecord {
|
||||
private final int mFullUserId;
|
||||
private final Object mUserLock = new Object();
|
||||
@GuardedBy("mUserLock")
|
||||
private final List<Session2Record> mSessionRecords = new ArrayList<>();
|
||||
private final SessionPriorityList mSessionPriorityList = new SessionPriorityList();
|
||||
|
||||
FullUserRecord(int fullUserId) {
|
||||
mFullUserId = fullUserId;
|
||||
}
|
||||
|
||||
public void addSession(Session2Record record) {
|
||||
synchronized (mUserLock) {
|
||||
mSessionRecords.add(record);
|
||||
}
|
||||
mSessionPriorityList.addSession(record);
|
||||
mHandler.post(() -> dispatchSession2Created(record.mSessionToken));
|
||||
mHandler.post(() -> dispatchSession2Changed(mFullUserId));
|
||||
}
|
||||
|
||||
private void removeSession(Session2Record record) {
|
||||
synchronized (mUserLock) {
|
||||
mSessionRecords.remove(record);
|
||||
}
|
||||
mSessionPriorityList.removeSession(record);
|
||||
mHandler.post(() -> dispatchSession2Changed(mFullUserId));
|
||||
//TODO: Handle if the removed session was the media button session.
|
||||
}
|
||||
@@ -549,48 +552,31 @@ public class MediaCommunicationService extends SystemService {
|
||||
}
|
||||
|
||||
public List<Session2Token> getAllSession2Tokens() {
|
||||
synchronized (mUserLock) {
|
||||
return mSessionRecords.stream()
|
||||
.map(Session2Record::getSessionToken)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return mSessionPriorityList.getAllTokens();
|
||||
}
|
||||
|
||||
public List<Session2Token> getSession2Tokens(int userId) {
|
||||
synchronized (mUserLock) {
|
||||
return mSessionRecords.stream()
|
||||
.filter(record -> record.getUserId() == userId)
|
||||
.map(Session2Record::getSessionToken)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return mSessionPriorityList.getTokensByUserId(userId);
|
||||
}
|
||||
|
||||
public void destroyAllSessions() {
|
||||
synchronized (mUserLock) {
|
||||
for (Session2Record session : mSessionRecords) {
|
||||
session.close();
|
||||
}
|
||||
mSessionRecords.clear();
|
||||
}
|
||||
mSessionPriorityList.destroyAllSessions();
|
||||
mHandler.post(() -> dispatchSession2Changed(mFullUserId));
|
||||
}
|
||||
|
||||
public void destroySessionsForUser(int userId) {
|
||||
boolean changed = false;
|
||||
synchronized (mUserLock) {
|
||||
for (int i = mSessionRecords.size() - 1; i >= 0; i--) {
|
||||
Session2Record session = mSessionRecords.get(i);
|
||||
if (session.getUserId() == userId) {
|
||||
mSessionRecords.remove(i);
|
||||
session.close();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
if (mSessionPriorityList.destroySessionsByUserId(userId)) {
|
||||
mHandler.post(() -> dispatchSession2Changed(mFullUserId));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsSession(Session2Record session) {
|
||||
return mSessionPriorityList.contains(session);
|
||||
}
|
||||
|
||||
public void onPlaybackStateChanged(Session2Record session, boolean promotePriority) {
|
||||
mSessionPriorityList.onPlaybackStateChanged(session, promotePriority);
|
||||
}
|
||||
}
|
||||
|
||||
static final class Session2Record {
|
||||
@@ -606,6 +592,7 @@ public class MediaCommunicationService extends SystemService {
|
||||
@GuardedBy("mSession2RecordLock")
|
||||
private boolean mIsClosed;
|
||||
|
||||
//TODO: introduce policy (See MediaSessionPolicyProvider)
|
||||
Session2Record(MediaCommunicationService service, FullUserRecord fullUser,
|
||||
Session2Token token, Executor controllerExecutor) {
|
||||
mServiceRef = new WeakReference<>(service);
|
||||
@@ -641,6 +628,12 @@ public class MediaCommunicationService extends SystemService {
|
||||
return mSessionToken;
|
||||
}
|
||||
|
||||
public boolean checkPlaybackActiveState(boolean expected) {
|
||||
synchronized (mSession2RecordLock) {
|
||||
return mIsConnected && mController.isPlaybackActive() == expected;
|
||||
}
|
||||
}
|
||||
|
||||
private class Controller2Callback extends MediaController2.ControllerCallback {
|
||||
@Override
|
||||
public void onConnected(MediaController2 controller,
|
||||
@@ -666,6 +659,20 @@ public class MediaCommunicationService extends SystemService {
|
||||
service.onSessionDied(Session2Record.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackActiveChanged(
|
||||
@NonNull MediaController2 controller,
|
||||
boolean playbackActive) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "playback active changed, " + mSessionToken + ", active="
|
||||
+ playbackActive);
|
||||
}
|
||||
MediaCommunicationService service = mServiceRef.get();
|
||||
if (service != null) {
|
||||
service.onSessionPlaybackStateChanged(Session2Record.this, playbackActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright 2021 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.media.Session2Token;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.server.media.MediaCommunicationService.Session2Record;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
//TODO: Define the priority specifically.
|
||||
/**
|
||||
* Keeps track of media sessions and their priority for notifications, media
|
||||
* button dispatch, etc.
|
||||
* Higher priority session has more chance to be selected as media button session,
|
||||
* which receives the media button events.
|
||||
*/
|
||||
class SessionPriorityList {
|
||||
private static final String TAG = "SessionPriorityList";
|
||||
private final Object mLock = new Object();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final List<Session2Record> mSessions = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
private Session2Record mMediaButtonSession;
|
||||
@Nullable
|
||||
private Session2Record mCachedVolumeSession;
|
||||
|
||||
//TODO: integrate AudioPlayerStateMonitor
|
||||
|
||||
public void addSession(Session2Record record) {
|
||||
synchronized (mLock) {
|
||||
mSessions.add(record);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSession(Session2Record record) {
|
||||
synchronized (mLock) {
|
||||
mSessions.remove(record);
|
||||
}
|
||||
if (record == mMediaButtonSession) {
|
||||
updateMediaButtonSession(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroyAllSessions() {
|
||||
synchronized (mLock) {
|
||||
for (Session2Record session : mSessions) {
|
||||
session.close();
|
||||
}
|
||||
mSessions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean destroySessionsByUserId(int userId) {
|
||||
boolean changed = false;
|
||||
synchronized (mLock) {
|
||||
for (int i = mSessions.size() - 1; i >= 0; i--) {
|
||||
Session2Record session = mSessions.get(i);
|
||||
if (session.getUserId() == userId) {
|
||||
mSessions.remove(i);
|
||||
session.close();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public List<Session2Token> getAllTokens() {
|
||||
List<Session2Token> sessions = new ArrayList<>();
|
||||
synchronized (mLock) {
|
||||
for (Session2Record session : mSessions) {
|
||||
sessions.add(session.getSessionToken());
|
||||
}
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
||||
public List<Session2Token> getTokensByUserId(int userId) {
|
||||
List<Session2Token> sessions = new ArrayList<>();
|
||||
synchronized (mLock) {
|
||||
for (Session2Record session : mSessions) {
|
||||
if (session.getUserId() == userId) {
|
||||
sessions.add(session.getSessionToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
||||
/** Gets the media button session which receives the media button events. */
|
||||
@Nullable
|
||||
public Session2Record getMediaButtonSession() {
|
||||
return mMediaButtonSession;
|
||||
}
|
||||
|
||||
/** Gets the media volume session which receives the volume key events. */
|
||||
@Nullable
|
||||
public Session2Record getMediaVolumeSession() {
|
||||
//TODO: if null, calculate it.
|
||||
return mCachedVolumeSession;
|
||||
}
|
||||
|
||||
public boolean contains(Session2Record session) {
|
||||
synchronized (mLock) {
|
||||
return mSessions.contains(session);
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlaybackStateChanged(Session2Record session, boolean promotePriority) {
|
||||
if (promotePriority) {
|
||||
synchronized (mLock) {
|
||||
if (mSessions.remove(session)) {
|
||||
mSessions.add(0, session);
|
||||
} else {
|
||||
Log.w(TAG, "onPlaybackStateChanged: Ignoring unknown session");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (session.checkPlaybackActiveState(false)) {
|
||||
// Just clear the cached volume session when a state goes inactive
|
||||
mCachedVolumeSession = null;
|
||||
}
|
||||
|
||||
// In most cases, playback state isn't needed for finding the media button session,
|
||||
// but we only use it as a hint if an app has multiple local media sessions.
|
||||
// In that case, we pick the media session whose PlaybackState matches
|
||||
// the audio playback configuration.
|
||||
if (mMediaButtonSession != null
|
||||
&& mMediaButtonSession.getSessionToken().getUid()
|
||||
== session.getSessionToken().getUid()) {
|
||||
Session2Record newMediaButtonSession =
|
||||
findMediaButtonSession(mMediaButtonSession.getSessionToken().getUid());
|
||||
if (newMediaButtonSession != mMediaButtonSession) {
|
||||
// Check if the policy states that this session should not be updated as a media
|
||||
// button session.
|
||||
updateMediaButtonSession(newMediaButtonSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMediaButtonSession(@Nullable Session2Record newSession) {
|
||||
mMediaButtonSession = newSession;
|
||||
//TODO: invoke callbacks for media button session changed listeners
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the media button session with the given {@param uid}.
|
||||
* If the app has multiple media sessions, the media session whose playback state is not null
|
||||
* and matches the audio playback state becomes the media button session. Otherwise the top
|
||||
* priority session becomes the media button session.
|
||||
*
|
||||
* @return The media button session. Returns {@code null} if the app doesn't have a media
|
||||
* session.
|
||||
*/
|
||||
@Nullable
|
||||
private Session2Record findMediaButtonSession(int uid) {
|
||||
Session2Record mediaButtonSession = null;
|
||||
synchronized (mLock) {
|
||||
for (Session2Record session : mSessions) {
|
||||
if (uid != session.getSessionToken().getUid()) {
|
||||
continue;
|
||||
}
|
||||
// TODO: check audio player state monitor
|
||||
if (mediaButtonSession == null) {
|
||||
// Pick the top priority session as a default.
|
||||
mediaButtonSession = session;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mediaButtonSession;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user