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

Commit fe4e4db4 authored by Jaewan Kim's avatar Jaewan Kim Committed by Android (Google) Code Review
Browse files

Merge changes from topics "medialibraryservice2", "mediabrowser2"

* changes:
  MediaSession2: Initial commit of MediaLibraryService2
  MediaSession2: Initial commit of MediaBrowser2
parents 8af6933e 5aec249a
Loading
Loading
Loading
Loading
+68 −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.Context;
import android.media.IMediaSession2;
import android.media.MediaBrowser2;
import android.media.MediaBrowser2.BrowserCallback;
import android.media.SessionToken;
import android.media.update.MediaBrowser2Provider;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;

import java.util.concurrent.Executor;

public class MediaBrowser2Impl extends MediaController2Impl implements MediaBrowser2Provider {
    private final String TAG = "MediaBrowser2";
    private final boolean DEBUG = true; // TODO(jaewan): change.

    private final MediaBrowser2 mInstance;
    private final MediaBrowser2.BrowserCallback mCallback;

    public MediaBrowser2Impl(MediaBrowser2 instance, Context context, SessionToken token,
            BrowserCallback callback, Executor executor) {
        super(instance, context, token, callback, executor);
        mInstance = instance;
        mCallback = callback;
    }

    @Override
    public void getBrowserRoot_impl(Bundle rootHints) {
        final IMediaSession2 binder = getSessionBinder();
        if (binder != null) {
            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);
        });
    }
}
+45 −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) {
@@ -188,6 +191,18 @@ public class MediaController2Impl implements MediaController2Provider {
        });
    }

    IMediaSession2 getSessionBinder() {
        return mSessionBinder;
    }

    MediaSession2CallbackStub getControllerStub() {
        return mSessionCallbackStub;
    }

    Executor getCallbackExecutor() {
        return mCallbackExecutor;
    }

    @Override
    public SessionToken getSessionToken_impl() {
        return mToken;
@@ -335,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 {
@@ -361,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;
                }
@@ -382,7 +401,9 @@ public class MediaController2Impl implements MediaController2Provider {
        }
    }

    private static class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
    // 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;

        private MediaSession2CallbackStub(MediaController2Impl controller) {
@@ -397,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();
        }
@@ -420,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();
        }
    }
}
+65 −16
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() {
@@ -124,6 +130,25 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
        mCommandHandler.postCommand(controller, Command.fromBundle(command), args);
    }

    @Override
    public void getBrowserRoot(IMediaSession2Callback caller, Bundle rootHints)
            throws RuntimeException {
        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
    @Override
    public PlaybackState getPlaybackState() throws RemoteException {
@@ -142,7 +167,7 @@ public class MediaSession2Stub extends IMediaSession2.Stub {
            if (controllerInfo == null) {
                return;
            }
            ((ControllerInfoImpl) controllerInfo.getProvider()).addFlag(callbackFlag);
            ControllerInfoImpl.from(controllerInfo).addFlag(callbackFlag);
        }
    }

@@ -156,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);
        }
    }

@@ -183,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);
                }
            }
@@ -196,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) {
@@ -207,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);
@@ -223,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
@@ -248,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.
@@ -283,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) {
@@ -293,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