Merge "[Media ML] Add button session management related classes"
This commit is contained in:
committed by
Android (Google) Code Review
commit
1a212544a2
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user