blob: bfd1fd493b2c4721d9d3f2375da9d71aac1af618 [file] [log] [blame]
/*
* 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.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.content.Context;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.PlaylistParams;
import android.media.session.MediaSessionManager;
import android.media.update.ApiLoader;
import android.media.update.MediaController2Provider;
import android.media.update.MediaController2Provider.PlaybackInfoProvider;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
import java.util.List;
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 #close()} 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(CommandGroup)} or
* {@link ControllerCallback#onDisconnected()} for the result.
* <p>
* A controller can be created through token from {@link MediaSessionManager} if you hold the
* signature|privileged permission "android.permission.MEDIA_CONTENT_CONTROL" permission or are
* an enabled notification listener or by getting a {@link SessionToken2} directly the
* the session owner.
* <p>
* MediaController2 objects are thread-safe.
* <p>
* @see MediaSession2
* @see MediaSessionService2
* @hide
*/
public class MediaController2 implements AutoCloseable {
/**
* 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 allowedCommands commands that's allowed by the session.
*/
public void onConnected(CommandGroup allowedCommands) { }
/**
* 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 #close()}, so you can put clean up code here.
* You don't need to call {@link #close()} after this.
*/
public void onDisconnected() { }
/**
* Called when the session set the custom layout through the
* {@link MediaSession2#setCustomLayout(ControllerInfo, List)}.
* <p>
* Can be called before {@link #onConnected(CommandGroup)} is called.
*
* @param layout
*/
public void onCustomLayoutChanged(List<CommandButton> layout) { }
/**
* Called when the session has changed anything related with the {@link PlaybackInfo}.
*
* @param info new playback info
*/
public void onPlaybackInfoChanged(PlaybackInfo info) { }
/**
* Called when the allowed commands are changed by session.
*
* @param commands newly allowed commands
*/
public void onAllowedCommandsChanged(CommandGroup commands) { }
/**
* Called when the session sent a custom command.
*
* @param command
* @param args
* @param receiver
*/
public void onCustomCommand(Command command, @Nullable Bundle args,
@Nullable ResultReceiver receiver) { }
/**
* Called when the playlist is changed.
*
* @param playlist A new playlist set by the session.
*/
public void onPlaylistChanged(@NonNull List<MediaItem2> playlist) { }
/**
* Called when the playback state is changed.
*
* @param state latest playback state
*/
public void onPlaybackStateChanged(@NonNull PlaybackState2 state) { }
/**
* Called when the playlist parameters are changed.
*
* @param params The new play list parameters.
*/
public void onPlaylistParamsChanged(@NonNull PlaylistParams params) { }
}
/**
* Holds information about the current playback and how audio is handled for
* this session.
*/
// The same as MediaController.PlaybackInfo
public static final class PlaybackInfo {
/**
* The session uses remote playback.
*/
public static final int PLAYBACK_TYPE_REMOTE = 2;
/**
* The session uses local playback.
*/
public static final int PLAYBACK_TYPE_LOCAL = 1;
private final PlaybackInfoProvider mProvider;
/**
* @hide
*/
@SystemApi
public PlaybackInfo(PlaybackInfoProvider provider) {
mProvider = provider;
}
/**
* @hide
*/
@SystemApi
public PlaybackInfoProvider getProvider() {
return mProvider;
}
/**
* Get the type of playback which affects volume handling. One of:
* <ul>
* <li>{@link #PLAYBACK_TYPE_LOCAL}</li>
* <li>{@link #PLAYBACK_TYPE_REMOTE}</li>
* </ul>
*
* @return The type of playback this session is using.
*/
public int getPlaybackType() {
return mProvider.getPlaybackType_impl();
}
/**
* Get the audio attributes for this session. The attributes will affect
* volume handling for the session. When the volume type is
* {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} these may be ignored by the
* remote volume handler.
*
* @return The attributes for this session.
*/
public AudioAttributes getAudioAttributes() {
return mProvider.getAudioAttributes_impl();
}
/**
* Get the type of volume control that can be used. One of:
* <ul>
* <li>{@link VolumeProvider2#VOLUME_CONTROL_ABSOLUTE}</li>
* <li>{@link VolumeProvider2#VOLUME_CONTROL_RELATIVE}</li>
* <li>{@link VolumeProvider2#VOLUME_CONTROL_FIXED}</li>
* </ul>
*
* @return The type of volume control that may be used with this session.
*/
public int getControlType() {
return mProvider.getControlType_impl();
}
/**
* Get the maximum volume that may be set for this session.
*
* @return The maximum allowed volume where this session is playing.
*/
public int getMaxVolume() {
return mProvider.getMaxVolume_impl();
}
/**
* Get the current volume for this session.
*
* @return The current volume where this session is playing.
*/
public int getCurrentVolume() {
return mProvider.getCurrentVolume_impl();
}
}
private final MediaController2Provider mProvider;
/**
* Create a {@link MediaController2} from the {@link SessionToken2}. 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 executor executor to run callbacks on.
* @param callback controller callback to receive changes in
*/
// TODO(jaewan): Put @CallbackExecutor to the constructor.
public MediaController2(@NonNull Context context, @NonNull SessionToken2 token,
@NonNull @CallbackExecutor Executor executor, @NonNull ControllerCallback callback) {
super();
mProvider = createProvider(context, token, executor, callback);
// 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.initialize();
}
MediaController2Provider createProvider(@NonNull Context context,
@NonNull SessionToken2 token, @NonNull Executor executor,
@NonNull ControllerCallback callback) {
return ApiLoader.getProvider(context)
.createMediaController2(context, this, token, executor, callback);
}
/**
* Release this object, and disconnect from the session. After this, callbacks wouldn't be
* received.
*/
@Override
public void close() {
mProvider.close_impl();
}
/**
* @hide
*/
@SystemApi
public MediaController2Provider getProvider() {
return mProvider;
}
/**
* @return token
*/
public @NonNull
SessionToken2 getSessionToken() {
return mProvider.getSessionToken_impl();
}
/**
* Returns whether this class is connected to active {@link MediaSession2} or not.
*/
public boolean isConnected() {
return mProvider.isConnected_impl();
}
public void play() {
mProvider.play_impl();
}
public void pause() {
mProvider.pause_impl();
}
public void stop() {
mProvider.stop_impl();
}
public void skipToPrevious() {
mProvider.skipToPrevious_impl();
}
public void skipToNext() {
mProvider.skipToNext_impl();
}
/**
* Request that the player prepare its playback. In other words, other sessions can continue
* to play during the preparation of this session. This method can be used to speed up the
* start of the playback. Once the preparation is done, the session will change its playback
* state to {@link PlaybackState2#STATE_PAUSED}. Afterwards, {@link #play} can be called to
* start playback.
*/
public void prepare() {
mProvider.prepare_impl();
}
/**
* Start fast forwarding. If playback is already fast forwarding this
* may increase the rate.
*/
public void fastForward() {
mProvider.fastForward_impl();
}
/**
* Start rewinding. If playback is already rewinding this may increase
* the rate.
*/
public void rewind() {
mProvider.rewind_impl();
}
/**
* Move to a new location in the media stream.
*
* @param pos Position to move to, in milliseconds.
*/
public void seekTo(long pos) {
mProvider.seekTo_impl(pos);
}
/**
* Sets the index of current DataSourceDesc in the play list to be played.
*
* @param index the index of DataSourceDesc in the play list you want to play
* @throws IllegalArgumentException if the play list is null
* @throws NullPointerException if index is outside play list range
*/
public void setCurrentPlaylistItem(int index) {
mProvider.setCurrentPlaylistItem_impl(index);
}
/**
* Sets the {@link PlaylistParams} for the current play list. Repeat/shuffle mode and metadata
* for the list can be set by calling this method.
*
* @param params A {@link PlaylistParams} object to set.
* @throws IllegalArgumentException if given {@param param} is null.
*/
public void setPlaylistParams(PlaylistParams params) {
mProvider.setPlaylistParams_impl(params);
}
/**
* @hide
*/
public void skipForward() {
// To match with KEYCODE_MEDIA_SKIP_FORWARD
}
/**
* @hide
*/
public void skipBackward() {
// To match with KEYCODE_MEDIA_SKIP_BACKWARD
}
/**
* Request that the player start playback for a specific media id.
*
* @param mediaId The id of the requested media.
* @param extras Optional extras that can include extra information about the media item
* to be played.
*/
public void playFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
mProvider.playFromMediaId_impl(mediaId, extras);
}
/**
* Request that the player start playback for a specific search query.
* An empty or null query should be treated as a request to play any
* music.
*
* @param query The search query.
* @param extras Optional extras that can include extra information
* about the query.
*/
public void playFromSearch(@NonNull String query, @Nullable Bundle extras) {
mProvider.playFromSearch_impl(query, extras);
}
/**
* Request that the player start playback for a specific {@link Uri}.
*
* @param uri The URI of the requested media.
* @param extras Optional extras that can include extra information about the media item
* to be played.
*/
public void playFromUri(@NonNull Uri uri, @Nullable Bundle extras) {
mProvider.playFromUri_impl(uri, extras);
}
/**
* Request that the player prepare playback for a specific media id. In other words, other
* sessions can continue to play during the preparation of this session. This method can be
* used to speed up the start of the playback. Once the preparation is done, the session
* will change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
* {@link #play} can be called to start playback. If the preparation is not needed,
* {@link #playFromMediaId} can be directly called without this method.
*
* @param mediaId The id of the requested media.
* @param extras Optional extras that can include extra information about the media item
* to be prepared.
*/
public void prepareFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
mProvider.prepareMediaId_impl(mediaId, extras);
}
/**
* Request that the player prepare playback for a specific search query. An empty or null
* query should be treated as a request to prepare any music. In other words, other sessions
* can continue to play during the preparation of this session. This method can be used to
* speed up the start of the playback. Once the preparation is done, the session will
* change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
* {@link #play} can be called to start playback. If the preparation is not needed,
* {@link #playFromSearch} can be directly called without this method.
*
* @param query The search query.
* @param extras Optional extras that can include extra information
* about the query.
*/
public void prepareFromSearch(@NonNull String query, @Nullable Bundle extras) {
mProvider.prepareFromSearch_impl(query, extras);
}
/**
* Request that the player prepare playback for a specific {@link Uri}. In other words,
* other sessions can continue to play during the preparation of this session. This method
* can be used to speed up the start of the playback. Once the preparation is done, the
* session will change its playback state to {@link PlaybackState2#STATE_PAUSED}. Afterwards,
* {@link #play} can be called to start playback. If the preparation is not needed,
* {@link #playFromUri} can be directly called without this method.
*
* @param uri The URI of the requested media.
* @param extras Optional extras that can include extra information about the media item
* to be prepared.
*/
public void prepareFromUri(@NonNull Uri uri, @Nullable Bundle extras) {
mProvider.prepareFromUri_impl(uri, extras);
}
/**
* Set the volume of the output this session is playing on. The command will be ignored if it
* does not support {@link VolumeProvider2#VOLUME_CONTROL_ABSOLUTE}.
* <p>
* If the session is local playback, this changes the device's volume with the stream that
* session's player is using. Flags will be specified for the {@link AudioManager}.
* <p>
* If the session is remote player (i.e. session has set volume provider), its volume provider
* will receive this request instead.
*
* @see #getPlaybackInfo()
* @param value The value to set it to, between 0 and the reported max.
* @param flags flags from {@link AudioManager} to include with the volume request for local
* playback
*/
public void setVolumeTo(int value, int flags) {
mProvider.setVolumeTo_impl(value, flags);
}
/**
* Adjust the volume of the output this session is playing on. The direction
* must be one of {@link AudioManager#ADJUST_LOWER},
* {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
* The command will be ignored if the session does not support
* {@link VolumeProvider2#VOLUME_CONTROL_RELATIVE} or
* {@link VolumeProvider2#VOLUME_CONTROL_ABSOLUTE}.
* <p>
* If the session is local playback, this changes the device's volume with the stream that
* session's player is using. Flags will be specified for the {@link AudioManager}.
* <p>
* If the session is remote player (i.e. session has set volume provider), its volume provider
* will receive this request instead.
*
* @see #getPlaybackInfo()
* @param direction The direction to adjust the volume in.
* @param flags flags from {@link AudioManager} to include with the volume request for local
* playback
*/
public void adjustVolume(int direction, int flags) {
mProvider.adjustVolume_impl(direction, flags);
}
/**
* Get the rating type supported by the session. One of:
* <ul>
* <li>{@link Rating2#RATING_NONE}</li>
* <li>{@link Rating2#RATING_HEART}</li>
* <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li>
* <li>{@link Rating2#RATING_3_STARS}</li>
* <li>{@link Rating2#RATING_4_STARS}</li>
* <li>{@link Rating2#RATING_5_STARS}</li>
* <li>{@link Rating2#RATING_PERCENTAGE}</li>
* </ul>
*
* @return The supported rating type
*/
public int getRatingType() {
return mProvider.getRatingType_impl();
}
/**
* Get an intent for launching UI associated with this session if one exists.
*
* @return A {@link PendingIntent} to launch UI or null.
*/
public @Nullable PendingIntent getSessionActivity() {
return mProvider.getSessionActivity_impl();
}
/**
* Get the lastly cached {@link PlaybackState2} from
* {@link ControllerCallback#onPlaybackStateChanged(PlaybackState2)}.
* <p>
* It may return {@code null} before the first callback or session has sent {@code null}
* playback state.
*
* @return a playback state. Can be {@code null}
*/
public @Nullable PlaybackState2 getPlaybackState() {
return mProvider.getPlaybackState_impl();
}
/**
* Get the current playback info for this session.
*
* @return The current playback info or null.
*/
public @Nullable PlaybackInfo getPlaybackInfo() {
return mProvider.getPlaybackInfo_impl();
}
/**
* Rate the media. This will cause the rating to be set for the current user.
* The Rating type must match the type returned by {@link #getRatingType()}.
*
* @param mediaId The id of the media
* @param rating The rating to set
*/
public void setRating(String mediaId, Rating2 rating) {
mProvider.setRating_impl(mediaId, rating);
}
/**
* Send custom command to the session
*
* @param command custom command
* @param args optional argument
* @param cb optional result receiver
*/
public void sendCustomCommand(@NonNull Command command, @Nullable Bundle args,
@Nullable ResultReceiver cb) {
mProvider.sendCustomCommand_impl(command, args, cb);
}
/**
* Return playlist from the session.
*
* @return playlist. Can be {@code null} if the controller doesn't have enough permission.
*/
public @Nullable List<MediaItem2> getPlaylist() {
return mProvider.getPlaylist_impl();
}
/**
* Returns the {@link PlaylistParams} for the current play list.
* Can return {@code null} if the controller doesn't have enough permission, or if the session
* has not set the parameters.
*/
public @Nullable PlaylistParams getPlaylistParams() {
return mProvider.getPlaylistParams_impl();
}
/**
* Removes the media item at index in the play list.
*<p>
* If index is same as the current index of the playlist, current playback
* will be stopped and playback moves to next source in the list.
*
* @return the removed DataSourceDesc at index in the play list
* @throws IllegalArgumentException if the play list is null
* @throws IndexOutOfBoundsException if index is outside play list range
*/
// TODO(jaewan): Remove with index was previously rejected by council (b/36524925)
// TODO(jaewan): Should we also add movePlaylistItem from index to index?
public void removePlaylistItem(MediaItem2 item) {
mProvider.removePlaylistItem_impl(item);
}
/**
* Inserts the media item to the play list at position index.
* <p>
* This will not change the currently playing media item.
* If index is less than or equal to the current index of the play list,
* the current index of the play list will be incremented correspondingly.
*
* @param index the index you want to add dsd to the play list
* @param item the media item you want to add to the play list
* @throws IndexOutOfBoundsException if index is outside play list range
* @throws NullPointerException if dsd is null
*/
public void addPlaylistItem(int index, MediaItem2 item) {
mProvider.addPlaylistItem_impl(index, item);
}
}