Merge "Media2: Initial commit of Session2Token"
diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
new file mode 100644
index 0000000..af14078
--- /dev/null
+++ b/media/java/android/media/Session2Token.java
@@ -0,0 +1,288 @@
+/*
+ * 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents an ongoing {@link MediaSession2} or a {@link 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.
+ * Use the <a href="{@docRoot}jetpack/androidx/">Support Library</a> instead.
+ * {@link androidx.media2.SessionToken} 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.
+ * <p>
+ * It can be also obtained by {@link MediaSessionManager}.
+ *
+ * @hide
+ */
+// New version of MediaSession2.Token for following reasons
+//   - Stop implementing Parcelable for updatable support
+//   - Represent session and library service (formerly browser service) in one class.
+//     Previously MediaSession2.Token was for session and ComponentName was for service.
+//     This helps controller apps to keep target of dispatching media key events in uniform way.
+//     For details about the reason, see following. (Android O+)
+//         android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged
+public final class Session2Token implements Parcelable {
+    private static final String TAG = "Session2Token";
+
+    public static final Creator<Session2Token> CREATOR = new Creator<Session2Token>() {
+        @Override
+        public Session2Token createFromParcel(Parcel p) {
+            return new Session2Token(p);
+        }
+
+        @Override
+        public Session2Token[] newArray(int size) {
+            return new Session2Token[size];
+        }
+    };
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "TYPE_", value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE})
+    public @interface TokenType {
+    }
+
+    /**
+     * Type for {@link MediaSession2}.
+     */
+    public static final int TYPE_SESSION = 0;
+
+    /**
+     * Type for {@link MediaSessionService2}.
+     */
+    public static final int TYPE_SESSION_SERVICE = 1;
+
+    /**
+     * Type for {@link MediaLibraryService2}.
+     */
+    public static final int TYPE_LIBRARY_SERVICE = 2;
+
+    private final int mUid;
+    private final @TokenType int mType;
+    private final String mPackageName;
+    private final String mServiceName;
+    private final IBinder mISession;
+    private final ComponentName mComponentName;
+
+    /**
+     * Constructor for the token. You can create token of {@link MediaSessionService2},
+     * {@link MediaLibraryService2} for {@link MediaController2} or {@link MediaBrowser2}.
+     *
+     * @param context The context.
+     * @param serviceComponent The component name of the service.
+     */
+    public Session2Token(@NonNull Context context, @NonNull ComponentName serviceComponent) {
+        if (context == null) {
+            throw new IllegalArgumentException("context shouldn't be null");
+        }
+        if (serviceComponent == null) {
+            throw new IllegalArgumentException("serviceComponent shouldn't be null");
+        }
+
+        final PackageManager manager = context.getPackageManager();
+        final int uid = getUid(manager, serviceComponent.getPackageName());
+
+        // TODO: Uncomment below to stop hardcode type.
+        final int type = TYPE_SESSION_SERVICE;
+//        final int type;
+//        if (isInterfaceDeclared(manager, MediaLibraryService2.SERVICE_INTERFACE,
+//                serviceComponent)) {
+//            type = TYPE_LIBRARY_SERVICE;
+//        } else if (isInterfaceDeclared(manager, MediaSessionService2.SERVICE_INTERFACE,
+//                    serviceComponent)) {
+//            type = TYPE_SESSION_SERVICE;
+//        } else if (isInterfaceDeclared(manager,
+//                        MediaBrowserServiceCompat.SERVICE_INTERFACE, serviceComponent)) {
+//            type = TYPE_BROWSER_SERVICE_LEGACY;
+//        } else {
+//            throw new IllegalArgumentException(serviceComponent + " doesn't implement none of"
+//                    + " MediaSessionService2, MediaLibraryService2, MediaBrowserService nor"
+//                    + " MediaBrowserServiceCompat. Use service's full name.");
+//        }
+        mComponentName = serviceComponent;
+        mPackageName = serviceComponent.getPackageName();
+        mServiceName = serviceComponent.getClassName();
+        mUid = uid;
+        mType = type;
+        mISession = null;
+    }
+
+    // TODO: Uncomment below
+//    Session2Token(int uid, int type, String packageName, IMediaSession2 iSession) {
+//        mUid = uid;
+//        mType = type;
+//        mPackageName = packageName;
+//        mServiceName = null;
+//        mComponentName = null;
+//        mISession = iSession.asBinder();
+//    }
+
+    Session2Token(Parcel in) {
+        mUid = in.readInt();
+        mType = in.readInt();
+        mPackageName = in.readString();
+        mServiceName = in.readString();
+        // TODO: Uncomment below and stop hardcode mISession
+        mISession = null;
+        //mISession = ISession.Stub.asInterface(in.readStrongBinder());
+        mComponentName = ComponentName.unflattenFromString(in.readString());
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mUid);
+        dest.writeInt(mType);
+        dest.writeString(mPackageName);
+        dest.writeString(mServiceName);
+        // TODO: Uncomment below
+        //dest.writeStrongBinder(mISession.asBinder());
+        dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mType, mUid, mPackageName, mServiceName, mISession);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Session2Token)) {
+            return false;
+        }
+        Session2Token other = (Session2Token) obj;
+        return mUid == other.mUid
+                && TextUtils.equals(mPackageName, other.mPackageName)
+                && TextUtils.equals(mServiceName, other.mServiceName)
+                && mType == other.mType
+                && Objects.equals(mISession, other.mISession);
+    }
+
+    @Override
+    public String toString() {
+        return "Session2Token {pkg=" + mPackageName + " type=" + mType
+                + " service=" + mServiceName + " IMediaSession2=" + mISession + "}";
+    }
+
+    /**
+     * @return uid of the session
+     */
+    public int getUid() {
+        return mUid;
+    }
+
+    /**
+     * @return package name of the session
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * @return service name of the session. Can be {@code null} for {@link #TYPE_SESSION}.
+     */
+    @Nullable
+    public String getServiceName() {
+        return mServiceName;
+    }
+
+    /**
+     * @hide
+     * @return component name of the session. Can be {@code null} for {@link #TYPE_SESSION}.
+     */
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * @return type of the token
+     * @see #TYPE_SESSION
+     * @see #TYPE_SESSION_SERVICE
+     * @see #TYPE_LIBRARY_SERVICE
+     */
+    public @TokenType int getType() {
+        return mType;
+    }
+
+    /**
+     * @hide
+     */
+    public Object getBinder() {
+        return mISession;
+    }
+
+    private static boolean isInterfaceDeclared(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 true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static int getUid(PackageManager manager, String packageName) {
+        try {
+            return manager.getApplicationInfo(packageName, 0).uid;
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalArgumentException("Cannot find package " + packageName);
+        }
+    }
+}