Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c8d846ff authored by Jaewan Kim's avatar Jaewan Kim Committed by Andre Eisenbach
Browse files

Add callback for AVRCP 1.6 support

Bug: 33828042
Test: Build
Change-Id: Iaf5cecfa38065cfeed096929952559d7cb2e248b
parent fe358c6a
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -404,6 +404,7 @@ LOCAL_SRC_FILES += \
	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/ISessionController.aidl \
	media/java/android/media/session/ISessionControllerCallback.aidl \
	media/java/android/media/session/ISession.aidl \
+34 −0
Original line number Diff line number Diff line
/* 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);
}
+4 −1
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.media.session;
import android.content.ComponentName;
import android.media.IRemoteVolumeController;
import android.media.session.IActiveSessionsListener;
import android.media.session.ICallback;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.os.Bundle;
@@ -41,4 +42,6 @@ interface ISessionManager {

    // For PhoneWindowManager to precheck media keys
    boolean isGlobalPriorityActive();

    void setCallback(in ICallback callback);
}
+135 −0
Original line number Diff line number Diff line
@@ -57,6 +57,8 @@ public final class MediaSessionManager {

    private Context mContext;

    private CallbackImpl mCallback;

    /**
     * @hide
     */
@@ -312,6 +314,36 @@ public final class MediaSessionManager {
        return false;
    }

    /**
     * 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}.
@@ -320,6 +352,56 @@ public final class MediaSessionManager {
        public void onActiveSessionsChanged(@Nullable List<MediaController> controllers);
    }

    /**
     * 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;
@@ -360,4 +442,57 @@ public final class MediaSessionManager {
            mHandler = null;
        }
    }

    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);
                }
            });
        }
    }
}
+131 −20
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
import android.media.session.IActiveSessionsListener;
import android.media.session.ICallback;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.media.session.ISessionManager;
@@ -101,6 +102,7 @@ public class MediaSessionService extends SystemService implements Monitor {
    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.
@@ -485,6 +487,7 @@ public class MediaSessionService extends SystemService implements Monitor {
            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()));
@@ -516,6 +519,52 @@ public class MediaSessionService extends SystemService implements Monitor {
        }
    }

    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());
@@ -530,6 +579,14 @@ public class MediaSessionService extends SystemService implements Monitor {
        }
    }

    private String getCallingPackageName(int uid) {
        String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
            if (packages != null && packages.length > 0) {
                return packages[0];
            }
        return "";
    }

    /**
     * Information about a particular user. The contents of this object is
     * guarded by mLock.
@@ -792,12 +849,48 @@ public class MediaSessionService extends SystemService implements Monitor {
                        Log.d(TAG, "dispatchMediaKeyEvent, useNotPlayingSessions="
                                + useNotPlayingSessions);
                    }
                    MediaSessionRecord session = mPriorityStack.getDefaultMediaButtonSession(
                            mCurrentUserIdList, useNotPlayingSessions);
                    if (isVoiceKey(keyEvent.getKeyCode())) {
                        handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
                        handleVoiceKeyEventLocked(keyEvent, needWakeLock);
                    } else {
                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }

        @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 {
@@ -805,6 +898,7 @@ public class MediaSessionService extends SystemService implements Monitor {
            }
        }


        @Override
        public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
            final long token = Binder.clearCallingIdentity();
@@ -932,13 +1026,7 @@ public class MediaSessionService extends SystemService implements Monitor {
            }
        }

        private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
                MediaSessionRecord session) {
            if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
                // If the phone app has priority just give it the event
                dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
                return;
            }
        private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
            int action = keyEvent.getAction();
            boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
            if (action == KeyEvent.ACTION_DOWN) {
@@ -955,15 +1043,15 @@ public class MediaSessionService extends SystemService implements Monitor {
                    if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
                        // Resend the down then send this event through
                        KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
                        dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
                        dispatchMediaKeyEventLocked(downEvent, needWakeLock);
                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
                    }
                }
            }
        }

        private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
                MediaSessionRecord session) {
        private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
            MediaSessionRecord session = getMediaButtonSessionLocked();
            if (session != null) {
                if (DEBUG_MEDIA_KEY_EVENT) {
                    Log.d(TAG, "Sending " + keyEvent + " to " + session);
@@ -977,6 +1065,14 @@ public class MediaSessionService extends SystemService implements Monitor {
                        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) {
@@ -993,26 +1089,41 @@ public class MediaSessionService extends SystemService implements Monitor {
                    mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
                    try {
                        if (user.mLastMediaButtonReceiver != null) {
                            PendingIntent receiver = user.mLastMediaButtonReceiver;
                            if (DEBUG_MEDIA_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_MEDIA_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;
                }