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

Commit 379e30d9 authored by Jaewan Kim's avatar Jaewan Kim
Browse files

MediaSession2: Add listeners for change in session token

Test: Run all MediaComponents tests once
Change-Id: Ic46ad9e4e4c9e1ce43b3dbad904eae7fc30d52a0
parent 543566fe
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -419,9 +419,9 @@ java_library {
        "location/java/android/location/IGpsGeofenceHardware.aidl",
        "location/java/android/location/INetInitiatedListener.aidl",
        "location/java/com/android/internal/location/ILocationProvider.aidl",
        "media/java/android/media/IAudioService.aidl",
        "media/java/android/media/IAudioFocusDispatcher.aidl",
        "media/java/android/media/IAudioRoutesObserver.aidl",
        "media/java/android/media/IAudioService.aidl",
        "media/java/android/media/IMediaHTTPConnection.aidl",
        "media/java/android/media/IMediaHTTPService.aidl",
        "media/java/android/media/IMediaResourceMonitor.aidl",
@@ -432,6 +432,7 @@ java_library {
        "media/java/android/media/IMediaSession2.aidl",
        "media/java/android/media/IMediaSession2Callback.aidl",
        "media/java/android/media/IPlaybackConfigDispatcher.aidl",
        "media/java/android/media/ISessionTokensListener.aidl",
        ":libaudioclient_aidl",
        "media/java/android/media/IRecordingConfigDispatcher.aidl",
        "media/java/android/media/IRemoteDisplayCallback.aidl",
+27 −0
Original line number Diff line number Diff line
/*
 * Copyright 2018 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;

import android.os.Bundle;

/**
 * Listens for changes to the list of session tokens.
 * @hide
 */
oneway interface ISessionTokensListener {
    void onSessionTokensChanged(in List<Bundle> tokens);
}
+5 −0
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.IMediaSession2;
import android.media.ISessionTokensListener;
import android.media.session.IActiveSessionsListener;
import android.media.session.ICallback;
import android.media.session.IOnMediaKeyListener;
@@ -55,4 +56,8 @@ interface ISessionManager {
    boolean onSessionCreated(in Bundle sessionToken);
    void onSessionDestroyed(in Bundle sessionToken);
    List<Bundle> getSessionTokens(boolean activeSessionOnly, boolean sessionServiceOnly);

    void addSessionTokensListener(in ISessionTokensListener listener, int userId,
            String packageName);
    void removeSessionTokensListener(in ISessionTokensListener listener);
}
+139 −9
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.media.session;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -24,8 +25,8 @@ import android.annotation.SystemService;
import android.content.ComponentName;
import android.content.Context;
import android.media.AudioManager;
import android.media.IMediaSession2;
import android.media.IRemoteVolumeController;
import android.media.ISessionTokensListener;
import android.media.MediaSession2;
import android.media.MediaSessionService2;
import android.media.SessionToken2;
@@ -44,6 +45,7 @@ import android.view.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;

/**
 * Provides support for interacting with {@link MediaSession media sessions}
@@ -71,6 +73,8 @@ public final class MediaSessionManager {

    private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
            = new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
    private final ArrayMap<OnSessionTokensChangedListener, SessionTokensChangedWrapper>
            mSessionTokensListener = new ArrayMap<>();
    private final Object mLock = new Object();
    private final ISessionManager mService;

@@ -371,13 +375,15 @@ public final class MediaSessionManager {
     * Get {@link List} of {@link SessionToken2} whose sessions are active now. This list represents
     * active sessions regardless of whether they're {@link MediaSession2} or
     * {@link MediaSessionService2}.
     * <p>
     * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
     * calling app. You may also retrieve this list if your app is an enabled notification listener
     * using the {@link NotificationListenerService} APIs.
     *
     * @return list of Tokens
     * @return list of tokens
     * @hide
     */
    // TODO(jaewan): Unhide
    // TODO(jaewan): Protect this with permission.
    // TODO(jaewna): Add listener for change in lists.
    public List<SessionToken2> getActiveSessionTokens() {
        try {
            List<Bundle> bundles = mService.getSessionTokens(
@@ -392,12 +398,15 @@ public final class MediaSessionManager {
    /**
     * Get {@link List} of {@link SessionToken2} for {@link MediaSessionService2} regardless of their
     * activeness. This list represents media apps that support background playback.
     * <p>
     * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
     * calling app. You may also retrieve this list if your app is an enabled notification listener
     * using the {@link NotificationListenerService} APIs.
     *
     * @return list of Tokens
     * @return list of tokens
     * @hide
     */
    // TODO(jaewan): Unhide
    // TODO(jaewna): Add listener for change in lists.
    public List<SessionToken2> getSessionServiceTokens() {
        try {
            List<Bundle> bundles = mService.getSessionTokens(
@@ -412,15 +421,17 @@ public final class MediaSessionManager {
    /**
     * Get all {@link SessionToken2}s. This is the combined list of {@link #getActiveSessionTokens()}
     * and {@link #getSessionServiceTokens}.
     * <p>
     * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
     * calling app. You may also retrieve this list if your app is an enabled notification listener
     * using the {@link NotificationListenerService} APIs.
     *
     * @return list of Tokens
     * @return list of tokens
     * @see #getActiveSessionTokens
     * @see #getSessionServiceTokens
     * @hide
     */
    // TODO(jaewan): Unhide
    // TODO(jaewan): Protect this with permission.
    // TODO(jaewna): Add listener for change in lists.
    public List<SessionToken2> getAllSessionTokens() {
        try {
            List<Bundle> bundles = mService.getSessionTokens(
@@ -432,6 +443,86 @@ public final class MediaSessionManager {
        }
    }

    /**
     * Add a listener to be notified when the {@link #getAllSessionTokens()} changes.
     * <p>
     * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
     * calling app. You may also retrieve this list if your app is an enabled notification listener
     * using the {@link NotificationListenerService} APIs.
     *
     * @param executor executor to run this command
     * @param listener The listener to add.
     * @hide
     */
    // TODO(jaewan): Unhide
    public void addOnSessionTokensChangedListener(@NonNull @CallbackExecutor Executor executor,
            @NonNull OnSessionTokensChangedListener listener) {
        addOnSessionTokensChangedListener(UserHandle.myUserId(), executor, listener);
    }

    /**
     * Add a listener to be notified when the {@link #getAllSessionTokens()} changes.
     * <p>
     * This requires the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by the
     * calling app. You may also retrieve this list if your app is an enabled notification listener
     * using the {@link NotificationListenerService} APIs.
     *
     * @param userId The userId to listen for changes on.
     * @param executor executor to run this command
     * @param listener The listener to add.
     * @hide
     */
    public void addOnSessionTokensChangedListener(int userId,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull OnSessionTokensChangedListener listener) {
        if (executor == null) {
            throw new IllegalArgumentException("executor may not be null");
        }
        if (listener == null) {
            throw new IllegalArgumentException("listener may not be null");
        }
        synchronized (mLock) {
            if (mSessionTokensListener.get(listener) != null) {
                Log.w(TAG, "Attempted to add session listener twice, ignoring.");
                return;
            }
            SessionTokensChangedWrapper wrapper = new SessionTokensChangedWrapper(
                    mContext, executor, listener);
            try {
                mService.addSessionTokensListener(wrapper.mStub, userId, mContext.getPackageName());
                mSessionTokensListener.put(listener, wrapper);
            } catch (RemoteException e) {
                Log.e(TAG, "Error in addSessionTokensListener.", e);
            }
        }
    }

    /**
     * Stop receiving session token updates on the specified listener.
     *
     * @param listener The listener to remove.
     * @hide
     */
    // TODO(jaewan): Unhide
    public void removeOnSessionTokensChangedListener(
            @NonNull OnSessionTokensChangedListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener may not be null");
        }
        synchronized (mLock) {
            SessionTokensChangedWrapper wrapper = mSessionTokensListener.remove(listener);
            if (wrapper != null) {
                try {
                    mService.removeSessionTokensListener(wrapper.mStub);
                } catch (RemoteException e) {
                    Log.e(TAG, "Error in removeSessionTokensListener.", e);
                } finally {
                    wrapper.release();
                }
            }
        }
    }

    private static List<SessionToken2> toTokenList(Context context, List<Bundle> bundles) {
        List<SessionToken2> tokens = new ArrayList<>();
        if (bundles != null) {
@@ -566,6 +657,16 @@ public final class MediaSessionManager {
        public void onActiveSessionsChanged(@Nullable List<MediaController> controllers);
    }

    /**
     * Listens for changes to the {@link #getAllSessionTokens()}. This can be added
     * using {@link #addOnActiveSessionsChangedListener}.
     * @hide
     */
    // TODO(jaewan): Unhide
    public interface OnSessionTokensChangedListener {
        void onSessionTokensChanged(@NonNull List<SessionToken2> tokens);
    }

    /**
     * Listens the volume key long-presses.
     * @hide
@@ -692,6 +793,35 @@ public final class MediaSessionManager {
        }
    }

    private static final class SessionTokensChangedWrapper {
        private Context mContext;
        private Executor mExecutor;
        private OnSessionTokensChangedListener mListener;

        public SessionTokensChangedWrapper(Context context, Executor executor,
                OnSessionTokensChangedListener listener) {
            mContext = context;
            mExecutor = executor;
            mListener = listener;
        }

        private final ISessionTokensListener.Stub mStub = new ISessionTokensListener.Stub() {
            @Override
            public void onSessionTokensChanged(final List<Bundle> bundles) {
                mExecutor.execute(() -> {
                    List<SessionToken2> tokens = toTokenList(mContext, bundles);
                    mListener.onSessionTokensChanged(tokens);
                });
            }
        };

        private void release() {
            mListener = null;
            mContext = null;
            mExecutor = null;
        }
    }

    private static final class OnVolumeKeyLongPressListenerImpl
            extends IOnVolumeKeyLongPressListener.Stub {
        private OnVolumeKeyLongPressListener mListener;
+12 −1
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IMediaSession2;
import android.media.IRemoteVolumeController;
import android.media.ISessionTokensListener;
import android.media.MediaLibraryService2;
import android.media.MediaSessionService2;
import android.media.SessionToken2;
@@ -1480,7 +1481,6 @@ public class MediaSessionService extends SystemService implements Monitor {
        }

        // TODO(jaewan): Protect this API with permission
        // TODO(jaewan): Add listeners for change in operations..
        @Override
        public List<Bundle> getSessionTokens(boolean activeSessionOnly,
                boolean sessionServiceOnly) throws RemoteException {
@@ -1504,6 +1504,17 @@ public class MediaSessionService extends SystemService implements Monitor {
            return tokens;
        }

        @Override
        public void addSessionTokensListener(ISessionTokensListener listener, int userId,
                String packageName) {
            // TODO(jaewan): Implement.
        }

        @Override
        public void removeSessionTokensListener(ISessionTokensListener listener) {
            // TODO(jaewan): Implement
        }

        private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
                final int uid) {
            String packageName = null;