From 4e05164ec10715a46f1a4cdbb2728cb079a641c9 Mon Sep 17 00:00:00 2001 From: Kyunglyul Hyun Date: Wed, 28 Jul 2021 05:54:30 +0000 Subject: [PATCH] Add MCM#dispatchMediaKeyEvent Make a room for MediaCommunicationService to handle key events. Bug:193757214 Test: atest CtsMediaTestCases Change-Id: I5236ab549f9f06b98d8229b45050df791c26c309 --- .../media/IMediaCommunicationService.aidl | 3 + .../framework/api/module-lib-current.txt | 1 + .../media/MediaCommunicationManager.java | 47 +++++++++++---- .../media/MediaCommunicationService.java | 57 ++++++++++++++++++- .../session/MediaSessionLegacyHelper.java | 5 +- 5 files changed, 101 insertions(+), 12 deletions(-) diff --git a/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl b/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl index fb3172b8c764e..e1c89e90b0f3f 100644 --- a/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl +++ b/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl @@ -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); } diff --git a/apex/media/framework/api/module-lib-current.txt b/apex/media/framework/api/module-lib-current.txt index eb6397a1826bf..7317f148b17b9 100644 --- a/apex/media/framework/api/module-lib-current.txt +++ b/apex/media/framework/api/module-lib-current.txt @@ -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); } diff --git a/apex/media/framework/java/android/media/MediaCommunicationManager.java b/apex/media/framework/java/android/media/MediaCommunicationManager.java index f39bcfb267bf5..b2528cd5516e3 100644 --- a/apex/media/framework/java/android/media/MediaCommunicationManager.java +++ b/apex/media/framework/java/android/media/MediaCommunicationManager.java @@ -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 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 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) diff --git a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java index ed31aa3d2a39f..03a2372d55c70 100644 --- a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java +++ b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java @@ -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 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 { diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java index 0d506f0eb52c3..728424efab679 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -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); }