Merge "Remove MediaController2 usages in MediaSessionServiceImpl"

This commit is contained in:
TreeHugger Robot
2019-01-25 14:21:24 +00:00
committed by Android (Google) Code Review
11 changed files with 203 additions and 83 deletions

View File

@@ -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<android.media.Session2Token> 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);
}

View File

@@ -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);

View File

@@ -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",
],
}

View File

@@ -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";

View File

@@ -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

View File

@@ -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());

View File

@@ -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;
}

View File

@@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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,

View File

@@ -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<ControllerLink> getSessions(in ComponentName compName, int userId);
List<Session2Token> getSession2Tokens(int userId);
void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,

View File

@@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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}.
*/

View File

@@ -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<Session2Token> 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);
}
}
}
}