MediaSession2: Add listeners for change in session token
Test: Run all MediaComponents tests once
Change-Id: Ic46ad9e4e4c9e1ce43b3dbad904eae7fc30d52a0
diff --git a/Android.bp b/Android.bp
index 763d242..28e51e7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -419,9 +419,9 @@
"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 @@
"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",
diff --git a/media/java/android/media/ISessionTokensListener.aidl b/media/java/android/media/ISessionTokensListener.aidl
new file mode 100644
index 0000000..c83a19e
--- /dev/null
+++ b/media/java/android/media/ISessionTokensListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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);
+}
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 8135106..37c46cb 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -18,6 +18,7 @@
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 @@
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);
}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 1023a10..454113c 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -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.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 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 @@
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 @@
* 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 @@
/**
* 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 @@
/**
* 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 @@
}
}
+ /**
+ * 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) {
@@ -567,6 +658,16 @@
}
/**
+ * 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 @@
}
}
+ 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;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 1d6d1ed..02df84e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -41,6 +41,7 @@
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 @@
}
// 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 @@
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;