public final class

SessionToken

extends java.lang.Object

implements VersionedParcelable

 java.lang.Object

↳androidx.media2.session.SessionToken

Gradle dependencies

compile group: 'androidx.media2', name: 'media2-session', version: '1.2.1'

  • groupId: androidx.media2
  • artifactId: media2-session
  • version: 1.2.1

Artifact androidx.media2:media2-session:1.2.1 it located at Google repository (https://maven.google.com/)

Overview

Represents an ongoing MediaSession or a MediaSessionService. If it's representing a session service, it may not be ongoing.

This may be passed to apps by the session owner to allow them to create a MediaController to communicate with the session.

It can be also obtained by MediaSessionManager.

Summary

Fields
public static final intTYPE_LIBRARY_SERVICE

Type for MediaLibraryService.

public static final intTYPE_SESSION

Type for MediaSession.

public static final intTYPE_SESSION_SERVICE

Type for MediaSessionService.

Constructors
publicSessionToken(Context context, ComponentName serviceComponent)

Constructor for the token.

Methods
public static voidcreateSessionToken(Context context, android.support.v4.media.session.MediaSessionCompat.Token compatToken, SessionToken.OnSessionTokenCreatedListener listener)

Creates SessionToken object from MediaSessionCompat.Token.

public booleanequals(java.lang.Object obj)

public java.lang.ObjectgetBinder()

public ComponentNamegetComponentName()

public BundlegetExtras()

public java.lang.StringgetPackageName()

public java.lang.StringgetServiceName()

public intgetType()

public intgetUid()

public inthashCode()

public booleanisLegacySession()

public java.lang.StringtoString()

from java.lang.Objectclone, finalize, getClass, notify, notifyAll, wait, wait, wait

Fields

public static final int TYPE_SESSION

Type for MediaSession.

public static final int TYPE_SESSION_SERVICE

Type for MediaSessionService.

public static final int TYPE_LIBRARY_SERVICE

Type for MediaLibraryService.

Constructors

public SessionToken(Context context, ComponentName serviceComponent)

Constructor for the token. You can create token of MediaSessionService, MediaLibraryService or MediaBrowserServiceCompat for MediaController or MediaBrowser.

Parameters:

context: The context.
serviceComponent: The component name of the service.

Methods

public int hashCode()

public boolean equals(java.lang.Object obj)

public java.lang.String toString()

public int getUid()

Returns:

uid of the session

public java.lang.String getPackageName()

Returns:

package name of the session

public java.lang.String getServiceName()

Returns:

service name of the session. Can be null for SessionToken.TYPE_SESSION.

public ComponentName getComponentName()

Returns:

component name of the session. Can be null for SessionToken.TYPE_SESSION.

public int getType()

Returns:

type of the token

See also: SessionToken.TYPE_SESSION, SessionToken.TYPE_SESSION_SERVICE, SessionToken.TYPE_LIBRARY_SERVICE

public Bundle getExtras()

Returns:

extras of the token

See also: MediaSession.Builder.setExtras(Bundle)

public boolean isLegacySession()

public java.lang.Object getBinder()

public static void createSessionToken(Context context, android.support.v4.media.session.MediaSessionCompat.Token compatToken, SessionToken.OnSessionTokenCreatedListener listener)

Creates SessionToken object from MediaSessionCompat.Token. When the SessionToken is ready, OnSessionTokenCreateListener will be called.

Source

/*
 * Copyright 2019 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.media2.session;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;

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.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.v4.media.session.MediaControllerCompat;
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 androidx.media.MediaBrowserServiceCompat;
import androidx.media.MediaSessionManager;
import androidx.media2.common.ClassVerificationHelper;
import androidx.versionedparcelable.ParcelField;
import androidx.versionedparcelable.VersionedParcelable;
import androidx.versionedparcelable.VersionedParcelize;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;

/**
 * Represents an ongoing {@link MediaSession} or a {@link MediaSessionService}.
 * If it's representing a session service, it may not be ongoing.
 * <p>
 * This may be passed to apps by the session owner to allow them to create a
 * {@link MediaController} 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.
//     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
@VersionedParcelize
public final class SessionToken implements VersionedParcelable {
    private static final String TAG = "SessionToken";

    private static final long WAIT_TIME_MS_FOR_SESSION_READY = 300;
    private static final int MSG_SEND_TOKEN2_FOR_LEGACY_SESSION = 1000;

    /**
     * @hide
     */
    @RestrictTo(LIBRARY)
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE})
    public @interface TokenType {
    }

    /**
     * Type for {@link MediaSession}.
     */
    public static final int TYPE_SESSION = 0;

    /**
     * Type for {@link MediaSessionService}.
     */
    public static final int TYPE_SESSION_SERVICE = 1;

    /**
     * Type for {@link MediaLibraryService}.
     */
    public static final int TYPE_LIBRARY_SERVICE = 2;

    /**
     * Type for {@link MediaSessionCompat}.
     */
    static final int TYPE_SESSION_LEGACY = 100;

    /**
     * Type for {@link MediaBrowserServiceCompat}.
     */
    static final int TYPE_BROWSER_SERVICE_LEGACY = 101;

    @ParcelField(1)
    SessionTokenImpl mImpl;

    // WARNING: Adding a new ParcelField may break old library users (b/152830728)

    /**
     * Constructor for the token. You can create token of {@link MediaSessionService},
     * {@link MediaLibraryService} or {@link MediaBrowserServiceCompat} for
     * {@link MediaController} or {@link MediaBrowser}.
     *
     * @param context The context.
     * @param serviceComponent The component name of the service.
     */
    public SessionToken(@NonNull Context context, @NonNull ComponentName serviceComponent) {
        if (context == null) {
            throw new NullPointerException("context shouldn't be null");
        }
        if (serviceComponent == null) {
            throw new NullPointerException("serviceComponent shouldn't be null");
        }
        final PackageManager manager = context.getPackageManager();
        final int uid = getUid(manager, serviceComponent.getPackageName());

        final int type;
        if (isInterfaceDeclared(manager, MediaLibraryService.SERVICE_INTERFACE,
                serviceComponent)) {
            type = TYPE_LIBRARY_SERVICE;
        } else if (isInterfaceDeclared(manager, MediaSessionService.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"
                    + " MediaSessionService, MediaLibraryService, MediaBrowserService nor"
                    + " MediaBrowserServiceCompat. Use service's full name");
        }
        if (type != TYPE_BROWSER_SERVICE_LEGACY) {
            mImpl = new SessionTokenImplBase(serviceComponent, uid, type);
        } else {
            mImpl = new SessionTokenImplLegacy(serviceComponent, uid);
        }
    }

    SessionToken(SessionTokenImpl impl) {
        mImpl = impl;
    }

    /**
     * Used for {@link VersionedParcelable}
     */
    SessionToken() {
        // do nothing
    }

    @Override
    public int hashCode() {
        return mImpl.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof SessionToken)) {
            return false;
        }
        SessionToken other = (SessionToken) obj;
        return mImpl.equals(other.mImpl);
    }

    @Override
    public String toString() {
        return mImpl.toString();
    }

    /**
     * @return uid of the session
     */
    public int getUid() {
        return mImpl.getUid();
    }

    /**
     * @return package name of the session
     */
    @NonNull
    public String getPackageName() {
        return mImpl.getPackageName();
    }

    /**
     * @return service name of the session. Can be {@code null} for {@link #TYPE_SESSION}.
     */
    @Nullable
    public String getServiceName() {
        return mImpl.getServiceName();
    }

    /**
     * @hide
     * @return component name of the session. Can be {@code null} for {@link #TYPE_SESSION}.
     */
    @RestrictTo(LIBRARY)
    public ComponentName getComponentName() {
        return mImpl.getComponentName();
    }

    /**
     * @return type of the token
     * @see #TYPE_SESSION
     * @see #TYPE_SESSION_SERVICE
     * @see #TYPE_LIBRARY_SERVICE
     */
    @TokenType
    public int getType() {
        return mImpl.getType();
    }

    /**
     * @return extras of the token
     * @see MediaSession.Builder#setExtras(Bundle)
     */
    @NonNull
    public Bundle getExtras() {
        Bundle extras = mImpl.getExtras();
        if (extras == null || MediaUtils.doesBundleHaveCustomParcelable(extras)) {
            return Bundle.EMPTY;
        }
        return new Bundle(extras);
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY)
    public boolean isLegacySession() {
        return mImpl.isLegacySession();
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY)
    public Object getBinder() {
        return mImpl.getBinder();
    }

    /**
     * Creates SessionToken object from MediaSessionCompat.Token.
     * When the SessionToken is ready, OnSessionTokenCreateListener will be called.
     * @hide
     */
    @RestrictTo(LIBRARY)
    public static void createSessionToken(@NonNull final Context context,
            @NonNull final MediaSessionCompat.Token compatToken,
            @NonNull final OnSessionTokenCreatedListener listener) {
        if (context == null) {
            throw new NullPointerException("context shouldn't be null");
        }
        if (compatToken == null) {
            throw new NullPointerException("compatToken shouldn't be null");
        }
        if (listener == null) {
            throw new NullPointerException("listener shouldn't be null");
        }

        // If the compat token already has the SessionToken in itself, just notify with that token.
        VersionedParcelable token2 = compatToken.getSession2Token();
        if (token2 instanceof SessionToken) {
            listener.onSessionTokenCreated(compatToken, (SessionToken) token2);
            return;
        }

        // Try retrieving media2 token by connecting to the session.
        final MediaControllerCompat controller = createMediaControllerCompat(context, compatToken);

        final String packageName = controller.getPackageName();
        final int uid = getUid(context.getPackageManager(), packageName);
        final HandlerThread thread = new HandlerThread(TAG);
        thread.start();
        final Handler handler = new Handler(thread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                synchronized (listener) {
                    if (msg.what != MSG_SEND_TOKEN2_FOR_LEGACY_SESSION) {
                        return;
                    }
                    controller.unregisterCallback((MediaControllerCompat.Callback) msg.obj);

                    // MediaControllerCompat.Callback#onSessionReady() is not called, which means
                    // that the connected session is a framework MediaSession instance.
                    SessionToken resultToken = new SessionToken(new SessionTokenImplLegacy(
                            compatToken, packageName, uid, controller.getSessionInfo()));

                    // To prevent repeating this process with the same compat token, put the result
                    // media2 token inside of the compat token.
                    compatToken.setSession2Token(resultToken);

                    listener.onSessionTokenCreated(compatToken, resultToken);
                    quitHandlerThread(thread);
                }
            }
        };

        MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
            @Override
            public void onSessionReady() {
                // The connected session is a MediaSessionCompat instance.
                synchronized (listener) {
                    handler.removeMessages(MSG_SEND_TOKEN2_FOR_LEGACY_SESSION);
                    controller.unregisterCallback(this);

                    // TODO: Add logic for getting media2 token in API 21- by using binder.

                    SessionToken resultToken;
                    if (compatToken.getSession2Token() instanceof SessionToken) {
                        // TODO(b/132928776): Add tests for this code path.
                        // The connected MediaSessionCompat is created by media2.MediaSession
                        resultToken = (SessionToken) compatToken.getSession2Token();
                    } else {
                        // The connected MediaSessionCompat is standalone.
                        resultToken = new SessionToken(new SessionTokenImplLegacy(
                                compatToken, packageName, uid, controller.getSessionInfo()));
                        // To prevent repeating this process with the same compat token,
                        // put the result media2 token inside of the compat token.
                        compatToken.setSession2Token(resultToken);
                    }

                    listener.onSessionTokenCreated(compatToken, resultToken);
                    quitHandlerThread(thread);
                }
            }
        };
        synchronized (listener) {
            controller.registerCallback(callback, handler);
            Message msg = handler.obtainMessage(MSG_SEND_TOKEN2_FOR_LEGACY_SESSION, callback);
            handler.sendMessageDelayed(msg, WAIT_TIME_MS_FOR_SESSION_READY);
        }
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    static void quitHandlerThread(HandlerThread thread) {
        if (Build.VERSION.SDK_INT >= 18) {
            ClassVerificationHelper.HandlerThread.Api18.quitSafely(thread);
        } else {
            thread.quit();
        }
    }

    private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface,
            ComponentName serviceComponent) {
        Intent serviceIntent = new Intent(serviceInterface);
        // Use queryIntentServices to find services with MediaLibraryService.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);
        }
    }

    private static MediaControllerCompat createMediaControllerCompat(Context context,
            MediaSessionCompat.Token sessionToken) {
        return new MediaControllerCompat(context, sessionToken);
    }

    /**
     * @hide
     * Interface definition of a listener to be invoked when a {@link SessionToken token2} object
     * is created from a {@link MediaSessionCompat.Token compat token}.
     *
     * @see #createSessionToken
     */
    @RestrictTo(LIBRARY)
    public interface OnSessionTokenCreatedListener {
        /**
         * Called when SessionToken object is created.
         *
         * @param compatToken the compat token used for creating {@code token2}
         * @param sessionToken the created SessionToken object
         */
        void onSessionTokenCreated(MediaSessionCompat.Token compatToken, SessionToken sessionToken);
    }

    interface SessionTokenImpl extends VersionedParcelable {
        boolean isLegacySession();
        int getUid();
        @NonNull String getPackageName();
        @Nullable String getServiceName();
        @Nullable ComponentName getComponentName();
        @TokenType int getType();
        @Nullable Bundle getExtras();
        Object getBinder();
    }
}