From ff36c48229c26f2aa6bc4d608d582840f0c3bae4 Mon Sep 17 00:00:00 2001 From: Hyundo Moon Date: Thu, 31 Jan 2019 02:28:20 +0000 Subject: [PATCH] Revert "Remove MediaController2 usages in MediaSessionServiceImpl" This reverts commit c1e4316e023a3632466203fe84481f4caf4fd26a. Reason for revert: Now the circular dependency problem is resolved, this change is now obsolete. Bug: 123336670 Change-Id: I065d811e9b8a3c7f851fb66d9527d62b417f1420 --- api/current.txt | 2 +- api/system-current.txt | 9 -- .../java/android/media/MediaConstants.java | 3 +- .../java/android/media/MediaController2.java | 14 +- .../java/android/media/MediaSession2.java | 15 +-- .../android/media/MediaSession2Service.java | 8 +- .../java/android/media/Session2Token.java | 122 ++++-------------- .../media/session/ISessionManager.aidl | 1 - .../media/session/MediaSessionManager.java | 35 +---- .../server/media/MediaSessionServiceImpl.java | 76 ++++++----- 10 files changed, 82 insertions(+), 203 deletions(-) diff --git a/api/current.txt b/api/current.txt index 25e78fd437765..d57cd614bd4e9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -26006,6 +26006,7 @@ 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 { @@ -27523,7 +27524,6 @@ 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 68197cd262c73..01207cd6350db 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3463,15 +3463,6 @@ 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/apex/java/android/media/MediaConstants.java b/media/apex/java/android/media/MediaConstants.java index 45ea8261c6fb2..65b6f55a068a2 100644 --- a/media/apex/java/android/media/MediaConstants.java +++ b/media/apex/java/android/media/MediaConstants.java @@ -24,8 +24,7 @@ class MediaConstants { static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME"; // Bundle key for Parcelable - 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_SESSION2LINK = "android.media.key.SESSION2LINK"; 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 41721f3011b10..887b4475a4d1d 100644 --- a/media/apex/java/android/media/MediaController2.java +++ b/media/apex/java/android/media/MediaController2.java @@ -20,11 +20,9 @@ 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_SESSION2_LINK; -import static android.media.MediaConstants.KEY_SESSION2_TOKEN; +import static android.media.MediaConstants.KEY_SESSION2LINK; 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; @@ -262,8 +260,7 @@ public class MediaController2 implements AutoCloseable { // Called by Controller2Link.onConnected void onConnected(int seq, Bundle connectionResult) { - Session2Token token = connectionResult.getParcelable(KEY_SESSION2_TOKEN); - Session2Link sessionBinder = token.getExtras().getParcelable(KEY_SESSION2_LINK); + Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK); Session2CommandGroup allowedCommands = connectionResult.getParcelable(KEY_ALLOWED_COMMANDS); boolean playbackActive = connectionResult.getBoolean(KEY_PLAYBACK_ACTIVE); @@ -284,7 +281,8 @@ 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 = token; + mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION, + mSessionToken.getPackageName(), sessionBinder); } mCallbackExecutor.execute(() -> { mCallback.onConnected(MediaController2.this, allowedCommands); @@ -355,7 +353,7 @@ public class MediaController2 implements AutoCloseable { } private boolean requestConnectToSession() { - Session2Link sessionBinder = mSessionToken.getExtras().getParcelable(KEY_SESSION2_LINK); + Session2Link sessionBinder = mSessionToken.getSessionLink(); Bundle connectionRequest = createConnectionRequest(); try { sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest); @@ -368,7 +366,7 @@ public class MediaController2 implements AutoCloseable { private boolean requestConnectToService() { // Service. Needs to get fresh binder whenever connection is needed. - final Intent intent = new Intent(SESSION_SERVICE_INTERFACE); + final Intent intent = new Intent(MediaSession2Service.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 80c91cc79c78b..fdd07fdd52e33 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_SESSION2_LINK; -import static android.media.MediaConstants.KEY_SESSION2_TOKEN; +import static android.media.MediaConstants.KEY_SESSION2LINK; 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,6 +34,7 @@ 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; @@ -107,10 +108,8 @@ public class MediaSession2 implements AutoCloseable { mCallbackExecutor = callbackExecutor; mCallback = callback; mSessionStub = new Session2Link(this); - - Bundle extras = new Bundle(); - extras.putParcelable(KEY_SESSION2_LINK, mSessionStub); - mSessionToken = new Session2Token(context, id, extras); + mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(), + mSessionStub); mSessionManager = (MediaSessionManager) mContext.getSystemService( Context.MEDIA_SESSION_SERVICE); // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. @@ -142,8 +141,6 @@ public class MediaSession2 implements AutoCloseable { for (ControllerInfo info : controllerInfos) { info.notifyDisconnected(); } - mSessionToken.destroy(); - mSessionManager.notifySession2Destroyed(mSessionToken); } catch (Exception e) { // Should not be here. } @@ -331,7 +328,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_SESSION2_TOKEN, mSessionToken); + connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub); 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 f18cd317ef12a..5bb746a7f9e33 100644 --- a/media/apex/java/android/media/MediaSession2Service.java +++ b/media/apex/java/android/media/MediaSession2Service.java @@ -16,8 +16,6 @@ package android.media; -import static android.media.Session2Token.SESSION_SERVICE_INTERFACE; - import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -47,6 +45,10 @@ 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); @@ -98,7 +100,7 @@ public abstract class MediaSession2Service extends Service { @Override @Nullable public IBinder onBind(@NonNull Intent intent) { - if (SESSION_SERVICE_INTERFACE.equals(intent.getAction())) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { synchronized (mLock) { return mStub; } diff --git a/media/apex/java/android/media/Session2Token.java b/media/apex/java/android/media/Session2Token.java index 80494ad1b2a63..238cc2b8ee7d9 100644 --- a/media/apex/java/android/media/Session2Token.java +++ b/media/apex/java/android/media/Session2Token.java @@ -19,17 +19,13 @@ 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; @@ -39,7 +35,7 @@ import java.util.List; import java.util.Objects; /** - * Represents an ongoing MediaSession2 or a MediaSession2Service. + * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}. * If it's representing a session service, it may not be ongoing. *

* This API is not generally intended for third party application developers. @@ -48,7 +44,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 - * MediaController2 to communicate with the session. + * {@link MediaController2} to communicate with the session. *

* It can be also obtained by {@link android.media.session.MediaSessionManager}. */ @@ -67,13 +63,6 @@ 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 */ @@ -83,26 +72,22 @@ public final class Session2Token implements Parcelable { } /** - * Type for MediaSession2. + * Type for {@link MediaSession2}. */ public static final int TYPE_SESSION = 0; /** - * Type for MediaSession2Service. + * Type for {@link 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}. @@ -121,67 +106,44 @@ public final class Session2Token implements Parcelable { final PackageManager manager = context.getPackageManager(); final int uid = getUid(manager, serviceComponent.getPackageName()); - if (!isInterfaceDeclared(manager, SESSION_SERVICE_INTERFACE, serviceComponent)) { + if (!isInterfaceDeclared(manager, MediaSession2Service.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; - mExtras = null; + mSessionLink = null; } - /** - * 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; + Session2Token(int uid, int type, String packageName, Session2Link sessionLink) { + mUid = uid; + mType = type; + mPackageName = packageName; 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 @@ -191,7 +153,7 @@ public final class Session2Token implements Parcelable { @Override public int hashCode() { - return Objects.hash(mSessionId, mPid, mUid, mType, mPackageName, mServiceName); + return Objects.hash(mType, mUid, mPackageName, mServiceName, mSessionLink); } @Override @@ -200,27 +162,17 @@ public final class Session2Token implements Parcelable { return false; } Session2Token other = (Session2Token) obj; - return TextUtils.equals(mSessionId, other.mSessionId) - && mPid == other.mPid - && mUid == other.mUid - && mType == other.mType + return mUid == other.mUid && TextUtils.equals(mPackageName, other.mPackageName) - && TextUtils.equals(mServiceName, other.mServiceName); + && TextUtils.equals(mServiceName, other.mServiceName) + && mType == other.mType + && Objects.equals(mSessionLink, other.mSessionLink); } @Override public String toString() { return "Session2Token {pkg=" + mPackageName + " type=" + mType - + " service=" + mServiceName + "}"; - } - - /** - * @return pid of the session - * @hide - */ - @SystemApi - public int getPid() { - return mPid; + + " service=" + mServiceName + " Session2Link=" + mSessionLink + "}"; } /** @@ -255,36 +207,8 @@ public final class Session2Token implements Parcelable { return mType; } - /** - * @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; + Session2Link getSessionLink() { + return mSessionLink; } 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 edaa55dad0e56..8143bfa00dc19 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -37,7 +37,6 @@ 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 bd8eaddd4a028..46f6c71c5be0d 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -26,6 +26,7 @@ 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; @@ -114,11 +115,11 @@ public final class MediaSessionManager { } /** - * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is + * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is * created. *

* Do not use this API directly, but create a new instance through the - * MediaSession2.Builder instead. + * {@link MediaSession2.Builder} instead. * * @param token newly created session2 token */ @@ -129,9 +130,6 @@ 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) { @@ -139,31 +137,6 @@ 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 @@ -219,7 +192,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 MediaSession2. + * reject your uses of {@link 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 409060e348417..5c7317892f3f7 100644 --- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java @@ -42,6 +42,8 @@ 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.IActiveSessionsListener; import android.media.session.ICallback; @@ -57,6 +59,7 @@ 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; @@ -1011,50 +1014,17 @@ 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()); } - 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); + 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); } finally { Binder.restoreCallingIdentity(token); } @@ -2235,4 +2205,30 @@ 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); + } + } + } }