Merge "MediaSessionManager: Add listener for Session2Token changes"
diff --git a/Android.bp b/Android.bp
index 980aa04..7e97e66 100644
--- a/Android.bp
+++ b/Android.bp
@@ -502,6 +502,7 @@
"media/java/android/media/session/IOnMediaKeyListener.aidl",
"media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl",
"media/java/android/media/session/ISession.aidl",
+ "media/java/android/media/session/ISession2TokensListener.aidl",
"media/java/android/media/session/ISessionCallback.aidl",
"media/java/android/media/session/ISessionController.aidl",
"media/java/android/media/session/ISessionControllerCallback.aidl",
diff --git a/media/java/android/media/session/ISession2TokensListener.aidl b/media/java/android/media/session/ISession2TokensListener.aidl
new file mode 100644
index 0000000..7d1a4aa
--- /dev/null
+++ b/media/java/android/media/session/ISession2TokensListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 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.media.Session2Token;
+
+/**
+ * Listens for changes to the list of session2 tokens.
+ * @hide
+ */
+oneway interface ISession2TokensListener {
+ void onSession2TokensChanged(in List<Session2Token> tokens);
+}
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 7ac3ef2..46516e0 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -23,6 +23,7 @@
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
+import android.media.session.ISession2TokensListener;
import android.media.session.SessionCallbackLink;
import android.os.Bundle;
import android.view.KeyEvent;
@@ -45,6 +46,8 @@
void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
int userId);
void removeSessionsListener(in IActiveSessionsListener listener);
+ void addSession2TokensListener(in ISession2TokensListener listener, int userId);
+ void removeSession2TokensListener(in ISession2TokensListener listener);
// This is for the system volume UI only
void setRemoteVolumeController(in IRemoteVolumeController rvc);
@@ -56,6 +59,5 @@
void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener);
void setOnMediaKeyListener(in IOnMediaKeyListener listener);
- // MediaSession2
boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid);
}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 56ea484..4596c22 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -41,6 +41,8 @@
import android.util.Log;
import android.view.KeyEvent;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -69,9 +71,13 @@
*/
public static final int RESULT_MEDIA_KEY_HANDLED = 1;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
= new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
- private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final ArrayMap<OnSession2TokensChangedListener, Session2TokensChangedWrapper>
+ mSession2TokensListeners = new ArrayMap<>();
private final ISessionManager mService;
private Context mContext;
@@ -324,6 +330,87 @@
}
/**
+ * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+ * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
+ * for consistent behavior across all devices.
+ *
+ * @param listener The listener to add
+ * @param handler The handler to call listener on. If {@code null}, calling thread's looper will
+ * be used.
+ * @hide
+ */
+ // TODO(jaewan): Unhide
+ public void addOnSession2TokensChangedListener(
+ @NonNull OnSession2TokensChangedListener listener, @Nullable Handler handler) {
+ addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, handler);
+ }
+
+ /**
+ * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+ * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
+ * for consistent behavior across all devices.
+ *
+ * @param userId The userId to listen for changes on
+ * @param listener The listener to add
+ * @param handler The handler to call listener on. If {@code null}, calling thread's looper will
+ * be used.
+ * @hide
+ */
+ public void addOnSession2TokensChangedListener(int userId,
+ @NonNull OnSession2TokensChangedListener listener, @Nullable Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener shouldn't be null");
+ }
+ synchronized (mLock) {
+ if (mSession2TokensListeners.get(listener) != null) {
+ Log.w(TAG, "Attempted to add session listener twice, ignoring.");
+ return;
+ }
+ Session2TokensChangedWrapper wrapper =
+ new Session2TokensChangedWrapper(listener, handler);
+ try {
+ mService.addSession2TokensListener(wrapper.getStub(), userId);
+ mSession2TokensListeners.put(listener, wrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in addSessionTokensListener.", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Removes the {@link OnSession2TokensChangedListener} to stop receiving session token updates.
+ *
+ * @param listener The listener to remove.
+ * @hide
+ */
+ // TODO(jaewan): Unhide
+ public void removeOnSession2TokensChangedListener(
+ @NonNull OnSession2TokensChangedListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener may not be null");
+ }
+ final Session2TokensChangedWrapper wrapper;
+ synchronized (mLock) {
+ wrapper = mSession2TokensListeners.remove(listener);
+ }
+ if (wrapper != null) {
+ try {
+ mService.removeSession2TokensListener(wrapper.getStub());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in removeSessionTokensListener.", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Set the remote volume controller to receive volume updates on. Only for
* use by system UI.
*
@@ -590,6 +677,26 @@
}
/**
+ * Listens for changes to the {@link #getSession2Tokens()}. This can be added
+ * using {@link #addOnSession2TokensChangedListener(OnSession2TokensChangedListener, Handler)}.
+ * <p>
+ * This API is not generally intended for third party application developers.
+ * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+ * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
+ * for consistent behavior across all devices.
+ *
+ * @hide
+ */
+ public interface OnSession2TokensChangedListener {
+ /**
+ * Called when the {@link #getSession2Tokens()} is changed.
+ *
+ * @param tokens list of {@link Session2Token}
+ */
+ void onSession2TokensChanged(@NonNull List<Session2Token> tokens);
+ }
+
+ /**
* Listens the volume key long-presses.
* @hide
*/
@@ -807,6 +914,27 @@
}
}
+ private static final class Session2TokensChangedWrapper {
+ private final OnSession2TokensChangedListener mListener;
+ private final Handler mHandler;
+ private final ISession2TokensListener.Stub mStub =
+ new ISession2TokensListener.Stub() {
+ @Override
+ public void onSession2TokensChanged(final List<Session2Token> tokens) {
+ mHandler.post(() -> mListener.onSession2TokensChanged(tokens));
+ }
+ };
+
+ Session2TokensChangedWrapper(OnSession2TokensChangedListener listener, Handler handler) {
+ mListener = listener;
+ mHandler = (handler == null) ? new Handler() : new Handler(handler.getLooper());
+ }
+
+ public ISession2TokensListener.Stub getStub() {
+ return mStub;
+ }
+ }
+
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 ce0e72b..ba7b87e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,6 +16,8 @@
package com.android.server.media;
+import static android.os.UserHandle.USER_ALL;
+
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.INotificationManager;
@@ -48,6 +50,7 @@
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
+import android.media.session.ISession2TokensListener;
import android.media.session.ISessionManager;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
@@ -119,6 +122,9 @@
// one place.
@GuardedBy("mLock")
private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
+ @GuardedBy("mLock")
+ private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords =
+ new ArrayList<>();
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
@@ -235,7 +241,7 @@
private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
List<MediaSessionRecord> records = new ArrayList<>();
- if (userId == UserHandle.USER_ALL) {
+ if (userId == USER_ALL) {
int size = mUserRecords.size();
for (int i = 0; i < size; i++) {
records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
@@ -251,13 +257,24 @@
// Return global priority session at the first whenever it's asked.
if (isGlobalPriorityActiveLocked()
- && (userId == UserHandle.USER_ALL
- || userId == mGlobalPrioritySession.getUserId())) {
+ && (userId == USER_ALL || userId == mGlobalPrioritySession.getUserId())) {
records.add(0, mGlobalPrioritySession);
}
return records;
}
+ List<Session2Token> getSession2TokensLocked(int userId) {
+ List<Session2Token> list = new ArrayList<>();
+ if (userId == USER_ALL) {
+ for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+ list.addAll(mSession2TokensPerUser.valueAt(i));
+ }
+ } else {
+ list.addAll(mSession2TokensPerUser.get(userId));
+ }
+ return list;
+ }
+
/**
* Tells the system UI that volume has changed on an active remote session.
*/
@@ -316,7 +333,7 @@
FullUserRecord user = getFullUserRecordLocked(userId);
if (user != null) {
if (user.mFullUserId == userId) {
- user.destroySessionsForUserLocked(UserHandle.USER_ALL);
+ user.destroySessionsForUserLocked(USER_ALL);
mUserRecords.remove(userId);
} else {
user.destroySessionsForUserLocked(userId);
@@ -393,14 +410,14 @@
for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
SessionsListenerRecord listener = mSessionsListeners.get(i);
try {
- enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
- listener.mUserId);
+ enforceMediaPermissions(listener.componentName, listener.pid, listener.uid,
+ listener.userId);
} catch (SecurityException e) {
- Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
+ Log.i(TAG, "ActiveSessionsListener " + listener.componentName
+ " is no longer authorized. Disconnecting.");
mSessionsListeners.remove(i);
try {
- listener.mListener
+ listener.listener
.onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
} catch (Exception e1) {
// ignore
@@ -562,13 +579,23 @@
private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
- if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) {
+ if (mSessionsListeners.get(i).listener.asBinder() == listener.asBinder()) {
return i;
}
}
return -1;
}
+ private int findIndexOfSession2TokensListenerLocked(ISession2TokensListener listener) {
+ for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
+ if (mSession2TokensListenerRecords.get(i).listener.asBinder() == listener.asBinder()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+
private void pushSessionsChanged(int userId) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(userId);
@@ -585,9 +612,9 @@
pushRemoteVolumeUpdateLocked(userId);
for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
SessionsListenerRecord record = mSessionsListeners.get(i);
- if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
+ if (record.userId == USER_ALL || record.userId == userId) {
try {
- record.mListener.onActiveSessionsChanged(tokens);
+ record.listener.onActiveSessionsChanged(tokens);
} catch (RemoteException e) {
Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
e);
@@ -614,6 +641,25 @@
}
}
+ void pushSession2TokensChangedLocked(int userId) {
+ List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
+ List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
+
+ for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
+ Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
+ try {
+ if (listenerRecord.userId == USER_ALL) {
+ listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
+ } else if (listenerRecord.userId == userId) {
+ listenerRecord.listener.onSession2TokensChanged(session2Tokens);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
+ mSession2TokensListenerRecords.remove(i);
+ }
+ }
+ }
+
/**
* Called when the media button receiver for the {@param record} is changed.
*
@@ -855,20 +901,20 @@
}
final class SessionsListenerRecord implements IBinder.DeathRecipient {
- private final IActiveSessionsListener mListener;
- private final ComponentName mComponentName;
- private final int mUserId;
- private final int mPid;
- private final int mUid;
+ public final IActiveSessionsListener listener;
+ public final ComponentName componentName;
+ public final int userId;
+ public final int pid;
+ public final int uid;
public SessionsListenerRecord(IActiveSessionsListener listener,
ComponentName componentName,
int userId, int pid, int uid) {
- mListener = listener;
- mComponentName = componentName;
- mUserId = userId;
- mPid = pid;
- mUid = uid;
+ this.listener = listener;
+ this.componentName = componentName;
+ this.userId = userId;
+ this.pid = pid;
+ this.uid = uid;
}
@Override
@@ -879,6 +925,24 @@
}
}
+ final class Session2TokensListenerRecord implements IBinder.DeathRecipient {
+ public final ISession2TokensListener listener;
+ public final int userId;
+
+ Session2TokensListenerRecord(ISession2TokensListener listener,
+ int userId) {
+ this.listener = listener;
+ this.userId = userId;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mSession2TokensListenerRecords.remove(this);
+ }
+ }
+ }
+
final class SettingsObserver extends ContentObserver {
private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
@@ -889,7 +953,7 @@
private void observe() {
mContentResolver.registerContentObserver(mSecureSettingsUri,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
}
@Override
@@ -984,15 +1048,9 @@
int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
true /* allowAll */, true /* requireFull */, "getSession2Tokens",
null /* optional packageName */);
- List<Session2Token> result = new ArrayList<>();
+ List<Session2Token> result;
synchronized (mLock) {
- if (resolvedUserId == UserHandle.USER_ALL) {
- for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
- result.addAll(mSession2TokensPerUser.valueAt(i));
- }
- } else {
- result.addAll(mSession2TokensPerUser.get(userId));
- }
+ result = getSession2TokensLocked(resolvedUserId);
}
return result;
} finally {
@@ -1038,7 +1096,7 @@
if (index != -1) {
SessionsListenerRecord record = mSessionsListeners.remove(index);
try {
- record.mListener.asBinder().unlinkToDeath(record, 0);
+ record.listener.asBinder().unlinkToDeath(record, 0);
} catch (Exception e) {
// ignore exceptions, the record is being removed
}
@@ -1046,6 +1104,56 @@
}
}
+ @Override
+ public void addSession2TokensListener(ISession2TokensListener listener,
+ int userId) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ // Check that they can make calls on behalf of the user and get the final user id.
+ int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ true /* allowAll */, true /* requireFull */, "addSession2TokensListener",
+ null /* optional packageName */);
+ synchronized (mLock) {
+ int index = findIndexOfSession2TokensListenerLocked(listener);
+ if (index >= 0) {
+ Log.w(TAG, "addSession2TokensListener is already added, ignoring");
+ return;
+ }
+ mSession2TokensListenerRecords.add(
+ new Session2TokensListenerRecord(listener, resolvedUserId));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void removeSession2TokensListener(ISession2TokensListener listener) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ synchronized (mLock) {
+ int index = findIndexOfSession2TokensListenerLocked(listener);
+ if (index >= 0) {
+ Session2TokensListenerRecord listenerRecord =
+ mSession2TokensListenerRecords.remove(index);
+ try {
+ listenerRecord.listener.asBinder().unlinkToDeath(listenerRecord, 0);
+ } catch (Exception e) {
+ // Ignore exception.
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
/**
* Handles the dispatching of the media button events to one of the
* registered listeners, or if there was none, broadcast an
@@ -2012,6 +2120,7 @@
synchronized (mLock) {
int userId = UserHandle.getUserId(mToken.getUid());
mSession2TokensPerUser.get(userId).add(mToken);
+ pushSession2TokensChangedLocked(userId);
}
}
@@ -2020,6 +2129,7 @@
synchronized (mLock) {
int userId = UserHandle.getUserId(mToken.getUid());
mSession2TokensPerUser.get(userId).remove(mToken);
+ pushSession2TokensChangedLocked(userId);
}
}
}