Merge "Introduce SessionPriorityList into MCS"

This commit is contained in:
Kyunglyul Hyun
2021-09-10 14:21:32 +00:00
committed by Android (Google) Code Review
3 changed files with 240 additions and 40 deletions

View File

@@ -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.
*/

View File

@@ -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);
}
}
}
}
}

View File

@@ -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;
}
}