Allow to register multiple callbacks in MediaSessionManager

Bug: 126758528
Test: manually
Change-Id: I613593a88c115a5ac694b8c984f3ae36e9cc1c78
Merged-In: I613593a88c115a5ac694b8c984f3ae36e9cc1c78
(cherry picked from commit 14ddf75c473a6cea391bd74d1638d9d28bcc9b01)
diff --git a/api/system-current.txt b/api/system-current.txt
index ebfd55e..b6512de 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3864,8 +3864,18 @@
 package android.media.session {
 
   public final class MediaSessionManager {
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void registerCallback(@NonNull android.media.session.MediaSessionManager.Callback, @Nullable android.os.Handler);
     method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
     method @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, @Nullable android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void unregisterCallback(@NonNull android.media.session.MediaSessionManager.Callback);
+  }
+
+  public abstract static class MediaSessionManager.Callback {
+    ctor public MediaSessionManager.Callback();
+    method public abstract void onAddressedPlayerChanged(android.media.session.MediaSession.Token);
+    method public abstract void onAddressedPlayerChanged(android.content.ComponentName);
+    method public abstract void onMediaKeyEventDispatched(android.view.KeyEvent, android.media.session.MediaSession.Token);
+    method public abstract void onMediaKeyEventDispatched(android.view.KeyEvent, android.content.ComponentName);
   }
 
   public static interface MediaSessionManager.OnMediaKeyListener {
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index a67a37e..01e6ed5 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -62,7 +62,8 @@
     // For PhoneWindowManager to precheck media keys
     boolean isGlobalPriorityActive();
 
-    void setCallback(in ICallback callback);
+    void registerCallback(in ICallback callback);
+    void unregisterCallback(in ICallback callback);
     void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener);
     void setOnMediaKeyListener(in IOnMediaKeyListener listener);
 
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 9685c4b..ecfb818 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -46,7 +46,9 @@
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -72,6 +74,7 @@
      * @hide
      */
     public static final int RESULT_MEDIA_KEY_HANDLED = 1;
+    private final ISessionManager mService;
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
@@ -80,13 +83,21 @@
     @GuardedBy("mLock")
     private final ArrayMap<OnSession2TokensChangedListener, Session2TokensChangedWrapper>
             mSession2TokensListeners = new ArrayMap<>();
-    private final ISessionManager mService;
+    @GuardedBy("mLock")
+    private final CallbackStub mCbStub = new CallbackStub();
+    @GuardedBy("mLock")
+    private final Map<Callback, Handler> mCallbacks = new HashMap<>();
+    @GuardedBy("mLock")
+    private MediaSession.Token mCurMediaButtonSession;
+    @GuardedBy("mLock")
+    private ComponentName mCurMediaButtonReceiver;
 
     private Context mContext;
-
-    private CallbackImpl mCallback;
     private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
     private OnMediaKeyListenerImpl mOnMediaKeyListener;
+    // TODO: Remove mLegacyCallback once Bluetooth app stop calling setCallback() method.
+    @GuardedBy("mLock")
+    private Callback mLegacyCallback;
 
     /**
      * @hide
@@ -752,18 +763,71 @@
      *            if the callback should be invoked on the calling thread's looper.
      * @hide
      */
+    // TODO: Remove this method once Bluetooth app stop calling it.
     public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
         synchronized (mLock) {
+            if (mLegacyCallback != null) {
+                unregisterCallback(mLegacyCallback);
+            }
+            mLegacyCallback = callback;
+            if (callback != null) {
+                registerCallback(callback, handler);
+            }
+        }
+    }
+
+    /**
+     * Register a {@link Callback}.
+     *
+     * @param callback A {@link Callback}.
+     * @param handler The handler on which the callback should be invoked, or {@code null}
+     *            if the callback should be invoked on the calling thread's looper.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
+        if (callback == null) {
+            throw new NullPointerException("callback shouldn't be null");
+        }
+        synchronized (mLock) {
             try {
-                if (callback == null) {
-                    mCallback = null;
-                    mService.setCallback(null);
-                } else {
-                    if (handler == null) {
-                        handler = new Handler();
-                    }
-                    mCallback = new CallbackImpl(callback, handler);
-                    mService.setCallback(mCallback);
+                if (handler == null) {
+                    handler = new Handler();
+                }
+                mCallbacks.put(callback, handler);
+                if (mCurMediaButtonSession != null) {
+                    handler.post(() -> callback.onAddressedPlayerChanged(mCurMediaButtonSession));
+                } else if (mCurMediaButtonReceiver != null) {
+                    handler.post(() -> callback.onAddressedPlayerChanged(mCurMediaButtonReceiver));
+                }
+
+                if (mCallbacks.size() == 1) {
+                    mService.registerCallback(mCbStub);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to set media key callback", e);
+            }
+        }
+    }
+
+    /**
+     * Unregister a {@link Callback}.
+     *
+     * @param callback A {@link Callback}.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void unregisterCallback(@NonNull Callback callback) {
+        if (callback == null) {
+            throw new NullPointerException("callback shouldn't be null");
+        }
+        synchronized (mLock) {
+            try {
+                mCallbacks.remove(callback);
+                if (mCallbacks.size() == 0) {
+                    mService.unregisterCallback(mCbStub);
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to set media key callback", e);
@@ -835,6 +899,7 @@
      * receive media key events.
      * @hide
      */
+    @SystemApi
     public static abstract class Callback {
         /**
          * Called when a media key event is dispatched to the media session
@@ -861,7 +926,7 @@
         /**
          * Called when the addressed player is changed to a media session.
          * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
-         * {@link #setCallback} if the addressed player exists.
+         * {@link #registerCallback} if the addressed player exists.
          *
          * @param sessionToken The media session's token.
          */
@@ -870,7 +935,7 @@
         /**
          * Called when the addressed player is changed to the media button receiver.
          * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
-         * {@link #setCallback} if the addressed player exists.
+         * {@link #registerCallback} if the addressed player exists.
          *
          * @param mediaButtonReceiver The media button receiver.
          */
@@ -1076,56 +1141,52 @@
         }
     }
 
-    private static final class CallbackImpl extends ICallback.Stub {
-        private final Callback mCallback;
-        private final Handler mHandler;
-
-        public CallbackImpl(@NonNull Callback callback, @NonNull Handler handler) {
-            mCallback = callback;
-            mHandler = handler;
-        }
+    private final class CallbackStub extends ICallback.Stub {
 
         @Override
         public void onMediaKeyEventDispatchedToMediaSession(KeyEvent event,
                 MediaSession.Token sessionToken) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mCallback.onMediaKeyEventDispatched(event, sessionToken);
+            synchronized (mLock) {
+                for (Map.Entry<Callback, Handler> e : mCallbacks.entrySet()) {
+                    e.getValue().post(
+                            () -> e.getKey().onMediaKeyEventDispatched(event, sessionToken));
                 }
-            });
+            }
         }
 
         @Override
         public void onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event,
                 ComponentName mediaButtonReceiver) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mCallback.onMediaKeyEventDispatched(event, mediaButtonReceiver);
+            synchronized (mLock) {
+                for (Map.Entry<Callback, Handler> e : mCallbacks.entrySet()) {
+                    e.getValue().post(
+                            () -> e.getKey().onMediaKeyEventDispatched(event, mediaButtonReceiver));
                 }
-            });
+            }
         }
 
         @Override
         public void onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mCallback.onAddressedPlayerChanged(sessionToken);
+            synchronized (mLock) {
+                mCurMediaButtonSession = sessionToken;
+                mCurMediaButtonReceiver = null;
+                for (Map.Entry<Callback, Handler> e : mCallbacks.entrySet()) {
+                    e.getValue().post(() -> e.getKey().onAddressedPlayerChanged(sessionToken));
                 }
-            });
+            }
         }
 
         @Override
         public void onAddressedPlayerChangedToMediaButtonReceiver(
                 ComponentName mediaButtonReceiver) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mCallback.onAddressedPlayerChanged(mediaButtonReceiver);
+            synchronized (mLock) {
+                mCurMediaButtonSession = null;
+                mCurMediaButtonReceiver = mediaButtonReceiver;
+                for (Map.Entry<Callback, Handler> e : mCallbacks.entrySet()) {
+                    e.getValue().post(() -> e.getKey().onAddressedPlayerChanged(
+                            mediaButtonReceiver));
                 }
-            });
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 5bd4b20..c05655a 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -92,6 +92,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -748,6 +749,8 @@
 
         private final int mFullUserId;
         private final MediaSessionStack mPriorityStack;
+        private final HashMap<IBinder, CallbackRecord> mCallbacks = new HashMap<>();
+
         private PendingIntent mLastMediaButtonReceiver;
         private ComponentName mRestoredMediaButtonReceiver;
         private int mRestoredMediaButtonReceiverComponentType;
@@ -761,7 +764,6 @@
 
         private IOnMediaKeyListener mOnMediaKeyListener;
         private int mOnMediaKeyListenerUid;
-        private ICallback mCallback;
 
         FullUserRecord(int fullUserId) {
             mFullUserId = fullUserId;
@@ -793,6 +795,24 @@
             }
         }
 
+        public void registerCallbackLocked(ICallback callback, int uid) {
+            IBinder cbBinder = callback.asBinder();
+            CallbackRecord cr = new CallbackRecord(callback, uid);
+            mCallbacks.put(cbBinder, cr);
+            try {
+                cbBinder.linkToDeath(cr, 0);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to register callback", e);
+                mCallbacks.remove(cbBinder);
+            }
+        }
+
+        public void unregisterCallbackLocked(ICallback callback) {
+            IBinder cbBinder = callback.asBinder();
+            CallbackRecord cr = mCallbacks.remove(cbBinder);
+            cbBinder.unlinkToDeath(cr, 0);
+        }
+
         public void dumpLocked(PrintWriter pw, String prefix) {
             pw.print(prefix + "Record for full_user=" + mFullUserId);
             // Dump managed profile user ids associated with this user.
@@ -811,7 +831,10 @@
             pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
             pw.println(indent + "Media key listener package: "
                     + getCallingPackageName(mOnMediaKeyListenerUid));
-            pw.println(indent + "Callback: " + mCallback);
+            pw.println(indent + "Callbacks: registered " + mCallbacks.size() + " callback(s)");
+            for (CallbackRecord cr : mCallbacks.values()) {
+                pw.println(indent + "  from " + getCallingPackageName(cr.uid));
+            }
             pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
             pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver);
             pw.println(indent + "Restored MediaButtonReceiverComponentType: "
@@ -871,21 +894,18 @@
                     mFullUserId);
         }
 
-        private void pushAddressedPlayerChangedLocked() {
-            if (mCallback == null) {
-                return;
-            }
+        private void pushAddressedPlayerChangedLocked(ICallback callback) {
             try {
                 MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
                 if (mediaButtonSession != null) {
-                    mCallback.onAddressedPlayerChangedToMediaSession(
+                    callback.onAddressedPlayerChangedToMediaSession(
                             mediaButtonSession.getSessionToken());
                 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
-                    mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
+                    callback.onAddressedPlayerChangedToMediaButtonReceiver(
                             mCurrentFullUserRecord.mLastMediaButtonReceiver
                                     .getIntent().getComponent());
                 } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
-                    mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
+                    callback.onAddressedPlayerChangedToMediaButtonReceiver(
                             mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
                 }
             } catch (RemoteException e) {
@@ -893,6 +913,12 @@
             }
         }
 
+        private void pushAddressedPlayerChangedLocked() {
+            for (CallbackRecord cr : mCallbacks.values()) {
+                pushAddressedPlayerChangedLocked(cr.callback);
+            }
+        }
+
         private MediaSessionRecord getMediaButtonSessionLocked() {
             return isGlobalPriorityActiveLocked()
                     ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
@@ -926,6 +952,23 @@
             // Pick legacy behavior for BroadcastReceiver or unknown.
             return COMPONENT_TYPE_BROADCAST;
         }
+
+        final class CallbackRecord implements IBinder.DeathRecipient {
+            public final ICallback callback;
+            public final int uid;
+
+            CallbackRecord(ICallback callback, int uid) {
+                this.callback = callback;
+                this.uid = uid;
+            }
+
+            @Override
+            public void binderDied() {
+                synchronized (mLock) {
+                    mCallbacks.remove(callback.asBinder());
+                }
+            }
+        }
     }
 
     final class SessionsListenerRecord implements IBinder.DeathRecipient {
@@ -1305,44 +1348,53 @@
         }
 
         @Override
-        public void setCallback(ICallback callback) {
+        public void registerCallback(final ICallback callback) {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(uid);
             final long token = Binder.clearCallingIdentity();
             try {
-                if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
-                    throw new SecurityException("Only Bluetooth service processes can set"
-                            + " Callback");
+                if (!hasMediaControlPermission(pid, uid)) {
+                    throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+                            + "  register Callback");
                 }
                 synchronized (mLock) {
-                    int userId = UserHandle.getUserId(uid);
                     FullUserRecord user = getFullUserRecordLocked(userId);
                     if (user == null || user.mFullUserId != userId) {
-                        Log.w(TAG, "Only the full user can set the callback"
+                        Log.w(TAG, "Only the full user can register the callback"
                                 + ", userId=" + userId);
                         return;
                     }
-                    user.mCallback = callback;
-                    Log.d(TAG, "The callback " + user.mCallback
-                            + " is set by " + getCallingPackageName(uid));
-                    if (user.mCallback == null) {
+                    user.registerCallbackLocked(callback, uid);
+                    Log.d(TAG, "The callback (" + callback.asBinder()
+                            + ") is registered by " + getCallingPackageName(uid));
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void unregisterCallback(final ICallback callback) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(uid);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (!hasMediaControlPermission(pid, uid)) {
+                    throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+                            + "  unregister Callback");
+                }
+                synchronized (mLock) {
+                    FullUserRecord user = getFullUserRecordLocked(userId);
+                    if (user == null || user.mFullUserId != userId) {
+                        Log.w(TAG, "Only the full user can unregister the callback"
+                                + ", userId=" + userId);
                         return;
                     }
-                    try {
-                        user.mCallback.asBinder().linkToDeath(
-                                new IBinder.DeathRecipient() {
-                                    @Override
-                                    public void binderDied() {
-                                        synchronized (mLock) {
-                                            user.mCallback = null;
-                                        }
-                                    }
-                                }, 0);
-                        user.pushAddressedPlayerChangedLocked();
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "Failed to set callback", e);
-                        user.mCallback = null;
-                    }
+                    user.unregisterCallbackLocked(callback);
+                    Log.d(TAG, "The callback (" + callback.asBinder()
+                            + ") is unregistered by " + getCallingPackageName(uid));
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -1771,6 +1823,7 @@
         public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
                 throws RemoteException {
             final int uid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(uid);
             final long token = Binder.clearCallingIdentity();
             try {
                 // Don't perform sanity check between controllerPackageName and controllerUid.
@@ -1781,8 +1834,8 @@
                 // Note that we can use Context#getOpPackageName() instead of
                 // Context#getPackageName() for getting package name that matches with the PID/UID,
                 // but it doesn't tell which package has created the MediaController, so useless.
-                return hasMediaControlPermission(UserHandle.getUserId(uid), controllerPackageName,
-                        controllerPid, controllerUid);
+                return hasMediaControlPermission(controllerPid, controllerUid)
+                        || hasEnabledNotificationListener(userId, controllerPackageName);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1808,13 +1861,7 @@
             return resolvedUserId;
         }
 
-        private boolean hasMediaControlPermission(int resolvedUserId, String packageName,
-                int pid, int uid) throws RemoteException {
-            // Allow API calls from the System UI and Settings
-            if (hasStatusBarServicePermission(pid, uid)) {
-                return true;
-            }
-
+        private boolean hasMediaControlPermission(int pid, int uid) {
             // Check if it's system server or has MEDIA_CONTENT_CONTROL.
             // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
             // check here.
@@ -1823,11 +1870,15 @@
                     == PackageManager.PERMISSION_GRANTED) {
                 return true;
             } else if (DEBUG) {
-                Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
+                Log.d(TAG, "uid(" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
             }
+            return false;
+        }
 
+        private boolean hasEnabledNotificationListener(int resolvedUserId, String packageName)
+                throws RemoteException {
             // You may not access another user's content as an enabled listener.
-            final int userId = UserHandle.getUserId(uid);
+            final int userId = UserHandle.getUserId(resolvedUserId);
             if (resolvedUserId != userId) {
                 return false;
             }
@@ -1845,7 +1896,7 @@
                 }
             }
             if (DEBUG) {
-                Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
+                Log.d(TAG, packageName + " (uid=" + resolvedUserId + ") doesn't have an enabled "
                         + "notification listener");
             }
             return false;
@@ -1950,13 +2001,14 @@
                 session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
                         needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                         mKeyEventReceiver);
-                if (mCurrentFullUserRecord.mCallback != null) {
-                    try {
-                        mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
+                try {
+                    for (FullUserRecord.CallbackRecord cr
+                            : mCurrentFullUserRecord.mCallbacks.values()) {
+                        cr.callback.onMediaKeyEventDispatchedToMediaSession(
                                 keyEvent, session.getSessionToken());
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "Failed to send callback", e);
                     }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to send callback", e);
                 }
             } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null
                     || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
@@ -1980,12 +2032,12 @@
                         receiver.send(mContext,
                                 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                                 mediaButtonIntent, mKeyEventReceiver, mHandler);
-                        if (mCurrentFullUserRecord.mCallback != null) {
-                            ComponentName componentName = mCurrentFullUserRecord
-                                    .mLastMediaButtonReceiver.getIntent().getComponent();
-                            if (componentName != null) {
-                                mCurrentFullUserRecord.mCallback
-                                        .onMediaKeyEventDispatchedToMediaButtonReceiver(
+                        ComponentName componentName = mCurrentFullUserRecord
+                                .mLastMediaButtonReceiver.getIntent().getComponent();
+                        if (componentName != null) {
+                            for (FullUserRecord.CallbackRecord cr
+                                    : mCurrentFullUserRecord.mCallbacks.values()) {
+                                cr.callback.onMediaKeyEventDispatchedToMediaButtonReceiver(
                                                 keyEvent, componentName);
                             }
                         }
@@ -2018,9 +2070,9 @@
                             Log.w(TAG, "Error sending media button to the restored intent "
                                     + receiver + ", type=" + componentType, e);
                         }
-                        if (mCurrentFullUserRecord.mCallback != null) {
-                            mCurrentFullUserRecord.mCallback
-                                    .onMediaKeyEventDispatchedToMediaButtonReceiver(
+                        for (FullUserRecord.CallbackRecord cr
+                                : mCurrentFullUserRecord.mCallbacks.values()) {
+                            cr.callback.onMediaKeyEventDispatchedToMediaButtonReceiver(
                                             keyEvent, receiver);
                         }
                     }