Merge "Add callback for AVRCP 1.6 support"
diff --git a/Android.mk b/Android.mk
index 45f3701..f544620 100644
--- a/Android.mk
+++ b/Android.mk
@@ -441,6 +441,7 @@
 	media/java/android/media/projection/IMediaProjectionManager.aidl \
 	media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl \
 	media/java/android/media/session/IActiveSessionsListener.aidl \
+	media/java/android/media/session/ICallback.aidl \
 	media/java/android/media/session/IOnMediaKeyListener.aidl \
 	media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl \
 	media/java/android/media/session/ISession.aidl \
diff --git a/media/java/android/media/session/ICallback.aidl b/media/java/android/media/session/ICallback.aidl
new file mode 100644
index 0000000..322bffa
--- /dev/null
+++ b/media/java/android/media/session/ICallback.aidl
@@ -0,0 +1,35 @@
+/* Copyright (C) 2016 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 android.media.session;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.media.session.MediaSession;
+import android.view.KeyEvent;
+
+/**
+ * @hide
+ */
+oneway interface ICallback {
+    void onMediaKeyEventDispatchedToMediaSession(in KeyEvent event,
+            in MediaSession.Token sessionToken);
+    void onMediaKeyEventDispatchedToMediaButtonReceiver(in KeyEvent event,
+            in ComponentName mediaButtonReceiver);
+
+    void onAddressedPlayerChangedToMediaSession(in MediaSession.Token sessionToken);
+    void onAddressedPlayerChangedToMediaButtonReceiver(in ComponentName mediaButtonReceiver);
+}
+
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 8a98773..5fcb430 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -18,8 +18,9 @@
 import android.content.ComponentName;
 import android.media.IRemoteVolumeController;
 import android.media.session.IActiveSessionsListener;
-import android.media.session.IOnVolumeKeyLongPressListener;
+import android.media.session.ICallback;
 import android.media.session.IOnMediaKeyListener;
+import android.media.session.IOnVolumeKeyLongPressListener;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
 import android.os.Bundle;
@@ -45,7 +46,7 @@
     // For PhoneWindowManager to precheck media keys
     boolean isGlobalPriorityActive();
 
+    void setCallback(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 1bfe6c5..83793ae 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -71,6 +71,7 @@
 
     private Context mContext;
 
+    private CallbackImpl mCallback;
     private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
     private OnMediaKeyListenerImpl mOnMediaKeyListener;
 
@@ -411,6 +412,36 @@
     }
 
     /**
+     * Set a {@link Callback}.
+     *
+     * <p>System can only have a single callback, and the callback can only be set by
+     * Bluetooth service process.
+     *
+     * @param callback A {@link Callback}. {@code null} to reset.
+     * @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
+     */
+    public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
+        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);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to set media key callback", e);
+            }
+        }
+    }
+
+    /**
      * Listens for changes to the list of active sessions. This can be added
      * using {@link #addOnActiveSessionsChangedListener}.
      */
@@ -448,6 +479,56 @@
         boolean onMediaKey(KeyEvent event);
     }
 
+    /**
+     * Callbacks for the media session service.
+     *
+     * <p>Called when a media key event is dispatched or the addressed player is changed.
+     * The addressed player is either the media session or the media button receiver that will
+     * receive media key events.
+     * @hide
+     */
+    public static abstract class Callback {
+        /**
+         * Called when a media key event is dispatched to the media session
+         * through the media session service.
+         *
+         * @param event Dispatched media key event.
+         * @param sessionToken The media session's token.
+         */
+        public abstract void onMediaKeyEventDispatched(KeyEvent event,
+                MediaSession.Token sessionToken);
+
+        /**
+         * Called when a media key event is dispatched to the media button receiver
+         * through the media session service.
+         * <p>MediaSessionService may broadcast key events to the media button receiver
+         * when reviving playback after the media session is released.
+         *
+         * @param event Dispatched media key event.
+         * @param mediaButtonReceiver The media button receiver.
+         */
+        public abstract void onMediaKeyEventDispatched(KeyEvent event,
+                ComponentName mediaButtonReceiver);
+
+        /**
+         * 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.
+         *
+         * @param sessionToken The media session's token.
+         */
+        public abstract void onAddressedPlayerChanged(MediaSession.Token sessionToken);
+
+        /**
+         * 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.
+         *
+         * @param mediaButtonReceiver The media button receiver.
+         */
+        public abstract void onAddressedPlayerChanged(ComponentName mediaButtonReceiver);
+    }
+
     private static final class SessionsChangedWrapper {
         private Context mContext;
         private OnActiveSessionsChangedListener mListener;
@@ -546,4 +627,57 @@
             });
         }
     }
+
+    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;
+        }
+
+        @Override
+        public void onMediaKeyEventDispatchedToMediaSession(KeyEvent event,
+                MediaSession.Token sessionToken) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onMediaKeyEventDispatched(event, sessionToken);
+                }
+            });
+        }
+
+        @Override
+        public void onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event,
+                ComponentName mediaButtonReceiver) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onMediaKeyEventDispatched(event, mediaButtonReceiver);
+                }
+            });
+        }
+
+        @Override
+        public void onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAddressedPlayerChanged(sessionToken);
+                }
+            });
+        }
+
+        @Override
+        public void onAddressedPlayerChangedToMediaButtonReceiver(
+                ComponentName mediaButtonReceiver) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.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 9ab685d..6349f21 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -37,6 +37,7 @@
 import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
 import android.media.session.IActiveSessionsListener;
+import android.media.session.ICallback;
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
 import android.media.session.ISession;
@@ -107,6 +108,7 @@
     private AudioManagerInternal mAudioManagerInternal;
     private ContentResolver mContentResolver;
     private SettingsObserver mSettingsObserver;
+    private ICallback mCallback;
 
     // List of user IDs running in the foreground.
     // Multiple users can be in the foreground if the work profile is on.
@@ -486,6 +488,7 @@
             if (size > 0 && records.get(0).isPlaybackActive(false)) {
                 rememberMediaButtonReceiverLocked(records.get(0));
             }
+            pushAddressedPlayerChangedLocked();
             ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
             for (int i = 0; i < size; i++) {
                 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
@@ -517,6 +520,52 @@
         }
     }
 
+    private MediaSessionRecord getMediaButtonSessionLocked() {
+        // If we don't have a media button receiver to fall back on
+        // include non-playing sessions for dispatching.
+        boolean useNotPlayingSessions = true;
+        for (int userId : mCurrentUserIdList) {
+            UserRecord ur = mUserRecords.get(userId);
+            if (ur.mLastMediaButtonReceiver != null
+                    || ur.mRestoredMediaButtonReceiver != null) {
+                useNotPlayingSessions = false;
+                break;
+            }
+        }
+        return mPriorityStack.getDefaultMediaButtonSession(
+                mCurrentUserIdList, useNotPlayingSessions);
+    }
+
+    private void pushAddressedPlayerChangedLocked() {
+        if (mCallback == null) {
+            return;
+        }
+        try {
+            MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
+            if (mediaButtonSession != null) {
+                mCallback.onAddressedPlayerChangedToMediaSession(
+                        new MediaSession.Token(mediaButtonSession.getControllerBinder()));
+            } else {
+                for (int userId : mCurrentUserIdList) {
+                    UserRecord user = mUserRecords.get(userId);
+                    if (user.mLastMediaButtonReceiver == null
+                            && user.mRestoredMediaButtonReceiver == null) {
+                        continue;
+                    }
+                    ComponentName componentName = user.mLastMediaButtonReceiver != null
+                            ? user.mLastMediaButtonReceiver.getIntent().getComponent()
+                            : user.mRestoredMediaButtonReceiver;
+                    mCallback.onAddressedPlayerChangedToMediaButtonReceiver(componentName);
+                    return;
+                }
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
+        }
+    }
+
+    // Remember media button receiver and keep it in the persistent storage.
+    // This should be called whenever there's no media session to receive media button event.
     private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
         PendingIntent receiver = record.getMediaButtonReceiver();
         UserRecord user = mUserRecords.get(record.getUserId());
@@ -839,6 +888,44 @@
         }
 
         @Override
+        public void setCallback(ICallback callback) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (uid != Process.BLUETOOTH_UID) {
+                    throw new SecurityException("Only Bluetooth service processes can set"
+                            + " Callback");
+                }
+                synchronized (mLock) {
+                    Log.d(TAG, "Callback + " + mCallback
+                            + " is set by " + getCallingPackageName(uid));
+                    mCallback = callback;
+                    if (mCallback == null) {
+                        return;
+                    }
+                    try {
+                        mCallback.asBinder().linkToDeath(
+                                new IBinder.DeathRecipient() {
+                                    @Override
+                                    public void binderDied() {
+                                        synchronized (mLock) {
+                                            mCallback = null;
+                                        }
+                                    }
+                                }, 0);
+                        pushAddressedPlayerChangedLocked();
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to set callback", e);
+                        mCallback = null;
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
@@ -1217,24 +1304,7 @@
         }
 
         private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
-            // If we don't have a media button receiver to fall back on
-            // include non-playing sessions for dispatching.
-            boolean useNotPlayingSessions = true;
-            for (int userId : mCurrentUserIdList) {
-                UserRecord ur = mUserRecords.get(userId);
-                if (ur.mLastMediaButtonReceiver != null
-                        || ur.mRestoredMediaButtonReceiver != null) {
-                    useNotPlayingSessions = false;
-                    break;
-                }
-            }
-            if (DEBUG) {
-                Log.d(TAG, "dispatchMediaKeyEvent, useNotPlayingSessions="
-                        + useNotPlayingSessions);
-            }
-
-            MediaSessionRecord session = mPriorityStack.getDefaultMediaButtonSession(
-                    mCurrentUserIdList, useNotPlayingSessions);
+            MediaSessionRecord session = getMediaButtonSessionLocked();
             if (session != null) {
                 if (DEBUG_KEY_EVENT) {
                     Log.d(TAG, "Sending " + keyEvent + " to " + session);
@@ -1247,6 +1317,14 @@
                         needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                         mKeyEventReceiver, Process.SYSTEM_UID,
                         getContext().getPackageName());
+                if (mCallback != null) {
+                    try {
+                        mCallback.onMediaKeyEventDispatchedToMediaSession(keyEvent,
+                                new MediaSession.Token(session.getControllerBinder()));
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to send callback", e);
+                    }
+                }
             } else {
                 // Launch the last PendingIntent we had with priority
                 for (int userId : mCurrentUserIdList) {
@@ -1263,26 +1341,41 @@
                     mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
                     try {
                         if (user.mLastMediaButtonReceiver != null) {
+                            PendingIntent receiver = user.mLastMediaButtonReceiver;
                             if (DEBUG_KEY_EVENT) {
                                 Log.d(TAG, "Sending " + keyEvent
-                                        + " to the last known pendingIntent "
-                                        + user.mLastMediaButtonReceiver);
+                                        + " to the last known pendingIntent " + receiver);
                             }
-                            user.mLastMediaButtonReceiver.send(getContext(),
+                            receiver.send(getContext(),
                                     needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                                     mediaButtonIntent, mKeyEventReceiver, mHandler);
+                            if (mCallback != null) {
+                                ComponentName componentName =
+                                        user.mLastMediaButtonReceiver.getIntent().getComponent();
+                                if (componentName != null) {
+                                    mCallback.onMediaKeyEventDispatchedToMediaButtonReceiver(
+                                            keyEvent, componentName);
+                                }
+                            }
                         } else {
+                            ComponentName receiver = user.mRestoredMediaButtonReceiver;
                             if (DEBUG_KEY_EVENT) {
                                 Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
-                                        + user.mRestoredMediaButtonReceiver);
+                                        + receiver);
                             }
-                            mediaButtonIntent.setComponent(user.mRestoredMediaButtonReceiver);
+                            mediaButtonIntent.setComponent(receiver);
                             getContext().sendBroadcastAsUser(mediaButtonIntent,
                                     UserHandle.of(userId));
+                            if (mCallback != null) {
+                                mCallback.onMediaKeyEventDispatchedToMediaButtonReceiver(
+                                        keyEvent, receiver);
+                            }
                         }
                     } catch (CanceledException e) {
                         Log.i(TAG, "Error sending key event to media button receiver "
                                 + user.mLastMediaButtonReceiver, e);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to send callback", e);
                     }
                     return;
                 }