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

Commit 5aec249a authored by Jaewan Kim's avatar Jaewan Kim
Browse files

MediaSession2: Initial commit of MediaLibraryService2

MediaLibraryService2 is the new name for the MediaBrowserService

Test: Run all MediaComponents tests once
Change-Id: I0a29c4015cd22b5fa4e4e0f55562afd865eea1d6
parent 735f3430
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -49,10 +49,20 @@ public class MediaBrowser2Impl extends MediaController2Impl implements MediaBrow
            try {
                binder.getBrowserRoot(getControllerStub(), rootHints);
            } catch (RemoteException e) {
                // TODO(jaewan): Handle disconnect.
                if (DEBUG) {
                    Log.w(TAG, "Cannot connect to the service or the session is gone", e);
                }
            }
        } else {
            Log.w(TAG, "Session isn't active", new IllegalStateException());
        }
    }

    public void onGetRootResult(
            final Bundle rootHints, final String rootMediaId, final Bundle rootExtra) {
        getCallbackExecutor().execute(() -> {
            mCallback.onGetRootResult(rootHints, rootMediaId, rootExtra);
        });
    }
}
+36 −2
Original line number Diff line number Diff line
@@ -159,6 +159,9 @@ public class MediaController2Impl implements MediaController2Provider {

    @Override
    public void release_impl() {
        if (DEBUG) {
            Log.d(TAG, "release from " + mToken);
        }
        final IMediaSession2 binder;
        synchronized (mLock) {
            if (mIsReleased) {
@@ -196,6 +199,10 @@ public class MediaController2Impl implements MediaController2Provider {
        return mSessionCallbackStub;
    }

    Executor getCallbackExecutor() {
        return mCallbackExecutor;
    }

    @Override
    public SessionToken getSessionToken_impl() {
        return mToken;
@@ -343,7 +350,8 @@ public class MediaController2Impl implements MediaController2Provider {
    private void onConnectionChangedNotLocked(IMediaSession2 sessionBinder,
            CommandGroup commandGroup) {
        if (DEBUG) {
            Log.d(TAG, "onConnectionChangedNotLocked sessionBinder=" + sessionBinder);
            Log.d(TAG, "onConnectionChangedNotLocked sessionBinder=" + sessionBinder
                    + ", commands=" + commandGroup);
        }
        boolean release = false;
        try {
@@ -369,6 +377,9 @@ public class MediaController2Impl implements MediaController2Provider {
                    // so can be used without worrying about deadlock.
                    mSessionBinder.asBinder().linkToDeath(mDeathRecipient, 0);
                } catch (RemoteException e) {
                    if (DEBUG) {
                        Log.d(TAG, "Session died too early.", e);
                    }
                    release = true;
                    return;
                }
@@ -390,7 +401,8 @@ public class MediaController2Impl implements MediaController2Provider {
        }
    }

    // TODO(jaewan): Pull out this from the controller2, and rename it to the MediaBrowserStub
    // TODO(jaewan): Pull out this from the controller2, and rename it to the MediaController2Stub
    //               or MediaBrowser2Stub.
    static class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
        private final WeakReference<MediaController2Impl> mController;

@@ -406,6 +418,15 @@ public class MediaController2Impl implements MediaController2Provider {
            return controller;
        }

        // TODO(jaewan): Refactor code to get rid of these pattern.
        private MediaBrowser2Impl getBrowser() throws IllegalStateException {
            final MediaController2Impl controller = getController();
            if (controller instanceof MediaBrowser2Impl) {
                return (MediaBrowser2Impl) controller;
            }
            return null;
        }

        public void destroy() {
            mController.clear();
        }
@@ -429,6 +450,19 @@ public class MediaController2Impl implements MediaController2Provider {
            controller.onConnectionChangedNotLocked(
                    sessionBinder, CommandGroup.fromBundle(commandGroup));
        }

        @Override
        public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra)
                throws RuntimeException {
            final MediaBrowser2Impl browser;
            try {
                browser = getBrowser();
            } catch (IllegalStateException e) {
                Log.w(TAG, "Don't fail silently here. Highly likely a bug");
                return;
            }
            browser.onGetRootResult(rootHints, rootMediaId, rootExtra);
        }
    }

    // This will be called on the main thread.
+54 −0
Original line number Diff line number Diff line
/*
 * 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.media;

import android.content.Intent;
import android.media.MediaLibraryService2;
import android.media.MediaLibraryService2.MediaLibrarySession;
import android.media.MediaSession2;
import android.media.MediaSessionService2;
import android.media.update.MediaLibraryService2Provider;

public class MediaLibraryService2Impl extends MediaSessionService2Impl implements
        MediaLibraryService2Provider {
    private final MediaSessionService2 mInstance;
    private MediaLibrarySession mLibrarySession;

    public MediaLibraryService2Impl(MediaLibraryService2 instance) {
        super(instance);
        mInstance = instance;
    }

    @Override
    public void onCreate_impl() {
        super.onCreate_impl();

        // Effectively final
        MediaSession2 session = getSession();
        if (!(session instanceof MediaLibrarySession)) {
            throw new RuntimeException("Expected MediaLibrarySession, but returned MediaSession2");
        }
        mLibrarySession = (MediaLibrarySession) getSession();
    }

    @Override
    Intent createServiceIntent() {
        Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
        serviceIntent.setAction(MediaLibraryService2.SERVICE_INTERFACE);
        return serviceIntent;
    }
}
+5 −7
Original line number Diff line number Diff line
@@ -48,7 +48,6 @@ public class MediaSession2Impl implements MediaSession2Provider {
    private final Context mContext;
    private final String mId;
    private final Handler mHandler;
    private final SessionCallback mCallback;
    private final MediaSession2Stub mSessionStub;
    private final SessionToken mSessionToken;

@@ -76,8 +75,7 @@ public class MediaSession2Impl implements MediaSession2Provider {
        mContext = context;
        mId = id;
        mHandler = new Handler(Looper.myLooper());
        mCallback = callback;
        mSessionStub = new MediaSession2Stub(this);
        mSessionStub = new MediaSession2Stub(this, callback);
        // 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
@@ -284,10 +282,6 @@ public class MediaSession2Impl implements MediaSession2Provider {
        return mInstance;
    }

    SessionCallback getCallback() {
        return mCallback;
    }

    MediaPlayerBase getPlayer() {
        return mPlayer;
    }
@@ -420,5 +414,9 @@ public class MediaSession2Impl implements MediaSession2Provider {
        public void removeFlag(int flag) {
            mFlag &= ~flag;
        }

        public static ControllerInfoImpl from(ControllerInfo controller) {
            return (ControllerInfoImpl) controller.getProvider();
        }
    }
}
+60 −17
Original line number Diff line number Diff line
@@ -21,10 +21,12 @@ 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.MediaLibrarySessionCallback;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.SessionCallback;
import android.media.session.PlaybackState;
import android.os.Binder;
import android.os.Bundle;
@@ -33,6 +35,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.service.media.MediaBrowserService.BrowserRoot;
import android.support.annotation.GuardedBy;
import android.util.ArrayMap;
import android.util.Log;
@@ -41,9 +44,6 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

// TODO(jaewan): Add a hook for media apps to log which app requested specific command.
// TODO(jaewan): Add a way to block specific command from a specific app. Also add supported
// command per apps.
public class MediaSession2Stub extends IMediaSession2.Stub {
    private static final String TAG = "MediaSession2Stub";
    private static final boolean DEBUG = true; // TODO(jaewan): Rename.
@@ -52,14 +52,20 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
    private final CommandHandler mCommandHandler;
    private final WeakReference<MediaSession2Impl> mSession;
    private final Context mContext;
    private final SessionCallback mSessionCallback;
    private final MediaLibrarySessionCallback mLibraryCallback;

    @GuardedBy("mLock")
    private final ArrayMap<IBinder, ControllerInfo> mControllers = new ArrayMap<>();

    public MediaSession2Stub(MediaSession2Impl session) {
    public MediaSession2Stub(MediaSession2Impl session, SessionCallback callback) {
        mSession = new WeakReference<>(session);
        mContext = session.getContext();
        // TODO(jaewan): Should be executor from the session builder
        mCommandHandler = new CommandHandler(session.getHandler().getLooper());
        mSessionCallback = callback;
        mLibraryCallback = (callback instanceof MediaLibrarySessionCallback)
                ? (MediaLibrarySessionCallback) callback : null;
    }

    public void destroyNotLocked() {
@@ -127,7 +133,20 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
    @Override
    public void getBrowserRoot(IMediaSession2Callback caller, Bundle rootHints)
            throws RuntimeException {
        // TODO(jaewan): Implement this.
        if (mLibraryCallback == null) {
            if (DEBUG) {
                Log.d(TAG, "Session cannot hand getBrowserRoot()");
            }
            return;
        }
        final ControllerInfo controller = getController(caller);
        if (controller == null) {
            if (DEBUG) {
                Log.d(TAG, "getBrowerRoot from a controller that hasn't connected. Ignore");
            }
            return;
        }
        mCommandHandler.postOnGetRoot(controller, rootHints);
    }

    @Deprecated
@@ -148,7 +167,7 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
            if (controllerInfo == null) {
                return;
            }
            ((ControllerInfoImpl) controllerInfo.getProvider()).addFlag(callbackFlag);
            ControllerInfoImpl.from(controllerInfo).addFlag(callbackFlag);
        }
    }

@@ -162,9 +181,7 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
            if (controllerInfo == null) {
                return;
            }
            ControllerInfoImpl impl =
                    ((ControllerInfoImpl) controllerInfo.getProvider());
            impl.removeFlag(callbackFlag);
            ControllerInfoImpl.from(controllerInfo).removeFlag(callbackFlag);
        }
    }

@@ -189,7 +206,7 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
        synchronized (mLock) {
            for (int i = 0; i < mControllers.size(); i++) {
                ControllerInfo controllerInfo = mControllers.valueAt(i);
                if (((ControllerInfoImpl) controllerInfo.getProvider()).containsFlag(flag)) {
                if (ControllerInfoImpl.from(controllerInfo).containsFlag(flag)) {
                    controllers.add(controllerInfo);
                }
            }
@@ -202,8 +219,7 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
        final List<ControllerInfo> list = getControllersWithFlag(CALLBACK_FLAG_PLAYBACK);
        for (int i = 0; i < list.size(); i++) {
            IMediaSession2Callback callbackBinder =
                    ((ControllerInfoImpl) list.get(i).getProvider())
                            .getControllerBinder();
                    ControllerInfoImpl.from(list.get(i)).getControllerBinder();
            try {
                callbackBinder.onPlaybackStateChanged(state);
            } catch (RemoteException e) {
@@ -213,9 +229,11 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
        }
    }

    // TODO(jaewan): Remove this. We should use Executor given by the session builder.
    private class CommandHandler extends Handler {
        public static final int MSG_CONNECT = 1000;
        public static final int MSG_COMMAND = 1001;
        public static final int MSG_ON_GET_ROOT = 2000;

        public CommandHandler(Looper looper) {
            super(looper);
@@ -229,18 +247,22 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
            }

            switch (msg.what) {
                case MSG_CONNECT:
                case MSG_CONNECT: {
                    ControllerInfo request = (ControllerInfo) msg.obj;
                    CommandGroup allowedCommands = session.getCallback().onConnect(request);
                    CommandGroup allowedCommands = mSessionCallback.onConnect(request);
                    // Don't reject connection for the request from trusted app.
                    // Otherwise server will fail to retrieve session's information to dispatch
                    // media keys to.
                    boolean accept = allowedCommands != null || request.isTrusted();
                    ControllerInfoImpl impl = (ControllerInfoImpl) request.getProvider();
                    ControllerInfoImpl impl = ControllerInfoImpl.from(request);
                    if (accept) {
                        synchronized (mLock) {
                            mControllers.put(impl.getId(), request);
                        }
                        if (allowedCommands == null) {
                            // For trusted apps, send non-null allowed commands to keep connection.
                            allowedCommands = new CommandGroup();
                        }
                    }
                    if (DEBUG) {
                        Log.d(TAG, "onConnectResult, request=" + request
@@ -254,10 +276,11 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
                        // Controller may be died prematurely.
                    }
                    break;
                case MSG_COMMAND:
                }
                case MSG_COMMAND: {
                    CommandParam param = (CommandParam) msg.obj;
                    Command command = param.command;
                    boolean accepted = session.getCallback().onCommandRequest(
                    boolean accepted = mSessionCallback.onCommandRequest(
                            param.controller, command);
                    if (!accepted) {
                        // Don't run rejected command.
@@ -289,6 +312,21 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
                    }
                    break;
                }
                case MSG_ON_GET_ROOT: {
                    final CommandParam param = (CommandParam) msg.obj;
                    final ControllerInfoImpl controller = ControllerInfoImpl.from(param.controller);
                    BrowserRoot root = mLibraryCallback.onGetRoot(param.controller, param.args);
                    try {
                        controller.getControllerBinder().onGetRootResult(param.args,
                                root == null ? null : root.getRootId(),
                                root == null ? null : root.getExtras());
                    } catch (RemoteException e) {
                        // Controller may be died prematurely.
                        // TODO(jaewan): Handle this.
                    }
                    break;
                }
            }
        }

        public void postConnect(ControllerInfo request) {
@@ -299,6 +337,11 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
            CommandParam param = new CommandParam(controller, command, args);
            obtainMessage(MSG_COMMAND, param).sendToTarget();
        }

        public void postOnGetRoot(ControllerInfo controller, Bundle rootHints) {
            CommandParam param = new CommandParam(controller, null, rootHints);
            obtainMessage(MSG_ON_GET_ROOT, param).sendToTarget();
        }
    }

    private static class CommandParam {
Loading