MediaSession2: Fix timing issue

Session/Controller needs mProvider. However, if the createProvider()
interacts with other components, than other components may use session
/controller object before mProvider is set.

This CL prevents such issues by calling initialize() to communicate
with other components after the provider is set.

Test: Run all MediaComponents test once
Change-Id: I7f4c52136038a0522471015344881552b678a2ab
This commit is contained in:
Jaewan Kim
2018-01-27 01:34:24 +09:00
parent 90321a74df
commit f7a7706d14
12 changed files with 244 additions and 322 deletions

View File

@@ -254,12 +254,13 @@ public class MediaController2 implements AutoCloseable {
@NonNull @CallbackExecutor Executor executor, @NonNull ControllerCallback callback) {
super();
mProvider = createProvider(context, token, executor, callback);
// This also connects to the token.
// Explicit connect() isn't added on purpose because retrying connect() is impossible with
// session whose session binder is only valid while it's active.
// prevent a controller from reusable after the
// session is released and recreated.
mProvider = createProvider(context, token, executor, callback);
mProvider.initialize();
}
MediaController2Provider createProvider(@NonNull Context context,

View File

@@ -1005,8 +1005,8 @@ public class MediaSession2 implements AutoCloseable {
Executor callbackExecutor, SessionCallback callback) {
super();
mProvider = createProvider(context, player, id, volumeProvider, ratingType, sessionActivity,
callbackExecutor, callback
);
callbackExecutor, callback);
mProvider.initialize();
}
MediaSession2Provider createProvider(Context context, MediaPlayerInterface player, String id,

View File

@@ -19,10 +19,13 @@ package android.media;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
import android.media.session.MediaSessionManager;
import android.media.update.ApiLoader;
import android.media.update.SessionToken2Provider;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import android.os.IInterface;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -37,7 +40,6 @@ import java.lang.annotation.RetentionPolicy;
* It can be also obtained by {@link MediaSessionManager}.
* @hide
*/
// TODO(jaewan): Move Token to updatable!
public final class SessionToken2 {
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE})
@@ -48,91 +50,81 @@ public final class SessionToken2 {
public static final int TYPE_SESSION_SERVICE = 1;
public static final int TYPE_LIBRARY_SERVICE = 2;
private static final String KEY_TYPE = "android.media.token.type";
private static final String KEY_PACKAGE_NAME = "android.media.token.package_name";
private static final String KEY_SERVICE_NAME = "android.media.token.service_name";
private static final String KEY_ID = "android.media.token.id";
private static final String KEY_SESSION_BINDER = "android.media.token.session_binder";
private final SessionToken2Provider mProvider;
private final @TokenType int mType;
private final String mPackageName;
private final String mServiceName;
private final String mId;
private final IMediaSession2 mSessionBinder;
/**
* Constructor for the token. You can only create token for session service or library service
* to use by {@link MediaController2} or {@link MediaBrowser2}.
*
* @param context context
* @param type type
* @param packageName package name
* @param serviceName name of service. Can be {@code null} if it's not an service.
*/
public SessionToken2(@NonNull Context context, @TokenType int type, @NonNull String packageName,
@NonNull String serviceName) {
this(context, -1, type, packageName, serviceName, null, null);
}
/**
* Constructor for the token.
*
* @hide
* @param context context
* @param uid uid
* @param type type
* @param packageName package name
* @param id id
* @param serviceName name of service. Can be {@code null} if it's not an service.
* @param sessionBinder binder for this session. Can be {@code null} if it's service.
* @hide
* @param id id. Can be {@code null} if serviceName is specified.
* @param sessionBinderInterface sessionBinder. Required for the session.
*/
// TODO(jaewan): UID is also needed.
// TODO(jaewan): Unhide
public SessionToken2(@TokenType int type, @NonNull String packageName, @NonNull String id,
@Nullable String serviceName, @Nullable IMediaSession2 sessionBinder) {
// TODO(jaewan): Add sanity check.
mType = type;
mPackageName = packageName;
mId = id;
mServiceName = serviceName;
mSessionBinder = sessionBinder;
@SystemApi
public SessionToken2(@NonNull Context context, int uid, @TokenType int type,
@NonNull String packageName, @Nullable String serviceName, @Nullable String id,
@Nullable IInterface sessionBinderInterface) {
mProvider = ApiLoader.getProvider(context)
.createSessionToken2(context, this, uid, type, packageName,
serviceName, id, sessionBinderInterface);
}
@Override
public int hashCode() {
final int prime = 31;
return mType
+ prime * (mPackageName.hashCode()
+ prime * (mId.hashCode()
+ prime * ((mServiceName != null ? mServiceName.hashCode() : 0)
+ prime * (mSessionBinder != null ? mSessionBinder.asBinder().hashCode() : 0))));
return mProvider.hashCode_impl();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SessionToken2 other = (SessionToken2) obj;
if (!mPackageName.equals(other.getPackageName())
|| !mServiceName.equals(other.getServiceName())
|| !mId.equals(other.getId())
|| mType != other.getType()) {
return false;
}
if (mSessionBinder == other.getSessionBinder()) {
return true;
} else if (mSessionBinder == null || other.getSessionBinder() == null) {
return false;
}
return mSessionBinder.asBinder().equals(other.getSessionBinder().asBinder());
return mProvider.equals_impl(obj);
}
@Override
public String toString() {
return "SessionToken {pkg=" + mPackageName + " id=" + mId + " type=" + mType
+ " service=" + mServiceName + " binder=" + mSessionBinder + "}";
return mProvider.toString_impl();
}
@SystemApi
public SessionToken2Provider getProvider() {
return mProvider;
}
/**
* @return uid of the session
*/
public int getUid() {
return mProvider.getUid_impl();
}
/**
* @return package name
*/
public String getPackageName() {
return mPackageName;
return mProvider.getPackageName_impl();
}
/**
* @return id
*/
public String getId() {
return mId;
return mProvider.getId_imp();
}
/**
@@ -141,82 +133,23 @@ public final class SessionToken2 {
* @see #TYPE_SESSION_SERVICE
*/
public @TokenType int getType() {
return mType;
}
/**
* @return session binder.
* @hide
*/
public @Nullable IMediaSession2 getSessionBinder() {
return mSessionBinder;
}
/**
* @return service name if it's session service.
* @hide
*/
public @Nullable String getServiceName() {
return mServiceName;
return mProvider.getType_impl();
}
/**
* Create a token from the bundle, exported by {@link #toBundle()}.
*
* @param bundle
* @return
*/
public static SessionToken2 fromBundle(@NonNull Bundle bundle) {
if (bundle == null) {
return null;
}
final @TokenType int type = bundle.getInt(KEY_TYPE, -1);
final String packageName = bundle.getString(KEY_PACKAGE_NAME);
final String serviceName = bundle.getString(KEY_SERVICE_NAME);
final String id = bundle.getString(KEY_ID);
final IBinder sessionBinder = bundle.getBinder(KEY_SESSION_BINDER);
// Sanity check.
switch (type) {
case TYPE_SESSION:
if (!(sessionBinder instanceof IMediaSession2)) {
throw new IllegalArgumentException("Session needs sessionBinder");
}
break;
case TYPE_SESSION_SERVICE:
if (TextUtils.isEmpty(serviceName)) {
throw new IllegalArgumentException("Session service needs service name");
}
if (sessionBinder != null && !(sessionBinder instanceof IMediaSession2)) {
throw new IllegalArgumentException("Invalid session binder");
}
break;
default:
throw new IllegalArgumentException("Invalid type");
}
if (TextUtils.isEmpty(packageName) || id == null) {
throw new IllegalArgumentException("Package name nor ID cannot be null.");
}
// TODO(jaewan): Revisit here when we add connection callback to the session for individual
// controller's permission check. With it, sessionBinder should be available
// if and only if for session, not session service.
return new SessionToken2(type, packageName, id, serviceName,
sessionBinder != null ? IMediaSession2.Stub.asInterface(sessionBinder) : null);
public static SessionToken2 fromBundle(@NonNull Context context, @NonNull Bundle bundle) {
return ApiLoader.getProvider(context).SessionToken2_fromBundle(context, bundle);
}
/**
* Create a {@link Bundle} from this token to share it across processes.
*
* @return Bundle
*/
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putString(KEY_PACKAGE_NAME, mPackageName);
bundle.putString(KEY_SERVICE_NAME, mServiceName);
bundle.putString(KEY_ID, mId);
bundle.putInt(KEY_TYPE, mType);
bundle.putBinder(KEY_SESSION_BINDER,
mSessionBinder != null ? mSessionBinder.asBinder() : null);
return bundle;
return mProvider.toBundle_impl();
}
}

View File

@@ -52,6 +52,7 @@ interface ISessionManager {
void setOnMediaKeyListener(in IOnMediaKeyListener listener);
// MediaSession2
Bundle createSessionToken(String callingPackage, String id, IMediaSession2 binder);
boolean onSessionCreated(in Bundle sessionToken);
void onSessionDestroyed(in Bundle sessionToken);
List<Bundle> getSessionTokens(boolean activeSessionOnly, boolean sessionServiceOnly);
}

View File

@@ -337,19 +337,34 @@ public final class MediaSessionManager {
/**
* Called when a {@link MediaSession2} is created.
*
* @hide
*/
// TODO(jaewan): System API
public SessionToken2 createSessionToken(@NonNull String callingPackage, @NonNull String id,
@NonNull IMediaSession2 binder) {
public boolean onSessionCreated(@NonNull SessionToken2 token) {
if (token == null) {
return false;
}
try {
Bundle bundle = mService.createSessionToken(callingPackage, id, binder);
return SessionToken2.fromBundle(bundle);
return mService.onSessionCreated(token.toBundle());
} catch (RemoteException e) {
Log.wtf(TAG, "Cannot communicate with the service.", e);
}
return false;
}
/** Called when a {@link MediaSession2} is destroyed.
* @hide
*/
// TODO(jaewan): System API
public void onSessionDestroyed(@NonNull SessionToken2 token) {
if (token == null) {
return;
}
try {
mService.onSessionDestroyed(token.toBundle());
} catch (RemoteException e) {
Log.wtf(TAG, "Cannot communicate with the service.", e);
}
return null;
}
/**
@@ -367,7 +382,7 @@ public final class MediaSessionManager {
try {
List<Bundle> bundles = mService.getSessionTokens(
/* activeSessionOnly */ true, /* sessionServiceOnly */ false);
return toTokenList(bundles);
return toTokenList(mContext, bundles);
} catch (RemoteException e) {
Log.wtf(TAG, "Cannot communicate with the service.", e);
return Collections.emptyList();
@@ -387,7 +402,7 @@ public final class MediaSessionManager {
try {
List<Bundle> bundles = mService.getSessionTokens(
/* activeSessionOnly */ false, /* sessionServiceOnly */ true);
return toTokenList(bundles);
return toTokenList(mContext, bundles);
} catch (RemoteException e) {
Log.wtf(TAG, "Cannot communicate with the service.", e);
return Collections.emptyList();
@@ -410,18 +425,18 @@ public final class MediaSessionManager {
try {
List<Bundle> bundles = mService.getSessionTokens(
/* activeSessionOnly */ false, /* sessionServiceOnly */ false);
return toTokenList(bundles);
return toTokenList(mContext, bundles);
} catch (RemoteException e) {
Log.wtf(TAG, "Cannot communicate with the service.", e);
return Collections.emptyList();
}
}
private static List<SessionToken2> toTokenList(List<Bundle> bundles) {
private static List<SessionToken2> toTokenList(Context context, List<Bundle> bundles) {
List<SessionToken2> tokens = new ArrayList<>();
if (bundles != null) {
for (int i = 0; i < bundles.size(); i++) {
SessionToken2 token = SessionToken2.fromBundle(bundles.get(i));
SessionToken2 token = SessionToken2.fromBundle(context, bundles.get(i));
if (token != null) {
tokens.add(token);
}

View File

@@ -35,6 +35,8 @@ import java.util.List;
* @hide
*/
public interface MediaController2Provider extends TransportControlProvider {
void initialize();
void close_impl();
SessionToken2 getSessionToken_impl();
boolean isConnected_impl();

View File

@@ -35,6 +35,8 @@ import java.util.List;
* @hide
*/
public interface MediaSession2Provider extends TransportControlProvider {
void initialize();
void close_impl();
void setPlayer_impl(MediaPlayerInterface player);
void setPlayer_impl(MediaPlayerInterface player, VolumeProvider volumeProvider);

View File

@@ -0,0 +1,34 @@
/*
* 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.update;
import android.os.Bundle;
/**
* @hide
*/
public interface SessionToken2Provider {
String getPackageName_impl();
String getId_imp();
int getType_impl();
int getUid_impl();
Bundle toBundle_impl();
int hashCode_impl();
boolean equals_impl(Object obj);
String toString_impl();
}

View File

@@ -34,6 +34,7 @@ import android.media.SessionToken2;
import android.media.VolumeProvider;
import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
import android.media.update.MediaSession2Provider.ControllerInfoProvider;
import android.os.Bundle;
import android.os.IInterface;
import android.util.AttributeSet;
import android.widget.MediaControlView2;
@@ -71,4 +72,8 @@ public interface StaticProvider {
MediaLibrarySession instance, MediaPlayerInterface player, String id,
VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
Executor executor, MediaLibrarySessionCallback callback);
SessionToken2Provider createSessionToken2(Context context, SessionToken2 instance,
int uid, int type, String packageName, String serviceName, String id,
IInterface sessionBinderInterface);
SessionToken2 SessionToken2_fromBundle(Context context, Bundle bundle);
}

View File

@@ -44,138 +44,88 @@ class MediaSession2Record {
private static final boolean DEBUG = true; // TODO(jaewan): Change
private final Context mContext;
private final SessionToken2 mSessionToken;
private final SessionDestroyedListener mSessionDestroyedListener;
// TODO(jaewan): Replace these with the mContext.getMainExecutor()
private final Handler mMainHandler;
private final Executor mMainExecutor;
private MediaController2 mController;
private ControllerCallback mControllerCallback;
private int mSessionPid;
/**
* Constructor
*/
public MediaSession2Record(@NonNull Context context,
public MediaSession2Record(@NonNull Context context, @NonNull SessionToken2 token,
@NonNull SessionDestroyedListener listener) {
mContext = context;
mSessionToken = token;
mSessionDestroyedListener = listener;
mMainHandler = new Handler(Looper.getMainLooper());
mMainExecutor = (runnable) -> {
mMainHandler.post(runnable);
};
}
public int getSessionPid() {
return mSessionPid;
mMainExecutor = (runnable) -> runnable.run();
}
public Context getContext() {
return mContext;
}
@CallSuper
public void onSessionDestroyed() {
if (mController != null) {
mControllerCallback.destroy();
mController.close();
// close() triggers ControllerCallback.onDisconnected() here already.
mController = null;
}
mSessionPid = 0;
}
/**
* Create session token and tell server that session is now active.
*
* @param sessionPid session's pid
* @return a token if successfully set, {@code null} if sanity check fails.
*/
// TODO(jaewan): also add uid for multiuser support
@CallSuper
public @Nullable
SessionToken2 createSessionToken(int sessionPid, String packageName, String id,
IMediaSession2 sessionBinder) {
public boolean onSessionCreated(SessionToken2 token) {
if (mController != null) {
if (mSessionPid != sessionPid) {
// A package uses the same id for session across the different process.
return null;
}
// If a session becomes inactive and then active again very quickly, previous 'inactive'
// may not have delivered yet. Check if it's the case and destroy controller before
// creating its session record to prevents getXXTokens() API from returning duplicated
// tokens.
// TODO(jaewan): Change this. If developer is really creating two sessions with the same
// id, this will silently invalidate previous session and no way for
// developers to know that.
// Instead, keep the list of static session ids from our APIs.
// Also change Controller2Impl.onConnectionChanged / getController.
// Also clean up ControllerCallback#destroy().
if (DEBUG) {
Log.d(TAG, "Session is recreated almost immediately. " + this);
}
onSessionDestroyed();
// Disclaimer: This may fail if following happens for an app.
// Step 1) Create a session in the process #1
// Step 2) Process #1 is killed
// Step 3) Before the death of process #1 is delivered,
// (i.e. ControllerCallback#onDisconnected is called),
// new process is started and create another session with the same
// id in the new process.
// Step 4) fail!!! But this is tricky case that wouldn't happen in normal.
Log.w(TAG, "Cannot create a new session with the id=" + token.getId() + " in the"
+ " pkg=" + token.getPackageName() + ". ID should be unique in a package");
return false;
}
mController = onCreateMediaController(packageName, id, sessionBinder);
mSessionPid = sessionPid;
return mController.getSessionToken();
mController = new MediaController2(mContext, token, mMainExecutor,
new ControllerCallback());
return true;
}
/**
* Called when session becomes active and needs controller to listen session's activeness.
* <p>
* Should be overridden by subclasses to create token with its own extra information.
*/
MediaController2 onCreateMediaController(
String packageName, String id, IMediaSession2 sessionBinder) {
SessionToken2 token = new SessionToken2(
SessionToken2.TYPE_SESSION, packageName, id, null, sessionBinder);
return createMediaController(token);
}
final MediaController2 createMediaController(SessionToken2 token) {
mControllerCallback = new ControllerCallback();
return new MediaController2(mContext, token, mMainExecutor, mControllerCallback);
}
/**
* @return controller. Note that framework can only call oneway calls.
* @return token
*/
public SessionToken2 getToken() {
return mController == null ? null : mController.getSessionToken();
return mSessionToken;
}
/**
* @return controller
*/
public MediaController2 getController() {
return mController;
}
@Override
public String toString() {
return getToken() == null
? "Token {null}"
: "SessionRecord {pid=" + mSessionPid + ", " + getToken().toString() + "}";
? "Token {null}" : "SessionRecord {" + getToken().toString() + "}";
}
private class ControllerCallback extends MediaController2.ControllerCallback {
private final AtomicBoolean mIsActive = new AtomicBoolean(true);
// This is called on the main thread with no lock. So place ensure followings.
// This is called on the random thread with no lock. So place ensure followings.
// 1. Don't touch anything in the parent class that needs synchronization.
// All other APIs in the MediaSession2Record assumes that server would use them with
// the lock hold.
// 2. This can be called after the controller registered is released.
// 2. This can be called after the controller registered is closed.
@Override
public void onDisconnected() {
if (!mIsActive.get()) {
return;
}
if (DEBUG) {
Log.d(TAG, "onDisconnected, token=" + getToken());
}
mSessionDestroyedListener.onSessionDestroyed(MediaSession2Record.this);
}
// TODO(jaewan): Remove this API when we revisit createSessionToken()
public void destroy() {
mIsActive.set(false);
}
};
}

View File

@@ -16,6 +16,8 @@
package com.android.server.media;
import static android.media.SessionToken2.TYPE_SESSION;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.KeyguardManager;
@@ -28,6 +30,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
@@ -130,15 +133,9 @@ public class MediaSessionService extends SystemService implements Monitor {
private final List<MediaSession2Record> mSessions = new ArrayList<>();
private final MediaSession2Record.SessionDestroyedListener mSessionDestroyedListener =
(MediaSession2Record record) -> {
(record) -> {
synchronized (mLock) {
if (DEBUG) {
Log.d(TAG, record.toString() + " becomes inactive");
}
record.onSessionDestroyed();
if (!(record instanceof MediaSessionService2Record)) {
mSessions.remove(record);
}
destroySessionLocked(record);
}
};
@@ -446,14 +443,16 @@ public class MediaSessionService extends SystemService implements Monitor {
}
// TODO(jaewan): Query per users.
// TODO(jaewan): Similar codes are also at the updatable. Can't we share codes?
PackageManager manager = getContext().getPackageManager();
List<ResolveInfo> services = new ArrayList<>();
// If multiple actions are declared for a service, browser gets higher priority.
List<ResolveInfo> libraryServices = getContext().getPackageManager().queryIntentServices(
List<ResolveInfo> libraryServices = manager.queryIntentServices(
new Intent(MediaLibraryService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
if (libraryServices != null) {
services.addAll(libraryServices);
}
List<ResolveInfo> sessionServices = getContext().getPackageManager().queryIntentServices(
List<ResolveInfo> sessionServices = manager.queryIntentServices(
new Intent(MediaSessionService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
if (sessionServices != null) {
services.addAll(sessionServices);
@@ -468,12 +467,19 @@ public class MediaSessionService extends SystemService implements Monitor {
continue;
}
ServiceInfo serviceInfo = services.get(i).serviceInfo;
int uid;
try {
uid = manager.getPackageUid(serviceInfo.packageName,
PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {
continue;
}
String id = (serviceInfo.metaData != null) ? serviceInfo.metaData.getString(
MediaSessionService2.SERVICE_META_DATA) : null;
// Do basic sanity check
// TODO(jaewan): also santity check if it's protected with the system|privileged
// permission
boolean conflict = (getSessionRecordLocked(serviceInfo.name, id) != null);
boolean conflict = (getSessionRecordLocked(uid, serviceInfo.name, id) != null);
if (conflict) {
Log.w(TAG, serviceInfo.packageName + " contains multiple"
+ " MediaSessionService2s declared in the manifest with"
@@ -481,10 +487,12 @@ public class MediaSessionService extends SystemService implements Monitor {
+ serviceInfo.packageName + "/" + serviceInfo.name);
} else {
int type = (libraryServices.contains(services.get(i)))
? SessionToken2.TYPE_LIBRARY_SERVICE : SessionToken2.TYPE_SESSION_SERVICE;
MediaSessionService2Record record =
new MediaSessionService2Record(getContext(), mSessionDestroyedListener,
type, serviceInfo.packageName, serviceInfo.name, id);
? SessionToken2.TYPE_LIBRARY_SERVICE
: SessionToken2.TYPE_SESSION_SERVICE;
SessionToken2 token = new SessionToken2(getContext(), uid, type,
serviceInfo.packageName, serviceInfo.name, id, null);
MediaSession2Record record = new MediaSession2Record(getContext(),
token, mSessionDestroyedListener);
mSessions.add(record);
}
}
@@ -497,17 +505,27 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
MediaSession2Record getSessionRecordLocked(String packageName, String id) {
private MediaSession2Record getSessionRecordLocked(int uid, String packageName, String id) {
for (int i = 0; i < mSessions.size(); i++) {
MediaSession2Record record = mSessions.get(i);
if (record.getToken().getPackageName().equals(packageName)
&& record.getToken().getId().equals(id)) {
return record;
SessionToken2 token = mSessions.get(i).getToken();
if (token.getUid() == uid && token.getPackageName().equals(packageName)
&& token.getId().equals(id)) {
return mSessions.get(i);
}
}
return null;
}
private void destroySessionLocked(MediaSession2Record record) {
if (DEBUG) {
Log.d(TAG, record.toString() + " becomes inactive");
}
record.onSessionDestroyed();
if (record.getToken().getType() == TYPE_SESSION) {
mSessions.remove(record);
}
}
private void enforcePackageName(String packageName, int uid) {
if (TextUtils.isEmpty(packageName)) {
throw new IllegalArgumentException("packageName may not be empty");
@@ -1410,29 +1428,55 @@ public class MediaSessionService extends SystemService implements Monitor {
}
@Override
public Bundle createSessionToken(String sessionPackage, String id,
IMediaSession2 sessionBinder) throws RemoteException {
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
MediaSession2Record record;
SessionToken2 token;
// TODO(jaewan): Add sanity check for the token if calling package is from uid.
public boolean onSessionCreated(Bundle sessionToken) {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final SessionToken2 token = SessionToken2.fromBundle(getContext(), sessionToken);
if (token == null || token.getUid() != uid) {
Log.w(TAG, "onSessionCreated failed, expected caller uid=" + token.getUid()
+ " but from uid=" + uid);
}
if (DEBUG) {
Log.d(TAG, "onSessionCreated " + token);
}
synchronized (mLock) {
record = getSessionRecordLocked(sessionPackage, id);
if (record == null) {
record = new MediaSession2Record(getContext(), mSessionDestroyedListener);
mSessions.add(record);
}
token = record.createSessionToken(pid, sessionPackage, id, sessionBinder);
if (token == null) {
Log.d(TAG, "failed to create session token for " + sessionPackage
+ " from pid=" + pid + ". Previously " + record);
MediaSession2Record record = getSessionRecordLocked(
uid, token.getPackageName(), token.getId());
if (record != null) {
return record.onSessionCreated(token);
} else {
Log.d(TAG, "session " + token + " is created");
record = new MediaSession2Record(
getContext(), token, mSessionDestroyedListener);
mSessions.add(record);
return record.onSessionCreated(token);
}
}
}
@Override
public void onSessionDestroyed(Bundle sessionToken) {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final SessionToken2 token = SessionToken2.fromBundle(getContext(), sessionToken);
if (token == null || token.getUid() != uid) {
Log.w(TAG, "onSessionDestroyed failed, expected caller uid=" + token.getUid()
+ " but from uid=" + uid);
}
if (DEBUG) {
Log.d(TAG, "onSessionDestroyed " + token);
}
synchronized (mLock) {
MediaSession2Record record = getSessionRecordLocked(
uid, token.getPackageName(), token.getId());
if (record != null) {
record.onSessionDestroyed();
} else {
if (DEBUG) {
Log.d(TAG, "Cannot find a session record to destroy. uid=" + uid
+ ", pkg=" + token.getPackageName() + ", id=" + token.getId());
}
}
}
return token == null ? null : token.toBundle();
}
// TODO(jaewan): Protect this API with permission
@@ -1444,8 +1488,8 @@ public class MediaSessionService extends SystemService implements Monitor {
synchronized (mLock) {
for (int i = 0; i < mSessions.size(); i++) {
MediaSession2Record record = mSessions.get(i);
boolean isSessionService = (record instanceof MediaSessionService2Record);
boolean isActive = record.getSessionPid() != 0;
boolean isSessionService = (record.getToken().getType() != TYPE_SESSION);
boolean isActive = record.getController() != null;
if ((!activeSessionOnly && isSessionService)
|| (!sessionServiceOnly && isActive)) {
SessionToken2 token = record.getToken();

View File

@@ -1,65 +0,0 @@
/*
* 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 com.android.server.media;
import android.content.Context;
import android.media.IMediaSession2;
import android.media.MediaController2;
import android.media.SessionToken2;
import android.media.MediaSessionService2;
/**
* Records a {@link MediaSessionService2}.
* <p>
* Owner of this object should handle synchronization.
*/
class MediaSessionService2Record extends MediaSession2Record {
private static final boolean DEBUG = true; // TODO(jaewan): Modify
private static final String TAG = "SessionService2Record";
private final int mType;
private final String mServiceName;
private final SessionToken2 mToken;
public MediaSessionService2Record(Context context,
SessionDestroyedListener sessionDestroyedListener, int type,
String packageName, String serviceName, String id) {
super(context, sessionDestroyedListener);
mType = type;
mServiceName = serviceName;
mToken = new SessionToken2(mType, packageName, id, mServiceName, null);
}
/**
* Overriden to change behavior of
* {@link #createSessionToken(int, String, String, IMediaSession2)}}.
*/
@Override
MediaController2 onCreateMediaController(
String packageName, String id, IMediaSession2 sessionBinder) {
SessionToken2 token = new SessionToken2(mType, packageName, id, mServiceName, sessionBinder);
return createMediaController(token);
}
/**
* @return token with no session binder information.
*/
@Override
public SessionToken2 getToken() {
return mToken;
}
}