blob: eb4229732a213a01c8a9df747d794073479efb64 [file] [log] [blame]
/*
* 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 androidx.media;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
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.support.v4.media.session.MediaSessionCompat;
import android.text.TextUtils;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Represents an ongoing {@link MediaSession2}.
* <p>
* This may be passed to apps by the session owner to allow them to create a
* {@link MediaController2} to communicate with the session.
* <p>
* It can be also obtained by {@link MediaSessionManager}.
*/
// New version of MediaSession.Token for following reasons
// - Stop implementing Parcelable for updatable support
// - Represent session and library service (formerly browser service) in one class.
// Previously MediaSession.Token was for session and ComponentName was for service.
public final class SessionToken2 {
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE})
public @interface TokenType {
}
/**
* Type for {@link MediaSession2}.
*/
public static final int TYPE_SESSION = 0;
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public static final int TYPE_SESSION_SERVICE = 1;
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public static final int TYPE_LIBRARY_SERVICE = 2;
//private final SessionToken2Provider mProvider;
// From the return value of android.os.Process.getUidForName(String) when error
private static final int UID_UNKNOWN = -1;
private static final String KEY_UID = "android.media.token.uid";
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_TOKEN = "android.media.token.session_token";
private final int mUid;
private final @TokenType int mType;
private final String mPackageName;
private final String mServiceName;
private final String mId;
private final MediaSessionCompat.Token mSessionCompatToken;
private final ComponentName mComponentName;
/**
* @hide
* 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 The context.
* @param serviceComponent The component name of the media browser service.
*/
@RestrictTo(LIBRARY_GROUP)
public SessionToken2(@NonNull Context context, @NonNull ComponentName serviceComponent) {
this(context, serviceComponent, UID_UNKNOWN);
}
/**
* 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 The context.
* @param serviceComponent The component name of the media browser service.
* @param uid uid of the app.
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public SessionToken2(@NonNull Context context, @NonNull ComponentName serviceComponent,
int uid) {
if (serviceComponent == null) {
throw new IllegalArgumentException("serviceComponent shouldn't be null");
}
mComponentName = serviceComponent;
mPackageName = serviceComponent.getPackageName();
mServiceName = serviceComponent.getClassName();
// Calculate uid if it's not specified.
final PackageManager manager = context.getPackageManager();
if (uid < 0) {
try {
uid = manager.getApplicationInfo(mPackageName, 0).uid;
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException("Cannot find package " + mPackageName);
}
}
mUid = uid;
// Infer id and type from package name and service name
String id = getSessionIdFromService(manager, MediaLibraryService2.SERVICE_INTERFACE,
serviceComponent);
if (id != null) {
mId = id;
mType = TYPE_LIBRARY_SERVICE;
} else {
// retry with session service
mId = getSessionIdFromService(manager, MediaSessionService2.SERVICE_INTERFACE,
serviceComponent);
mType = TYPE_SESSION_SERVICE;
}
if (mId == null) {
throw new IllegalArgumentException("service " + mServiceName + " doesn't implement"
+ " session service nor library service. Use service's full name.");
}
mSessionCompatToken = null;
}
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
SessionToken2(int uid, int type, String packageName, String serviceName,
String id, MediaSessionCompat.Token sessionCompatToken) {
mUid = uid;
mType = type;
mPackageName = packageName;
mServiceName = serviceName;
mComponentName = (mType == TYPE_SESSION) ? null
: new ComponentName(packageName, serviceName);
mId = id;
mSessionCompatToken = sessionCompatToken;
}
@Override
public int hashCode() {
final int prime = 31;
return mType
+ prime * (mUid
+ prime * (mPackageName.hashCode()
+ prime * (mId.hashCode()
+ prime * (mServiceName != null ? mServiceName.hashCode() : 0))));
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof SessionToken2)) {
return false;
}
SessionToken2 other = (SessionToken2) obj;
return mUid == other.mUid
&& TextUtils.equals(mPackageName, other.mPackageName)
&& TextUtils.equals(mServiceName, other.mServiceName)
&& TextUtils.equals(mId, other.mId)
&& mType == other.mType;
}
@Override
public String toString() {
return "SessionToken {pkg=" + mPackageName + " id=" + mId + " type=" + mType
+ " service=" + mServiceName + " sessionCompatToken=" + mSessionCompatToken + "}";
}
/**
* @return uid of the session
*/
public int getUid() {
return mUid;
}
/**
* @return package name
*/
public @NonNull String getPackageName() {
return mPackageName;
}
/**
* @return service name. Can be {@code null} for TYPE_SESSION.
*/
public @Nullable String getServiceName() {
return mServiceName;
}
/**
* @hide
* @return component name of this session token. Can be null for TYPE_SESSION.
*/
@RestrictTo(LIBRARY_GROUP)
public ComponentName getComponentName() {
return mComponentName;
}
/**
* @return id
*/
public String getId() {
return mId;
}
/**
* @return type of the token
* @see #TYPE_SESSION
*/
public @TokenType int getType() {
return mType;
}
/**
* 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 int uid = bundle.getInt(KEY_UID);
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 MediaSessionCompat.Token token = bundle.getParcelable(KEY_SESSION_TOKEN);
// Sanity check.
switch (type) {
case TYPE_SESSION:
if (token == null) {
throw new IllegalArgumentException("Unexpected token for session,"
+ " SessionCompat.Token=" + token);
}
break;
case TYPE_SESSION_SERVICE:
case TYPE_LIBRARY_SERVICE:
if (TextUtils.isEmpty(serviceName)) {
throw new IllegalArgumentException("Session service needs service name");
}
break;
default:
throw new IllegalArgumentException("Invalid type");
}
if (TextUtils.isEmpty(packageName) || id == null) {
throw new IllegalArgumentException("Package name nor ID cannot be null.");
}
return new SessionToken2(uid, type, packageName, serviceName, id, token);
}
/**
* Create a {@link Bundle} from this token to share it across processes.
* @return Bundle
*/
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putInt(KEY_UID, mUid);
bundle.putString(KEY_PACKAGE_NAME, mPackageName);
bundle.putString(KEY_SERVICE_NAME, mServiceName);
bundle.putString(KEY_ID, mId);
bundle.putInt(KEY_TYPE, mType);
bundle.putParcelable(KEY_SESSION_TOKEN, mSessionCompatToken);
return bundle;
}
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public static String getSessionId(ResolveInfo resolveInfo) {
if (resolveInfo == null || resolveInfo.serviceInfo == null) {
return null;
} else if (resolveInfo.serviceInfo.metaData == null) {
return "";
} else {
return resolveInfo.serviceInfo.metaData.getString(
MediaSessionService2.SERVICE_META_DATA, "");
}
}
MediaSessionCompat.Token getSessionCompatToken() {
return mSessionCompatToken;
}
private static String getSessionIdFromService(PackageManager manager, String serviceInterface,
ComponentName serviceComponent) {
Intent serviceIntent = new Intent(serviceInterface);
// Use queryIntentServices to find services with MediaLibraryService2.SERVICE_INTERFACE.
// We cannot use resolveService with intent specified class name, because resolveService
// ignores actions if Intent.setClassName() is specified.
serviceIntent.setPackage(serviceComponent.getPackageName());
List<ResolveInfo> list = manager.queryIntentServices(
serviceIntent, PackageManager.GET_META_DATA);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ResolveInfo resolveInfo = list.get(i);
if (resolveInfo == null || resolveInfo.serviceInfo == null) {
continue;
}
if (TextUtils.equals(
resolveInfo.serviceInfo.name, serviceComponent.getClassName())) {
return getSessionId(resolveInfo);
}
}
}
return null;
}
}