diff --git a/api/current.txt b/api/current.txt index 014ba9d3d7a8a..de5085e0b563e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -25948,7 +25948,6 @@ package android.media { method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession(); method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2); method public final void removeSession(@NonNull android.media.MediaSession2); - field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; } public static class MediaSession2Service.MediaNotification { @@ -27471,6 +27470,7 @@ package android.media.session { method @NonNull public java.util.List getSession2Tokens(); method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo); method public void notifySession2Created(@NonNull android.media.Session2Token); + method public void notifySession2Destroyed(@NonNull android.media.Session2Token); method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener); method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener); } diff --git a/api/system-current.txt b/api/system-current.txt index 213aedc4b44db..64b3d6bef35eb 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3414,6 +3414,15 @@ package android.media { method public void stop(); } + public final class Session2Token implements android.os.Parcelable { + ctor public Session2Token(@NonNull android.content.Context, @NonNull String, @Nullable android.os.Bundle); + method public void destroy(); + method @NonNull public android.os.Bundle getExtras(); + method public int getPid(); + method public boolean isDestroyed(); + field public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service"; + } + public static class SubtitleData.Builder { ctor public SubtitleData.Builder(); ctor public SubtitleData.Builder(@NonNull android.media.SubtitleData); diff --git a/media/Android.bp b/media/Android.bp index 88ed9c6a05a98..0675a36f3e812 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -132,7 +132,6 @@ filegroup { "apex/java/android/media/Session2Command.java", "apex/java/android/media/Session2CommandGroup.java", "apex/java/android/media/Session2Link.java", - "apex/java/android/media/Session2Token.java", ], } diff --git a/media/apex/java/android/media/MediaConstants.java b/media/apex/java/android/media/MediaConstants.java index 65b6f55a068a2..45ea8261c6fb2 100644 --- a/media/apex/java/android/media/MediaConstants.java +++ b/media/apex/java/android/media/MediaConstants.java @@ -24,7 +24,8 @@ class MediaConstants { static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME"; // Bundle key for Parcelable - static final String KEY_SESSION2LINK = "android.media.key.SESSION2LINK"; + static final String KEY_SESSION2_TOKEN = "android.media.key.SESSION2_TOKEN"; + static final String KEY_SESSION2_LINK = "android.media.key.SESSION2_LINK"; static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS"; static final String KEY_PLAYBACK_ACTIVE = "android.media.key.PLAYBACK_ACTIVE"; diff --git a/media/apex/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java index 887b4475a4d1d..41721f3011b10 100644 --- a/media/apex/java/android/media/MediaController2.java +++ b/media/apex/java/android/media/MediaController2.java @@ -20,9 +20,11 @@ import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; -import static android.media.MediaConstants.KEY_SESSION2LINK; +import static android.media.MediaConstants.KEY_SESSION2_LINK; +import static android.media.MediaConstants.KEY_SESSION2_TOKEN; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; +import static android.media.Session2Token.SESSION_SERVICE_INTERFACE; import static android.media.Session2Token.TYPE_SESSION; import android.annotation.NonNull; @@ -260,7 +262,8 @@ public class MediaController2 implements AutoCloseable { // Called by Controller2Link.onConnected void onConnected(int seq, Bundle connectionResult) { - Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK); + Session2Token token = connectionResult.getParcelable(KEY_SESSION2_TOKEN); + Session2Link sessionBinder = token.getExtras().getParcelable(KEY_SESSION2_LINK); Session2CommandGroup allowedCommands = connectionResult.getParcelable(KEY_ALLOWED_COMMANDS); boolean playbackActive = connectionResult.getBoolean(KEY_PLAYBACK_ACTIVE); @@ -281,8 +284,7 @@ public class MediaController2 implements AutoCloseable { // Implementation for the local binder is no-op, // so can be used without worrying about deadlock. sessionBinder.linkToDeath(mDeathRecipient, 0); - mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION, - mSessionToken.getPackageName(), sessionBinder); + mConnectedToken = token; } mCallbackExecutor.execute(() -> { mCallback.onConnected(MediaController2.this, allowedCommands); @@ -353,7 +355,7 @@ public class MediaController2 implements AutoCloseable { } private boolean requestConnectToSession() { - Session2Link sessionBinder = mSessionToken.getSessionLink(); + Session2Link sessionBinder = mSessionToken.getExtras().getParcelable(KEY_SESSION2_LINK); Bundle connectionRequest = createConnectionRequest(); try { sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest); @@ -366,7 +368,7 @@ public class MediaController2 implements AutoCloseable { private boolean requestConnectToService() { // Service. Needs to get fresh binder whenever connection is needed. - final Intent intent = new Intent(MediaSession2Service.SERVICE_INTERFACE); + final Intent intent = new Intent(SESSION_SERVICE_INTERFACE); intent.setClassName(mSessionToken.getPackageName(), mSessionToken.getServiceName()); // Use bindService() instead of startForegroundService() to start session service for three diff --git a/media/apex/java/android/media/MediaSession2.java b/media/apex/java/android/media/MediaSession2.java index fdd07fdd52e33..80c91cc79c78b 100644 --- a/media/apex/java/android/media/MediaSession2.java +++ b/media/apex/java/android/media/MediaSession2.java @@ -20,10 +20,10 @@ import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; -import static android.media.MediaConstants.KEY_SESSION2LINK; +import static android.media.MediaConstants.KEY_SESSION2_LINK; +import static android.media.MediaConstants.KEY_SESSION2_TOKEN; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; -import static android.media.Session2Token.TYPE_SESSION; import android.annotation.NonNull; import android.annotation.Nullable; @@ -34,7 +34,6 @@ import android.media.session.MediaSessionManager; import android.media.session.MediaSessionManager.RemoteUserInfo; import android.os.Bundle; import android.os.Handler; -import android.os.Process; import android.os.ResultReceiver; import android.util.ArrayMap; import android.util.ArraySet; @@ -108,8 +107,10 @@ public class MediaSession2 implements AutoCloseable { mCallbackExecutor = callbackExecutor; mCallback = callback; mSessionStub = new Session2Link(this); - mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(), - mSessionStub); + + Bundle extras = new Bundle(); + extras.putParcelable(KEY_SESSION2_LINK, mSessionStub); + mSessionToken = new Session2Token(context, id, extras); mSessionManager = (MediaSessionManager) mContext.getSystemService( Context.MEDIA_SESSION_SERVICE); // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. @@ -141,6 +142,8 @@ public class MediaSession2 implements AutoCloseable { for (ControllerInfo info : controllerInfos) { info.notifyDisconnected(); } + mSessionToken.destroy(); + mSessionManager.notifySession2Destroyed(mSessionToken); } catch (Exception e) { // Should not be here. } @@ -328,7 +331,7 @@ public class MediaSession2 implements AutoCloseable { // It's needed because we cannot call synchronous calls between // session/controller. Bundle connectionResult = new Bundle(); - connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub); + connectionResult.putParcelable(KEY_SESSION2_TOKEN, mSessionToken); connectionResult.putParcelable(KEY_ALLOWED_COMMANDS, controllerInfo.mAllowedCommands); connectionResult.putBoolean(KEY_PLAYBACK_ACTIVE, isPlaybackActive()); diff --git a/media/apex/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java index 5bb746a7f9e33..f18cd317ef12a 100644 --- a/media/apex/java/android/media/MediaSession2Service.java +++ b/media/apex/java/android/media/MediaSession2Service.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.Session2Token.SESSION_SERVICE_INTERFACE; + import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -45,10 +47,6 @@ import java.util.Map; * for consistent behavior across all devices. */ public abstract class MediaSession2Service extends Service { - /** - * The {@link Intent} that must be declared as handled by the service. - */ - public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; private static final String TAG = "MediaSession2Service"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -100,7 +98,7 @@ public abstract class MediaSession2Service extends Service { @Override @Nullable public IBinder onBind(@NonNull Intent intent) { - if (SERVICE_INTERFACE.equals(intent.getAction())) { + if (SESSION_SERVICE_INTERFACE.equals(intent.getAction())) { synchronized (mLock) { return mStub; } diff --git a/media/apex/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java similarity index 70% rename from media/apex/java/android/media/Session2Token.java rename to media/java/android/media/Session2Token.java index 238cc2b8ee7d9..80494ad1b2a63 100644 --- a/media/apex/java/android/media/Session2Token.java +++ b/media/java/android/media/Session2Token.java @@ -19,13 +19,17 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.media.session.MediaSessionManager; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; import android.text.TextUtils; import android.util.Log; @@ -35,7 +39,7 @@ import java.util.List; import java.util.Objects; /** - * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}. + * Represents an ongoing MediaSession2 or a MediaSession2Service. * If it's representing a session service, it may not be ongoing. *

* This API is not generally intended for third party application developers. @@ -44,7 +48,7 @@ import java.util.Objects; * for consistent behavior across all devices. *

* This may be passed to apps by the session owner to allow them to create a - * {@link MediaController2} to communicate with the session. + * MediaController2 to communicate with the session. *

* It can be also obtained by {@link android.media.session.MediaSessionManager}. */ @@ -63,6 +67,13 @@ public final class Session2Token implements Parcelable { } }; + /** + * The {@link Intent} that must be declared for the session service. + * @hide + */ + @SystemApi + public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service"; + /** * @hide */ @@ -72,22 +83,26 @@ public final class Session2Token implements Parcelable { } /** - * Type for {@link MediaSession2}. + * Type for MediaSession2. */ public static final int TYPE_SESSION = 0; /** - * Type for {@link MediaSession2Service}. + * Type for MediaSession2Service. */ public static final int TYPE_SESSION_SERVICE = 1; + private final String mSessionId; + private final int mPid; private final int mUid; @TokenType private final int mType; private final String mPackageName; private final String mServiceName; - private final Session2Link mSessionLink; private final ComponentName mComponentName; + private final Bundle mExtras; + + private boolean mDestroyed = false; /** * Constructor for the token with type {@link #TYPE_SESSION_SERVICE}. @@ -106,44 +121,67 @@ public final class Session2Token implements Parcelable { final PackageManager manager = context.getPackageManager(); final int uid = getUid(manager, serviceComponent.getPackageName()); - if (!isInterfaceDeclared(manager, MediaSession2Service.SERVICE_INTERFACE, - serviceComponent)) { + if (!isInterfaceDeclared(manager, SESSION_SERVICE_INTERFACE, serviceComponent)) { Log.w(TAG, serviceComponent + " doesn't implement MediaSession2Service."); } + mSessionId = null; mComponentName = serviceComponent; mPackageName = serviceComponent.getPackageName(); mServiceName = serviceComponent.getClassName(); + mPid = -1; mUid = uid; mType = TYPE_SESSION_SERVICE; - mSessionLink = null; + mExtras = null; } - Session2Token(int uid, int type, String packageName, Session2Link sessionLink) { - mUid = uid; - mType = type; - mPackageName = packageName; + /** + * Constructor for the token with type {@link #TYPE_SESSION}. + * + * @param context The context. + * @param sessionId The ID of the session. Should be unique. + * @param extras The extras. + * @hide + */ + @SystemApi + public Session2Token(@NonNull Context context, @NonNull String sessionId, + @Nullable Bundle extras) { + if (sessionId == null) { + throw new IllegalArgumentException("sessionId shouldn't be null"); + } + if (context == null) { + throw new IllegalArgumentException("context shouldn't be null"); + } + mSessionId = sessionId; + mPid = Process.myPid(); + mUid = Process.myUid(); + mType = TYPE_SESSION; + mPackageName = context.getPackageName(); + mExtras = extras; mServiceName = null; mComponentName = null; - mSessionLink = sessionLink; } Session2Token(Parcel in) { + mSessionId = in.readString(); + mPid = in.readInt(); mUid = in.readInt(); mType = in.readInt(); mPackageName = in.readString(); mServiceName = in.readString(); - mSessionLink = in.readParcelable(null); mComponentName = ComponentName.unflattenFromString(in.readString()); + mExtras = in.readParcelable(null); } @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mSessionId); + dest.writeInt(mPid); dest.writeInt(mUid); dest.writeInt(mType); dest.writeString(mPackageName); dest.writeString(mServiceName); - dest.writeParcelable(mSessionLink, flags); dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString()); + dest.writeParcelable(mExtras, flags); } @Override @@ -153,7 +191,7 @@ public final class Session2Token implements Parcelable { @Override public int hashCode() { - return Objects.hash(mType, mUid, mPackageName, mServiceName, mSessionLink); + return Objects.hash(mSessionId, mPid, mUid, mType, mPackageName, mServiceName); } @Override @@ -162,17 +200,27 @@ public final class Session2Token implements Parcelable { return false; } Session2Token other = (Session2Token) obj; - return mUid == other.mUid - && TextUtils.equals(mPackageName, other.mPackageName) - && TextUtils.equals(mServiceName, other.mServiceName) + return TextUtils.equals(mSessionId, other.mSessionId) + && mPid == other.mPid + && mUid == other.mUid && mType == other.mType - && Objects.equals(mSessionLink, other.mSessionLink); + && TextUtils.equals(mPackageName, other.mPackageName) + && TextUtils.equals(mServiceName, other.mServiceName); } @Override public String toString() { return "Session2Token {pkg=" + mPackageName + " type=" + mType - + " service=" + mServiceName + " Session2Link=" + mSessionLink + "}"; + + " service=" + mServiceName + "}"; + } + + /** + * @return pid of the session + * @hide + */ + @SystemApi + public int getPid() { + return mPid; } /** @@ -207,8 +255,36 @@ public final class Session2Token implements Parcelable { return mType; } - Session2Link getSessionLink() { - return mSessionLink; + /** + * @return extras + * @hide + */ + @SystemApi + @NonNull + public Bundle getExtras() { + return mExtras == null ? new Bundle() : new Bundle(mExtras); + } + + /** + * Destroys this session token. After this method is called, + * {@link MediaSessionManager#notifySession2Created(Session2Token)} should not be called + * with this token. + * + * @see MediaSessionManager#notifySession2Created(Session2Token) + * @hide + */ + @SystemApi + public void destroy() { + mDestroyed = true; + } + + /** + * @return whether this token is destroyed + * @hide + */ + @SystemApi + public boolean isDestroyed() { + return mDestroyed; } private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface, diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index ed162504c553a..fa6e034303151 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -37,6 +37,7 @@ interface ISessionManager { SessionLink createSession(String packageName, in SessionCallbackLink sessionCb, String tag, int userId); void notifySession2Created(in Session2Token sessionToken); + void notifySession2Destroyed(in Session2Token sessionToken); List getSessions(in ComponentName compName, int userId); List getSession2Tokens(int userId); void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent, diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index c64c452be3ef5..cae4d1749287c 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -26,7 +26,6 @@ import android.content.ComponentName; import android.content.Context; import android.media.AudioManager; import android.media.IRemoteVolumeController; -import android.media.MediaSession2; import android.media.Session2Token; import android.os.Handler; import android.os.IBinder; @@ -115,11 +114,11 @@ public final class MediaSessionManager { } /** - * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is + * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is * created. *

* Do not use this API directly, but create a new instance through the - * {@link MediaSession2.Builder} instead. + * MediaSession2.Builder instead. * * @param token newly created session2 token */ @@ -130,6 +129,9 @@ public final class MediaSessionManager { if (token.getType() != Session2Token.TYPE_SESSION) { throw new IllegalArgumentException("token's type should be TYPE_SESSION"); } + if (token.isDestroyed()) { + throw new IllegalArgumentException("token is already destroyed"); + } try { mService.notifySession2Created(token); } catch (RemoteException e) { @@ -137,6 +139,31 @@ public final class MediaSessionManager { } } + /** + * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is + * destroyed. + *

+ * Do not use this API directly, but close a session with MediaSession2#close() instead. + * + * @param token destroyed session2 token + */ + public void notifySession2Destroyed(@NonNull Session2Token token) { + if (token == null) { + throw new IllegalArgumentException("token shouldn't be null"); + } + if (token.getType() != Session2Token.TYPE_SESSION) { + throw new IllegalArgumentException("token's type should be TYPE_SESSION"); + } + if (!token.isDestroyed()) { + throw new IllegalArgumentException("token should have been destroyed"); + } + try { + mService.notifySession2Destroyed(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + /** * Get a list of controllers for all ongoing sessions. The controllers will * be provided in priority order with the most important controller at index @@ -192,7 +219,7 @@ public final class MediaSessionManager { * current user. *

* Although this API can be used without any restriction, each session owners can accept or - * reject your uses of {@link MediaSession2}. + * reject your uses of MediaSession2. * * @return A list of {@link Session2Token}. */ diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java index 1541b1d520cd8..dd26a29d55af4 100644 --- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java @@ -42,8 +42,6 @@ import android.media.AudioPlaybackConfiguration; import android.media.AudioSystem; import android.media.IAudioService; import android.media.IRemoteVolumeController; -import android.media.MediaController2; -import android.media.Session2CommandGroup; import android.media.Session2Token; import android.media.session.ControllerLink; import android.media.session.IActiveSessionsListener; @@ -60,7 +58,6 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; @@ -1007,17 +1004,50 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { if (DEBUG) { Log.d(TAG, "Session2 is created " + sessionToken); } + if (pid != sessionToken.getPid()) { + throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid + + " but actually=" + sessionToken.getPid()); + } if (uid != sessionToken.getUid()) { throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid + " but actually=" + sessionToken.getUid()); } - Controller2Callback callback = new Controller2Callback(sessionToken); - // Note: It's safe not to keep controller here because it wouldn't be GC'ed until - // it's closed. - // TODO: Keep controller as well for better readability - // because the GC behavior isn't straightforward. - MediaController2 controller = new MediaController2(mContext, sessionToken, - new HandlerExecutor(mHandler), callback); + int userId = UserHandle.getUserId(uid); + List session2Tokens = mSession2TokensPerUser.get(userId); + if (session2Tokens.contains(sessionToken)) { + if (DEBUG) { + Log.d(TAG, "notifySession2Created(): Ignoring already existing token " + + sessionToken); + } + return; + } + session2Tokens.add(sessionToken); + pushSession2TokensChangedLocked(userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void notifySession2Destroyed(Session2Token sessionToken) throws RemoteException { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + if (DEBUG) { + Log.d(TAG, "Session2 is destroyed " + sessionToken); + } + if (pid != sessionToken.getPid()) { + throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid + + " but actually=" + sessionToken.getPid()); + } + if (uid != sessionToken.getUid()) { + throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid + + " but actually=" + sessionToken.getUid()); + } + int userId = UserHandle.getUserId(uid); + mSession2TokensPerUser.get(userId).remove(sessionToken); + pushSession2TokensChangedLocked(userId); } finally { Binder.restoreCallingIdentity(token); } @@ -2114,30 +2144,4 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget(); } } - - private class Controller2Callback extends MediaController2.ControllerCallback { - private final Session2Token mToken; - - Controller2Callback(Session2Token token) { - mToken = token; - } - - @Override - public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) { - synchronized (mLock) { - int userId = UserHandle.getUserId(mToken.getUid()); - mSession2TokensPerUser.get(userId).add(mToken); - pushSession2TokensChangedLocked(userId); - } - } - - @Override - public void onDisconnected(MediaController2 controller) { - synchronized (mLock) { - int userId = UserHandle.getUserId(mToken.getUid()); - mSession2TokensPerUser.get(userId).remove(mToken); - pushSession2TokensChangedLocked(userId); - } - } - } }