Merge "[Media ML] Add button session management related classes"

This commit is contained in:
TreeHugger Robot
2020-02-04 08:26:56 +00:00
committed by Android (Google) Code Review
8 changed files with 205 additions and 17 deletions

View File

@@ -61,6 +61,7 @@ import java.util.concurrent.Executor;
* @see MediaSession
* @see MediaController
*/
// TODO: (jinpark) Add API for getting and setting session policies from MediaSessionService.
@SystemService(Context.MEDIA_SESSION_SERVICE)
public final class MediaSessionManager {
private static final String TAG = "SessionManager";

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2020 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.NonNull;
import android.annotation.Nullable;
import android.media.session.MediaSession;
import android.view.KeyEvent;
/**
* Provides a way to customize behavior for media key events.
*/
public interface MediaKeyDispatcher {
/**
* Implement this to customize the logic for which MediaSession should consume which key event.
*
* @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons.
* @param asSystemService {@code true} if the event came from the system service via hardware
* devices. {@code false} if the event came from the app process through key injection.
* @return a {@link MediaSession.Token} instance that should consume the given key event.
*/
@Nullable
MediaSession.Token getSessionForKeyEvent(@NonNull KeyEvent keyEvent,
boolean asSystemService);
}

View File

@@ -50,15 +50,18 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
private final MediaSessionService mService;
@GuardedBy("mLock")
private boolean mIsConnected;
@GuardedBy("mLock")
private int mPolicies;
public MediaSession2Record(Session2Token sessionToken, MediaSessionService service,
Looper handlerLooper) {
Looper handlerLooper, int policies) {
mSessionToken = sessionToken;
mService = service;
mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper));
mController = new MediaController2.Builder(service.getContext(), sessionToken)
.setControllerCallback(mHandlerExecutor, new Controller2Callback())
.build();
mPolicies = policies;
}
@Override
@@ -129,6 +132,21 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
return false;
}
@Override
public int getSessionPolicies() {
synchronized (mLock) {
return mPolicies;
}
}
@Override
public void setSessionPolicies(int policies) {
synchronized (mLock) {
mPolicies = policies;
}
}
@Override
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "token=" + mSessionToken);

View File

@@ -159,9 +159,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
private long mDuration = -1;
private String mMetadataDescription;
private int mPolicies;
public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
ISessionCallback cb, String tag, Bundle sessionInfo,
MediaSessionService service, Looper handlerLooper) throws RemoteException {
MediaSessionService service, Looper handlerLooper, int policies)
throws RemoteException {
mOwnerPid = ownerPid;
mOwnerUid = ownerUid;
mUserId = userId;
@@ -178,6 +181,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
mAudioAttrs = DEFAULT_ATTRIBUTES;
mPolicies = policies;
// May throw RemoteException if the session app is killed.
mSessionCb.mCb.asBinder().linkToDeath(this, 0);
@@ -437,6 +441,20 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
cb);
}
@Override
public int getSessionPolicies() {
synchronized (mLock) {
return mPolicies;
}
}
@Override
public void setSessionPolicies(int policies) {
synchronized (mLock) {
mPolicies = policies;
}
}
@Override
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + mTag + " " + this);
@@ -808,6 +826,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
@Override
public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException {
if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) == 1) {
return;
}
mMediaButtonReceiver = pi;
final long token = Binder.clearCallingIdentity();
try {

View File

@@ -20,6 +20,8 @@ import android.media.AudioManager;
import android.os.ResultReceiver;
import android.view.KeyEvent;
import com.android.server.media.SessionPolicyProvider.SessionPolicy;
import java.io.PrintWriter;
/**
@@ -127,6 +129,18 @@ public interface MediaSessionRecordImpl extends AutoCloseable {
boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
KeyEvent ke, int sequenceId, ResultReceiver cb);
/**
* Get session policies from custom policy provider set when MediaSessionRecord is instantiated.
* If custom policy does not exist, will return null.
*/
@SessionPolicy
int getSessionPolicies();
/**
* Overwrite session policies that have been set when MediaSessionRecord is instantiated.
*/
void setSessionPolicies(@SessionPolicy int policies);
/**
* Dumps internal state
*

View File

@@ -141,6 +141,9 @@ public class MediaSessionService extends SystemService implements Monitor {
final RemoteCallbackList<IRemoteVolumeController> mRemoteVolumeControllers =
new RemoteCallbackList<>();
private SessionPolicyProvider mCustomSessionPolicyProvider;
private MediaKeyDispatcher mCustomMediaKeyDispatcher;
public MediaSessionService(Context context) {
super(context);
mContext = context;
@@ -179,6 +182,9 @@ public class MediaSessionService extends SystemService implements Monitor {
mSettingsObserver.observe();
mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_LEANBACK);
// TODO: (jinpark) check if config value for custom MediaKeyDispatcher and
// SessionPolicyProvider have been overlayed and instantiate using reflection.
updateUser();
}
@@ -555,7 +561,8 @@ public class MediaSessionService extends SystemService implements Monitor {
* 4. It needs to be added to the relevant user record.
*/
private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo,
int policies) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null) {
@@ -566,7 +573,8 @@ public class MediaSessionService extends SystemService implements Monitor {
final MediaSessionRecord session;
try {
session = new MediaSessionRecord(callerPid, callerUid, userId,
callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper());
callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper(),
policies);
} catch (RemoteException e) {
throw new RuntimeException("Media Session owner died prematurely.", e);
}
@@ -1127,8 +1135,11 @@ public class MediaSessionService extends SystemService implements Monitor {
if (cb == null) {
throw new IllegalArgumentException("Controller callback cannot be null");
}
int policies = (mCustomSessionPolicyProvider != null)
? mCustomSessionPolicyProvider.getSessionPoliciesForApplication(
uid, packageName) : 0;
return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag,
sessionInfo).getSessionBinder();
sessionInfo, policies).getSessionBinder();
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1148,7 +1159,7 @@ public class MediaSessionService extends SystemService implements Monitor {
+ " but actually=" + sessionToken.getUid());
}
MediaSession2Record record = new MediaSession2Record(
sessionToken, MediaSessionService.this, mHandler.getLooper());
sessionToken, MediaSessionService.this, mHandler.getLooper(), 0);
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
user.mPriorityStack.addSession(record);
@@ -1308,12 +1319,11 @@ public class MediaSessionService extends SystemService implements Monitor {
* ACTION_MEDIA_BUTTON intent to the rest of the system.
*
* @param packageName The caller package
* @param asSystemService {@code true} if the event sent to the session as if it was come
* from the system service instead of the app process. This helps sessions to
* distinguish between the key injection by the app and key events from the
* hardware devices. Should be used only when the volume key events aren't handled
* by foreground activity. {@code false} otherwise to tell session about the real
* caller.
* @param asSystemService {@code true} if the event sent to the session came from the
* service instead of the app process. This helps sessions to distinguish between
* the key injection by the app and key events from the hardware devices. Should be
* used only when the hardware key events aren't handled by foreground activity.
* {@code false} otherwise to tell session about the real caller.
* @param keyEvent a non-null KeyEvent whose key code is one of the
* supported media buttons
* @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
@@ -2115,14 +2125,27 @@ public class MediaSessionService extends SystemService implements Monitor {
// TODO(jaewan): Implement
return;
}
MediaSessionRecord session =
(MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked();
MediaSessionRecord session = null;
// Retrieve custom session for key event if it exists.
if (mCustomMediaKeyDispatcher != null) {
MediaSession.Token token =
mCustomMediaKeyDispatcher.getSessionForKeyEvent(keyEvent, asSystemService);
if (token != null) {
session = getMediaSessionRecordLocked(token);
}
}
if (session == null) {
session = (MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked();
}
if (session != null) {
if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Sending " + keyEvent + " to " + session);
}
if (needWakeLock) {
mKeyEventReceiver.aquireWakeLockLocked();
mKeyEventReceiver.acquireWakeLockLocked();
}
// If we don't need a wakelock use -1 as the id so we won't release it later.
session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
@@ -2140,7 +2163,7 @@ public class MediaSessionService extends SystemService implements Monitor {
} else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
|| mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
if (needWakeLock) {
mKeyEventReceiver.aquireWakeLockLocked();
mKeyEventReceiver.acquireWakeLockLocked();
}
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -2349,7 +2372,7 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
public void aquireWakeLockLocked() {
public void acquireWakeLockLocked() {
if (mRefCount == 0) {
mMediaEventWakeLock.acquire();
}

View File

@@ -274,6 +274,15 @@ class MediaSessionStack {
}
private void updateMediaButtonSession(MediaSessionRecordImpl newMediaButtonSession) {
// Check if the policy states that this session should not be updated as a media button
// session.
if (newMediaButtonSession != null) {
int policies = newMediaButtonSession.getSessionPolicies();
if ((policies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_SESSION) == 1) {
return;
}
}
MediaSessionRecordImpl oldMediaButtonSession = mMediaButtonSession;
mMediaButtonSession = newMediaButtonSession;
mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2020 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.IntDef;
import android.annotation.NonNull;
import android.media.session.MediaSession;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Interface for customizing {@link MediaSessionService}
*/
public interface SessionPolicyProvider {
@IntDef(value = {
SESSION_POLICY_IGNORE_BUTTON_RECEIVER,
SESSION_POLICY_IGNORE_BUTTON_SESSION
})
@Retention(RetentionPolicy.SOURCE)
@interface SessionPolicy {}
/**
* Policy to ignore media button receiver, to not revive the media app when its media session is
* released or the app is dead.
*
* @see MediaSession#setMediaButtonReceiver
*/
int SESSION_POLICY_IGNORE_BUTTON_RECEIVER = 1 << 0;
/**
* Policy to ignore sessions that should not respond to media key events via
* {@link MediaSessionService}. A typical use case is to explicitly
* ignore sessions that should not respond to media key events even if their playback state has
* changed most recently.
*/
int SESSION_POLICY_IGNORE_BUTTON_SESSION = 1 << 1;
/**
* Use this to statically set policies for sessions when they are created.
* Use android.media.session.MediaSessionManager#setSessionPolicies(MediaSession.Token, int)
* to dynamically change policies at runtime.
*
* @param uid
* @param packageName
* @return list of policies
*/
@SessionPolicy int getSessionPoliciesForApplication(int uid, @NonNull String packageName);
}