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

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

MediaSession2: Move MediaSession2/MediaController2 from experimental

APIs will be unhidden later

Test: Run MediaComponentsTest
Change-Id: I2d9fcd98232016281fad128e9e674885b41e20d9
parent e65bd19f
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -416,6 +416,8 @@ java_library {
        "media/java/android/media/IMediaRouterService.aidl",
        "media/java/android/media/IMediaScannerListener.aidl",
        "media/java/android/media/IMediaScannerService.aidl",
        "media/java/android/media/IMediaSession2.aidl",
        "media/java/android/media/IMediaSession2Callback.aidl",
        "media/java/android/media/IPlaybackConfigDispatcher.aidl",
        ":libaudioclient_aidl",
        "media/java/android/media/IRecordingConfigDispatcher.aidl",
+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 android.media;

import android.media.session.PlaybackState;
import android.media.IMediaSession2Callback;

/**
 * Interface to MediaSession2. Framework MUST only call oneway APIs.
 *
 * @hide
 */
// TODO(jaewan): Make this oneway interface.
//               Malicious app can fake session binder and holds commands from controller.
interface IMediaSession2 {
    // TODO(jaewan): add onCommand() to send private command
    // TODO(jaewan): Due to the nature of oneway calls, APIs can be called in out of order
    //               Add id for individual calls to address this.

    // TODO(jaewan): We may consider to add another binder just for the connection
    //               not to expose other methods to the controller whose connection wasn't accepted.
    //               But this would be enough for now because it's the same as existing
    //               MediaBrowser and MediaBrowserService.
    oneway void connect(String callingPackage, IMediaSession2Callback callback);
    oneway void release(IMediaSession2Callback caller);

    //////////////////////////////////////////////////////////////////////////////////////////////
    // Playback controls.
    //////////////////////////////////////////////////////////////////////////////////////////////
    oneway void play(IMediaSession2Callback caller);
    oneway void pause(IMediaSession2Callback caller);
    oneway void stop(IMediaSession2Callback caller);
    oneway void skipToPrevious(IMediaSession2Callback caller);
    oneway void skipToNext(IMediaSession2Callback caller);

    PlaybackState getPlaybackState();

    //////////////////////////////////////////////////////////////////////////////////////////////
    // Callbacks -- remove them
    //////////////////////////////////////////////////////////////////////////////////////////////
    /**
     * @param callbackBinder binder to be used to notify changes.
     * @param callbackFlag one of {@link MediaController2#FLAG_CALLBACK_PLAYBACK} or
     *     {@link MediaController2#FLAG_CALLBACK_SESSION_ACTIVENESS}
     * @param requestCode If >= 0, this code will be called back by the callback after the callback
     *     is registered.
     */
    // TODO(jaewan): Due to the nature of the binder, calls can be called out of order.
    //               Need a way to ensure calling of unregisterCallback unregisters later
    //               registerCallback.
    oneway void registerCallback(IMediaSession2Callback callbackBinder,
            int callbackFlag, int requestCode);
    oneway void unregisterCallback(IMediaSession2Callback callbackBinder, int callbackFlag);
}
+46 −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 android.media;

import android.media.session.PlaybackState;
import android.media.IMediaSession2;

/**
 * Interface from MediaSession2 to MediaSession2Record.
 * <p>
 * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
 * and holds calls from session to make session owner(s) frozen.
 *
 * @hide
 */
oneway interface IMediaSession2Callback {
    void onPlaybackStateChanged(in PlaybackState state);

    /**
     * Called only when the controller is created with service's token.
     *
     * @param sessionBinder {@code null} if the connect is rejected or is disconnected. a session
     *     binder if the connect is accepted.
     * @param commands initially allowed commands.
     */
    // TODO(jaewan): Also need to pass flags for allowed actions for permission check.
    //               For example, a media can allow setRating only for whitelisted apps
    //               it's better for controller to know such information in advance.
    //               Follow-up TODO: Add similar functions to the session.
    // TODO(jaewan): Is term 'accepted/rejected' correct? For permission, 'grant' is used.
    void onConnectionChanged(IMediaSession2 sessionBinder, long commands);
}
+196 −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 android.media;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.media.MediaSession2.CommandFlags;
import android.media.MediaSession2.ControllerInfo;
import android.media.session.PlaybackState;
import android.media.update.ApiLoader;
import android.media.update.MediaController2Provider;
import android.os.Handler;
import java.util.concurrent.Executor;

/**
 * Allows an app to interact with an active {@link MediaSession2} or a
 * {@link MediaSessionService2} in any status. Media buttons and other commands can be sent to
 * the session.
 * <p>
 * When you're done, use {@link #release()} to clean up resources. This also helps session service
 * to be destroyed when there's no controller associated with it.
 * <p>
 * When controlling {@link MediaSession2}, the controller will be available immediately after
 * the creation.
 * <p>
 * When controlling {@link MediaSessionService2}, the {@link MediaController2} would be
 * available only if the session service allows this controller by
 * {@link MediaSession2.SessionCallback#onConnect(ControllerInfo)} for the service. Wait
 * {@link ControllerCallback#onConnected(long)} or {@link ControllerCallback#onDisconnected()} for
 * the result.
 * <p>
 * A controller can be created through {@link MediaPlayerSessionManager} if you hold the
 * signature|privileged permission "android.permission.MEDIA_CONTENT_CONTROL" permission or are
 * an enabled notification listener or by getting a {@link SessionToken} directly the
 * the session owner.
 * <p>
 * MediaController2 objects are thread-safe.
 * <p>
 * @see MediaSession2
 * @see MediaSessionService2
 * @hide
 */
// TODO(jaewan): Unhide
// TODO(jaewan): Revisit comments. Currently MediaBrowser case is missing.
public class MediaController2 extends MediaPlayerBase {
    /**
     * Interface for listening to change in activeness of the {@link MediaSession2}.  It's
     * active if and only if it has set a player.
     */
    public abstract static class ControllerCallback {
        /**
         * Called when the controller is successfully connected to the session. The controller
         * becomes available afterwards.
         *
         * @param commands commands that's allowed by the session.
         */
        public void onConnected(@CommandFlags long commands) { }

        /**
         * Called when the session refuses the controller or the controller is disconnected from
         * the session. The controller becomes unavailable afterwards and the callback wouldn't
         * be called.
         * <p>
         * It will be also called after the {@link #release()}, so you can put clean up code here.
         * You don't need to call {@link #release()} after this.
         */
        public void onDisconnected() { }
    }

    private final MediaController2Provider mProvider;

    /**
     * Create a {@link MediaController2} from the {@link SessionToken}. This connects to the session
     * and may wake up the service if it's not available.
     *
     * @param context Context
     * @param token token to connect to
     * @param callback controller callback to receive changes in
     * @param executor executor to run callbacks on.
     */
    // TODO(jaewan): Put @CallbackExecutor to the constructor.
    public MediaController2(@NonNull Context context, @NonNull SessionToken token,
            @NonNull ControllerCallback callback, @NonNull Executor executor) {
        super();

        // This also connects to the token.
        // Explicit connect() isn't added on purpose because retrying connect() is impossible with
        // session whose session binder is only valid while it's active.
        // prevent a controller from reusable after the
        // session is released and recreated.
        mProvider = ApiLoader.getProvider(context)
                .createMediaController2(this, context, token, callback, executor);
    }

    /**
     * Release this object, and disconnect from the session. After this, callbacks wouldn't be
     * received.
     */
    public void release() {
        mProvider.release_impl();
    }

    /**
     * @hide
     */
    public MediaController2Provider getProvider() {
        return mProvider;
    }

    /**
     * @return token
     */
    public @NonNull
    SessionToken getSessionToken() {
        return mProvider.getSessionToken_impl();
    }

    /**
     * Returns whether this class is connected to active {@link MediaSession2} or not.
     */
    public boolean isConnected() {
        return mProvider.isConnected_impl();
    }

    @Override
    public void play() {
        mProvider.play_impl();
    }

    @Override
    public void pause() {
        mProvider.pause_impl();
    }

    @Override
    public void stop() {
        mProvider.stop_impl();
    }

    @Override
    public void skipToPrevious() {
        mProvider.skipToPrevious_impl();
    }

    @Override
    public void skipToNext() {
        mProvider.skipToNext_impl();
    }

    @Override
    public @Nullable PlaybackState getPlaybackState() {
        return mProvider.getPlaybackState_impl();
    }

    /**
     * Add a {@link PlaybackListener} to listen changes in the
     * {@link MediaSession2}.
     *
     * @param listener the listener that will be run
     * @param handler the Handler that will receive the listener
     * @throws IllegalArgumentException Called when either the listener or handler is {@code null}.
     */
    // TODO(jaewan): Match with the addSessionAvailabilityListener() that tells the current state
    //               through the listener.
    // TODO(jaewan): Can handler be null? Follow the API guideline after it's finalized.
    @Override
    public void addPlaybackListener(@NonNull PlaybackListener listener, @NonNull Handler handler) {
        mProvider.addPlaybackListener_impl(listener, handler);
    }

    /**
     * Remove previously added {@link PlaybackListener}.
     *
     * @param listener the listener to be removed
     * @throws IllegalArgumentException if the listener is {@code null}.
     */
    @Override
    public void removePlaybackListener(@NonNull PlaybackListener listener) {
        mProvider.removePlaybackListener_impl(listener);
    }
}
+69 −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 android.media;

import android.media.session.PlaybackState;
import android.os.Handler;

/**
 * Tentative interface for all media players that want media session.
 * APIs are named to avoid conflicts with MediaPlayer APIs.
 * All calls should be asynchrounous.
 *
 * @hide
 */
// TODO(wjia) Finalize the list of MediaPlayer2, which MediaPlayerBase's APIs will be come from.
public abstract class MediaPlayerBase {
    /**
     * Listens change in {@link PlaybackState}.
     */
    public interface PlaybackListener {
        /**
         * Called when {@link PlaybackState} for this player is changed.
         */
        void onPlaybackChanged(PlaybackState state);
    }

    // TODO(jaewan): setDataSources()?
    // TODO(jaewan): Add release() or do that in stop()?

    // TODO(jaewan): Add set/getSupportedActions().
    public abstract void play();
    public abstract void pause();
    public abstract void stop();
    public abstract void skipToPrevious();
    public abstract void skipToNext();

    // Currently PlaybackState's error message is the content title (for testing only)
    // TODO(jaewan): Add metadata support
    public abstract PlaybackState getPlaybackState();

    /**
     * Add a {@link PlaybackListener} to be invoked when the playback state is changed.
     *
     * @param listener the listener that will be run
     * @param handler the Handler that will receive the listener
     */
    public abstract void addPlaybackListener(PlaybackListener listener, Handler handler);

    /**
     * Remove previously added {@link PlaybackListener}.
     *
     * @param listener the listener to be removed
     */
    public abstract void removePlaybackListener(PlaybackListener listener);
}
Loading