Add MCM#dispatchMediaKeyEvent

Make a room for MediaCommunicationService to handle
key events.

Bug:193757214
Test: atest CtsMediaTestCases

Change-Id: I5236ab549f9f06b98d8229b45050df791c26c309
This commit is contained in:
Kyunglyul Hyun
2021-07-28 05:54:30 +00:00
parent ce4836de9c
commit 4e05164ec1
5 changed files with 101 additions and 12 deletions

View File

@@ -18,6 +18,7 @@ package android.media;
import android.media.Session2Token;
import android.media.IMediaCommunicationServiceCallback;
import android.media.MediaParceledListSlice;
import android.view.KeyEvent;
/** {@hide} */
interface IMediaCommunicationService {
@@ -25,6 +26,8 @@ interface IMediaCommunicationService {
boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid);
MediaParceledListSlice getSession2Tokens(int userId);
void dispatchMediaKeyEvent(String packageName, in KeyEvent keyEvent, boolean asSystemService);
void registerCallback(IMediaCommunicationServiceCallback callback, String packageName);
void unregisterCallback(IMediaCommunicationServiceCallback callback);
}

View File

@@ -2,6 +2,7 @@
package android.media {
public class MediaCommunicationManager {
method public void dispatchMediaKeyEvent(@NonNull android.view.KeyEvent, boolean);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void registerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaCommunicationManager.SessionCallback);
method public void unregisterSessionCallback(@NonNull android.media.MediaCommunicationManager.SessionCallback);
}

View File

@@ -32,6 +32,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.service.media.MediaBrowserService;
import android.util.Log;
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
import com.android.modules.annotation.MinSdk;
@@ -63,7 +64,8 @@ public class MediaCommunicationManager {
private static final int CURRENT_VERSION = VERSION_1;
private final Context mContext;
private final IMediaCommunicationService mService;
// Do not access directly use getService().
private IMediaCommunicationService mService;
private final Object mLock = new Object();
private final CopyOnWriteArrayList<SessionCallbackRecord> mTokenCallbackRecords =
@@ -80,10 +82,6 @@ public class MediaCommunicationManager {
throw new UnsupportedOperationException("Android version must be S or greater.");
}
mContext = context;
mService = IMediaCommunicationService.Stub.asInterface(
MediaFrameworkInitializer.getMediaServiceManager()
.getMediaCommunicationServiceRegisterer()
.get());
}
/**
@@ -105,7 +103,7 @@ public class MediaCommunicationManager {
throw new IllegalArgumentException("token's type should be TYPE_SESSION");
}
try {
mService.notifySession2Created(token);
getService().notifySession2Created(token);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -130,7 +128,7 @@ public class MediaCommunicationManager {
return false;
}
try {
return mService.isTrusted(
return getService().isTrusted(
userInfo.getPackageName(), userInfo.getPid(), userInfo.getUid());
} catch (RemoteException e) {
Log.w(TAG, "Cannot communicate with the service.", e);
@@ -182,7 +180,7 @@ public class MediaCommunicationManager {
MediaCommunicationServiceCallbackStub callbackStub =
new MediaCommunicationServiceCallbackStub();
try {
mService.registerCallback(callbackStub, mContext.getPackageName());
getService().registerCallback(callbackStub, mContext.getPackageName());
mCallbackStub = callbackStub;
} catch (RemoteException ex) {
Log.e(TAG, "Failed to register callback.", ex);
@@ -205,7 +203,7 @@ public class MediaCommunicationManager {
synchronized (mLock) {
if (mCallbackStub != null && mTokenCallbackRecords.isEmpty()) {
try {
mService.unregisterCallback(mCallbackStub);
getService().unregisterCallback(mCallbackStub);
} catch (RemoteException ex) {
Log.e(TAG, "Failed to unregister callback.", ex);
}
@@ -214,9 +212,19 @@ public class MediaCommunicationManager {
}
}
private IMediaCommunicationService getService() {
if (mService == null) {
mService = IMediaCommunicationService.Stub.asInterface(
MediaFrameworkInitializer.getMediaServiceManager()
.getMediaCommunicationServiceRegisterer()
.get());
}
return mService;
}
private List<Session2Token> getSession2Tokens(int userId) {
try {
MediaParceledListSlice slice = mService.getSession2Tokens(userId);
MediaParceledListSlice slice = getService().getSession2Tokens(userId);
return slice == null ? Collections.emptyList() : slice.getList();
} catch (RemoteException e) {
Log.e(TAG, "Failed to get session tokens", e);
@@ -224,6 +232,25 @@ public class MediaCommunicationManager {
return Collections.emptyList();
}
/**
* Sends a media key event. The receiver will be selected automatically.
*
* @param keyEvent the key event to send
* @param asSystemService if {@code true}, the event sent to the session as if it was come from
* the system service instead of the app process.
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean asSystemService) {
Objects.requireNonNull(keyEvent, "keyEvent shouldn't be null");
try {
getService().dispatchMediaKeyEvent(mContext.getPackageName(),
keyEvent, asSystemService);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send key event.", e);
}
}
/**
* Callback for listening to changes to the sessions.
* @see #registerSessionCallback(Executor, SessionCallback)

View File

@@ -31,6 +31,7 @@ import android.media.MediaController2;
import android.media.MediaParceledListSlice;
import android.media.Session2CommandGroup;
import android.media.Session2Token;
import android.media.session.MediaSessionManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -42,6 +43,7 @@ import android.os.UserManager;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
@@ -60,7 +62,7 @@ import java.util.stream.Collectors;
* @hide
*/
public class MediaCommunicationService extends SystemService {
private static final String TAG = "MediaCommunicationService";
private static final String TAG = "MediaCommunicationSrv";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
final Context mContext;
@@ -77,6 +79,7 @@ public class MediaCommunicationService extends SystemService {
@GuardedBy("mLock")
final List<CallbackRecord> mCallbackRecords = new ArrayList<>();
final NotificationManager mNotificationManager;
MediaSessionManager mSessionManager;
public MediaCommunicationService(Context context) {
super(context);
@@ -90,6 +93,17 @@ public class MediaCommunicationService extends SystemService {
updateUser();
}
@Override
public void onBootPhase(int phase) {
super.onBootPhase(phase);
switch (phase) {
// This ensures MediaSessionService is started
case PHASE_BOOT_COMPLETED:
mSessionManager = mContext.getSystemService(MediaSessionManager.class);
break;
}
}
@Override
public void onUserStarting(@NonNull TargetUser user) {
if (DEBUG) Log.d(TAG, "onUserStarting: " + user);
@@ -267,6 +281,24 @@ public class MediaCommunicationService extends SystemService {
session.close();
}
static boolean isMediaSessionKey(int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
return true;
}
return false;
}
private class Stub extends IMediaCommunicationService.Stub {
@Override
public void notifySession2Created(Session2Token sessionToken) {
@@ -350,6 +382,29 @@ public class MediaCommunicationService extends SystemService {
}
}
@Override
public void dispatchMediaKeyEvent(String packageName, KeyEvent keyEvent,
boolean asSystemService) {
if (keyEvent == null || !isMediaSessionKey(keyEvent.getKeyCode())) {
Log.w(TAG, "Attempted to dispatch null or non-media key event.");
return;
}
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
//TODO: Dispatch key event to media session 2 if required
if (asSystemService) {
mSessionManager.dispatchMediaKeyEventAsSystemService(keyEvent);
} else {
mSessionManager.dispatchMediaKeyEvent(keyEvent, false);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void registerCallback(IMediaCommunicationServiceCallback callback,
String packageName) throws RemoteException {

View File

@@ -26,6 +26,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.media.MediaCommunicationManager;
import android.media.MediaMetadata;
import android.media.MediaMetadataEditor;
import android.media.MediaMetadataRetriever;
@@ -53,6 +54,7 @@ public class MediaSessionLegacyHelper {
private Context mContext;
private MediaSessionManager mSessionManager;
private MediaCommunicationManager mCommunicationManager;
private Handler mHandler = new Handler(Looper.getMainLooper());
// The legacy APIs use PendingIntents to register/unregister media button
// receivers and these are associated with RCC.
@@ -63,6 +65,7 @@ public class MediaSessionLegacyHelper {
mContext = context;
mSessionManager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
mCommunicationManager = context.getSystemService(MediaCommunicationManager.class);
}
@UnsupportedAppUsage
@@ -171,7 +174,7 @@ public class MediaSessionLegacyHelper {
Log.w(TAG, "Tried to send a null key event. Ignoring.");
return;
}
mSessionManager.dispatchMediaKeyEvent(keyEvent, needWakeLock);
mCommunicationManager.dispatchMediaKeyEvent(keyEvent, needWakeLock);
if (DEBUG) {
Log.d(TAG, "dispatched media key " + keyEvent);
}