Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dd439786 authored by Jaewan Kim's avatar Jaewan Kim
Browse files

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: Ic6eb1a7f96a2084b3a011da30a5053aff5620977
parent d21fb560
Loading
Loading
Loading
Loading
+12 −6
Original line number Diff line number Diff line
@@ -92,7 +92,6 @@ public class MediaController2Impl implements MediaController2Provider {
    public MediaController2Impl(Context context, MediaController2 instance, SessionToken2 token,
            Executor executor, ControllerCallback callback) {
        mInstance = instance;

        if (context == null) {
            throw new IllegalArgumentException("context shouldn't be null");
        }
@@ -115,21 +114,28 @@ public class MediaController2Impl implements MediaController2Provider {
        };

        mSessionBinder = null;
    }

        if (token.getSessionBinder() == null) {
    @Override
    public void initialize() {
        SessionToken2Impl impl = SessionToken2Impl.from(mToken);
        // TODO(jaewan): More sanity checks.
        if (impl.getSessionBinder() == null) {
            // Session service
            mServiceConnection = new SessionServiceConnection();
            connectToService();
        } else {
            // Session
            mServiceConnection = null;
            connectToSession(token.getSessionBinder());
            connectToSession(impl.getSessionBinder());
        }
    }

    // Should be only called by constructor.
    private void connectToService() {
        // Service. Needs to get fresh binder whenever connection is needed.
        SessionToken2Impl impl = SessionToken2Impl.from(mToken);
        final Intent intent = new Intent(MediaSessionService2.SERVICE_INTERFACE);
        intent.setClassName(mToken.getPackageName(), mToken.getServiceName());
        intent.setClassName(mToken.getPackageName(), impl.getServiceName());

        // Use bindService() instead of startForegroundService() to start session service for three
        // reasons.
@@ -166,7 +172,7 @@ public class MediaController2Impl implements MediaController2Provider {
    @Override
    public void close_impl() {
        if (DEBUG) {
            Log.d(TAG, "relese from " + mToken);
            Log.d(TAG, "release from " + mToken);
        }
        final IMediaSession2 binder;
        synchronized (mLock) {
+3 −4
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.media.MediaPlayerInterface;
import android.media.MediaSession2;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSessionService2;
import android.media.SessionToken2;
import android.media.VolumeProvider;
import android.media.update.MediaLibraryService2Provider;
import android.os.Bundle;
@@ -55,10 +56,8 @@ public class MediaLibraryService2Impl extends MediaSessionService2Impl implement
    }

    @Override
    Intent createServiceIntent() {
        Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
        serviceIntent.setAction(MediaLibraryService2.SERVICE_INTERFACE);
        return serviceIntent;
    int getSessionType() {
        return SessionToken2.TYPE_LIBRARY_SERVICE;
    }

    public static class MediaLibrarySessionImpl extends MediaSession2Impl
+86 −30
Original line number Diff line number Diff line
@@ -16,14 +16,21 @@

package com.android.media;

import static android.media.SessionToken2.TYPE_LIBRARY_SERVICE;
import static android.media.SessionToken2.TYPE_SESSION;
import static android.media.SessionToken2.TYPE_SESSION_SERVICE;

import android.Manifest.permission;
import android.app.PendingIntent;
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.media.AudioAttributes;
import android.media.IMediaSession2Callback;
import android.media.MediaItem2;
import android.media.MediaLibraryService2;
import android.media.MediaPlayerInterface;
import android.media.MediaSession2;
import android.media.MediaSession2.Builder;
@@ -33,15 +40,18 @@ import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
import android.media.PlaybackState2;
import android.media.SessionToken2;
import android.media.VolumeProvider;
import android.media.session.MediaSessionManager;
import android.media.update.MediaSession2Provider;
import android.os.Bundle;
import android.os.Process;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.support.annotation.GuardedBy;
import android.text.TextUtils;
import android.util.Log;

import java.lang.ref.WeakReference;
@@ -95,25 +105,69 @@ public class MediaSession2Impl implements MediaSession2Provider {
        mCallback = callback;
        mCallbackExecutor = callbackExecutor;
        mSessionStub = new MediaSession2Stub(this);
        // Ask server to create session token for following reasons.
        //   1. Make session ID unique per package.
        //      Server can only know if the package has another process and has another session
        //      with the same id. Let server check this.
        //      Note that 'ID is unique per package' is important for controller to distinguish

        // Infer type from the id and package name.
        String sessionService = getServiceName(context, MediaSessionService2.SERVICE_INTERFACE, id);
        String libraryService = getServiceName(context, MediaLibraryService2.SERVICE_INTERFACE, id);
        if (sessionService != null && libraryService != null) {
            throw new IllegalArgumentException("Ambiguous session type. Multiple"
                    + " session services define the same id=" + id);
        } else if (sessionService != null) {
            mSessionToken = new SessionToken2(context, Process.myUid(), TYPE_SESSION_SERVICE,
                    mContext.getPackageName(), sessionService, id, mSessionStub);
        } else if (libraryService != null) {
            mSessionToken = new SessionToken2(context, Process.myUid(), TYPE_LIBRARY_SERVICE,
                    mContext.getPackageName(), libraryService, id, mSessionStub);
        } else {
            mSessionToken = new SessionToken2(context, Process.myUid(), TYPE_SESSION,
                    mContext.getPackageName(), null, id, mSessionStub);
        }

        // Only remember player. Actual settings will be done in the initialize().
        mPlayer = player;
    }

    private static String getServiceName(Context context, String serviceAction, String id) {
        PackageManager manager = context.getPackageManager();
        Intent serviceIntent = new Intent(serviceAction);
        serviceIntent.setPackage(context.getPackageName());
        List<ResolveInfo> services = manager.queryIntentServices(serviceIntent,
                PackageManager.GET_META_DATA);
        String serviceName = null;
        if (services != null) {
            for (int i = 0; i < services.size(); i++) {
                String serviceId = SessionToken2Impl.getSessionId(services.get(i));
                if (serviceId != null && TextUtils.equals(id, serviceId)) {
                    if (services.get(i).serviceInfo == null) {
                        continue;
                    }
                    if (serviceName != null) {
                        throw new IllegalArgumentException("Ambiguous session type. Multiple"
                                + " session services define the same id=" + id);
                    }
                    serviceName = services.get(i).serviceInfo.name;
                }
            }
        }
        return serviceName;
    }

    @Override
    public void initialize() {
        synchronized (mLock) {
            setPlayerLocked(mPlayer);
        }
        // Ask server for the sanity check, and starts
        // Sanity check for making session ID unique 'per package' cannot be done in here.
        // Server can only know if the package has another process and has another session with the
        // same id. Note that 'ID is unique per package' is important for controller to distinguish
        // a session in another package.
        //   2. Easier to know the type of session.
        //      Session created here can be the session service token. In order distinguish,
        //      we need to iterate AndroidManifest.xml but it's already done by the server.
        //      Let server to create token with the type.
        MediaSessionManager manager =
                (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
        mSessionToken = manager.createSessionToken(mContext.getPackageName(), mId, mSessionStub);
        if (mSessionToken == null) {
        if (!manager.onSessionCreated(mSessionToken)) {
            throw new IllegalStateException("Session with the same id is already used by"
                    + " another process. Use MediaController2 instead.");
        }

        setPlayerInternal(player);
    }

    // TODO(jaewan): Add explicit release() and do not remove session object with the
@@ -126,28 +180,31 @@ public class MediaSession2Impl implements MediaSession2Provider {
        if (player == null) {
            throw new IllegalArgumentException("player shouldn't be null");
        }
        setPlayerInternal(player);
        if (player == mPlayer) {
            return;
        }

    private void setPlayerInternal(MediaPlayerInterface player) {
        synchronized (mLock) {
            if (mPlayer == player) {
                // Player didn't changed. No-op.
                return;
            setPlayerLocked(player);
        }
    }

    private void setPlayerLocked(MediaPlayerInterface player) {
        if (mPlayer != null && mListener != null) {
            // This might not work for a poorly implemented player.
            mPlayer.removePlaybackListener(mListener);
        }
        mPlayer = player;
        mListener = new MyPlaybackListener(this, player);
        player.addPlaybackListener(mCallbackExecutor, mListener);
            mPlayer = player;
        }
        notifyPlaybackStateChangedNotLocked(player.getPlaybackState());
    }

    @Override
    public void close_impl() {
        // Stop system service from listening this session first.
        MediaSessionManager manager =
                (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
        manager.onSessionDestroyed(mSessionToken);

        if (mSessionStub != null) {
            if (DEBUG) {
                Log.d(TAG, "session is now unavailable, id=" + mId);
@@ -160,7 +217,6 @@ public class MediaSession2Impl implements MediaSession2Provider {
                // close can be called multiple times
                mPlayer.removePlaybackListener(mListener);
                mPlayer = null;
                return;
            }
        }
    }
+0 −6
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.media;

import static com.android.media.MediaController2Impl.CALLBACK_FLAG_PLAYBACK;

import android.content.Context;
import android.media.IMediaSession2;
import android.media.IMediaSession2Callback;
import android.media.MediaLibraryService2.BrowserRoot;
@@ -28,15 +27,10 @@ import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.SessionCallback;
import android.media.PlaybackState2;
import android.media.session.PlaybackState;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.support.annotation.GuardedBy;
import android.util.ArrayMap;
+11 −24
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.media.MediaSession2;
import android.media.MediaSessionService2;
import android.media.MediaSessionService2.MediaNotification;
import android.media.PlaybackState2;
import android.media.SessionToken2;
import android.media.SessionToken2.TokenType;
import android.media.session.PlaybackState;
import android.media.update.MediaSessionService2Provider;
import android.os.IBinder;
@@ -81,39 +83,24 @@ public class MediaSessionService2Impl implements MediaSessionService2Provider {
                NOTIFICATION_SERVICE);
        mStartSelfIntent = new Intent(mInstance, mInstance.getClass());

        Intent serviceIntent = createServiceIntent();
        ResolveInfo resolveInfo = mInstance.getPackageManager()
                .resolveService(serviceIntent, PackageManager.GET_META_DATA);
        String id;
        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
            throw new IllegalArgumentException("service " + mInstance + " doesn't implement"
                    + serviceIntent.getAction());
        } else if (resolveInfo.serviceInfo.metaData == null) {
            if (DEBUG) {
                Log.d(TAG, "Failed to resolve ID for " + mInstance + ". Using empty id");
            }
            id = "";
        } else {
            id = resolveInfo.serviceInfo.metaData.getString(
                    MediaSessionService2.SERVICE_META_DATA, "");
        }
        mSession = mInstance.onCreateSession(id);
        if (mSession == null || !id.equals(mSession.getToken().getId())) {
            throw new RuntimeException("Expected session with id " + id + ", but got " + mSession);
        SessionToken2 token = new SessionToken2(mInstance, getSessionType(),
                mInstance.getPackageName(), mInstance.getClass().getName());
        mSession = mInstance.onCreateSession(token.getId());
        if (mSession == null || !token.getId().equals(mSession.getToken().getId())) {
            throw new RuntimeException("Expected session with id " + token.getId()
                    + ", but got " + mSession);
        }
        // TODO(jaewan): Uncomment here.
        // mSession.addPlaybackListener(mListener, mSession.getExecutor());
    }

    Intent createServiceIntent() {
        Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
        serviceIntent.setAction(MediaSessionService2.SERVICE_INTERFACE);
        return serviceIntent;
    @TokenType int getSessionType() {
        return SessionToken2.TYPE_SESSION_SERVICE;
    }

    public IBinder onBind_impl(Intent intent) {
        if (MediaSessionService2.SERVICE_INTERFACE.equals(intent.getAction())) {
            return mSession.getToken().getSessionBinder().asBinder();
            return SessionToken2Impl.from(mSession.getToken()).getSessionBinder().asBinder();
        }
        return null;
    }
Loading