blob: e92b09a06cc5771a407e163541e079806d956266 [file] [log] [blame]
/*
* Copyright (C) 2014 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.support.v4.media.session;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ResultReceiver;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.VolumeProviderCompat;
import android.text.TextUtils;
/**
* Allows interaction with media controllers, volume keys, media buttons, and
* transport controls.
* <p>
* A MediaSession should be created when an app wants to publish media playback
* information or handle media keys. In general an app only needs one session
* for all playback, though multiple sessions can be created to provide finer
* grain controls of media.
* <p>
* Once a session is created the owner of the session may pass its
* {@link #getSessionToken() session token} to other processes to allow them to
* create a {@link MediaControllerCompat} to interact with the session.
* <p>
* To receive commands, media keys, and other events a {@link Callback} must be
* set with {@link #setCallback(Callback)}.
* <p>
* When an app is finished performing playback it must call {@link #release()}
* to clean up the session and notify any controllers.
* <p>
* MediaSession objects are thread safe.
* <p>
* This is a helper for accessing features in
* {@link android.media.session.MediaSession} introduced after API level 4 in a
* backwards compatible fashion.
*/
public class MediaSessionCompat {
private final MediaSessionImpl mImpl;
/**
* Set this flag on the session to indicate that it can handle media button
* events.
*/
public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
/**
* Set this flag on the session to indicate that it handles transport
* control commands through its {@link Callback}.
*/
public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
/**
* Creates a new session.
*
* @param context The context.
* @param tag A short name for debugging purposes.
*/
public MediaSessionCompat(Context context, String tag) {
if (context == null) {
throw new IllegalArgumentException("context must not be null");
}
if (TextUtils.isEmpty(tag)) {
throw new IllegalArgumentException("tag must not be null or empty");
}
if (android.os.Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaSessionImplApi21(context, tag);
} else {
mImpl = new MediaSessionImplBase();
}
}
private MediaSessionCompat(MediaSessionImpl impl) {
mImpl = impl;
}
/**
* Add a callback to receive updates on for the MediaSession. This includes
* media button and volume events. The caller's thread will be used to post
* events.
*
* @param callback The callback object
*/
public void setCallback(Callback callback) {
setCallback(callback, null);
}
/**
* Set the callback to receive updates for the MediaSession. This includes
* media button and volume events. Set the callback to null to stop
* receiving events.
*
* @param callback The callback to receive updates on.
* @param handler The handler that events should be posted on.
*/
public void setCallback(Callback callback, Handler handler) {
mImpl.setCallback(callback, handler != null ? handler : new Handler());
}
/**
* Set any flags for the session.
*
* @param flags The flags to set for this session.
*/
public void setFlags(int flags) {
mImpl.setFlags(flags);
}
/**
* Set the stream this session is playing on. This will affect the system's
* volume handling for this session. If {@link #setPlaybackToRemote} was
* previously called it will stop receiving volume commands and the system
* will begin sending volume changes to the appropriate stream.
* <p>
* By default sessions are on {@link AudioManager#STREAM_MUSIC}.
*
* @param stream The {@link AudioManager} stream this session is playing on.
*/
public void setPlaybackToLocal(int stream) {
mImpl.setPlaybackToLocal(stream);
}
/**
* Configure this session to use remote volume handling. This must be called
* to receive volume button events, otherwise the system will adjust the
* current stream volume for this session. If {@link #setPlaybackToLocal}
* was previously called that stream will stop receiving volume changes for
* this session.
*
* @param volumeProvider The provider that will handle volume changes. May
* not be null.
*/
public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
if (volumeProvider == null) {
throw new IllegalArgumentException("volumeProvider may not be null!");
}
mImpl.setPlaybackToRemote(volumeProvider);
}
/**
* Set if this session is currently active and ready to receive commands. If
* set to false your session's controller may not be discoverable. You must
* set the session to active before it can start receiving media button
* events or transport commands.
*
* @param active Whether this session is active or not.
*/
public void setActive(boolean active) {
mImpl.setActive(active);
}
/**
* Get the current active state of this session.
*
* @return True if the session is active, false otherwise.
*/
public boolean isActive() {
return mImpl.isActive();
}
/**
* Send a proprietary event to all MediaControllers listening to this
* Session. It's up to the Controller/Session owner to determine the meaning
* of any events.
*
* @param event The name of the event to send
* @param extras Any extras included with the event
*/
public void sendSessionEvent(String event, Bundle extras) {
if (TextUtils.isEmpty(event)) {
throw new IllegalArgumentException("event cannot be null or empty");
}
mImpl.sendSessionEvent(event, extras);
}
/**
* This must be called when an app has finished performing playback. If
* playback is expected to start again shortly the session can be left open,
* but it must be released if your activity or service is being destroyed.
*/
public void release() {
mImpl.release();
}
/**
* Retrieve a token object that can be used by apps to create a
* {@link MediaControllerCompat} for interacting with this session. The owner of
* the session is responsible for deciding how to distribute these tokens.
*
* @return A token that can be used to create a MediaController for this
* session.
*/
public Token getSessionToken() {
return mImpl.getSessionToken();
}
/**
* Update the current playback state.
*
* @param state The current state of playback
*/
public void setPlaybackState(PlaybackStateCompat state) {
mImpl.setPlaybackState(state);
}
/**
* Update the current metadata. New metadata can be created using
* {@link android.media.MediaMetadata.Builder}.
*
* @param metadata The new metadata
*/
public void setMetadata(MediaMetadataCompat metadata) {
mImpl.setMetadata(metadata);
}
/**
* Gets the underlying framework {@link android.media.session.MediaSession} object.
* <p>
* This method is only supported on API 21+.
* </p>
*
* @return The underlying {@link android.media.session.MediaSession} object,
* or null if none.
*/
public Object getMediaSession() {
return mImpl.getMediaSession();
}
/**
* Obtain a compat wrapper for an existing MediaSession.
*
* @param mediaSession The {@link android.media.session.MediaSession} to
* wrap.
* @return A compat wrapper for the provided session.
*/
public static MediaSessionCompat obtain(Object mediaSession) {
return new MediaSessionCompat(new MediaSessionImplApi21(mediaSession));
}
/**
* Receives transport controls, media buttons, and commands from controllers
* and the system. The callback may be set using {@link #setCallback}.
*/
public abstract static class Callback {
final Object mCallbackObj;
public Callback() {
if (android.os.Build.VERSION.SDK_INT >= 21) {
mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
} else {
mCallbackObj = null;
}
}
/**
* Called when a controller has sent a custom command to this session.
* The owner of the session may handle custom commands but is not
* required to.
*
* @param command The command name.
* @param extras Optional parameters for the command, may be null.
* @param cb A result receiver to which a result may be sent by the command, may be null.
*/
public void onCommand(String command, Bundle extras, ResultReceiver cb) {
}
/**
* Override to handle media button events.
*
* @param mediaButtonEvent The media button event intent.
* @return True if the event was handled, false otherwise.
*/
public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
return false;
}
/**
* Override to handle requests to begin playback.
*/
public void onPlay() {
}
/**
* Override to handle requests to pause playback.
*/
public void onPause() {
}
/**
* Override to handle requests to skip to the next media item.
*/
public void onSkipToNext() {
}
/**
* Override to handle requests to skip to the previous media item.
*/
public void onSkipToPrevious() {
}
/**
* Override to handle requests to fast forward.
*/
public void onFastForward() {
}
/**
* Override to handle requests to rewind.
*/
public void onRewind() {
}
/**
* Override to handle requests to stop playback.
*/
public void onStop() {
}
/**
* Override to handle requests to seek to a specific position in ms.
*
* @param pos New position to move to, in milliseconds.
*/
public void onSeekTo(long pos) {
}
/**
* Override to handle the item being rated.
*
* @param rating
*/
public void onSetRating(RatingCompat rating) {
}
private class StubApi21 implements MediaSessionCompatApi21.Callback {
@Override
public void onCommand(String command, Bundle extras, ResultReceiver cb) {
Callback.this.onCommand(command, extras, cb);
}
@Override
public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
return Callback.this.onMediaButtonEvent(mediaButtonIntent);
}
@Override
public void onPlay() {
Callback.this.onPlay();
}
@Override
public void onPause() {
Callback.this.onPause();
}
@Override
public void onSkipToNext() {
Callback.this.onSkipToNext();
}
@Override
public void onSkipToPrevious() {
Callback.this.onSkipToPrevious();
}
@Override
public void onFastForward() {
Callback.this.onFastForward();
}
@Override
public void onRewind() {
Callback.this.onRewind();
}
@Override
public void onStop() {
Callback.this.onStop();
}
@Override
public void onSeekTo(long pos) {
Callback.this.onSeekTo(pos);
}
@Override
public void onSetRating(Object ratingObj) {
Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
}
}
}
/**
* Represents an ongoing session. This may be passed to apps by the session
* owner to allow them to create a {@link MediaControllerCompat} to communicate with
* the session.
*/
public static final class Token implements Parcelable {
private final Parcelable mInner;
Token(Parcelable inner) {
mInner = inner;
}
@Override
public int describeContents() {
return mInner.describeContents();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mInner, flags);
}
/**
* Gets the underlying framework {@link android.media.session.MediaSession.Token} object.
* <p>
* This method is only supported on API 21+.
* </p>
*
* @return The underlying {@link android.media.session.MediaSession.Token} object,
* or null if none.
*/
public Object getToken() {
return mInner;
}
public static final Parcelable.Creator<Token> CREATOR
= new Parcelable.Creator<Token>() {
@Override
public Token createFromParcel(Parcel in) {
return new Token(in.readParcelable(null));
}
@Override
public Token[] newArray(int size) {
return new Token[size];
}
};
}
interface MediaSessionImpl {
void setCallback(Callback callback, Handler handler);
void setFlags(int flags);
void setPlaybackToLocal(int stream);
void setPlaybackToRemote(VolumeProviderCompat volumeProvider);
void setActive(boolean active);
boolean isActive();
void sendSessionEvent(String event, Bundle extras);
void release();
Token getSessionToken();
void setPlaybackState(PlaybackStateCompat state);
void setMetadata(MediaMetadataCompat metadata);
Object getMediaSession();
}
// TODO: compatibility implementation
static class MediaSessionImplBase implements MediaSessionImpl {
@Override
public void setCallback(Callback callback, Handler handler) {
}
@Override
public void setFlags(int flags) {
}
@Override
public void setPlaybackToLocal(int stream) {
}
@Override
public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
}
@Override
public void setActive(boolean active) {
}
@Override
public boolean isActive() {
return false;
}
@Override
public void sendSessionEvent(String event, Bundle extras) {
}
@Override
public void release() {
}
@Override
public Token getSessionToken() {
return null;
}
@Override
public void setPlaybackState(PlaybackStateCompat state) {
}
@Override
public void setMetadata(MediaMetadataCompat metadata) {
}
@Override
public Object getMediaSession() {
return null;
}
}
static class MediaSessionImplApi21 implements MediaSessionImpl {
private final Object mSessionObj;
private final Token mToken;
public MediaSessionImplApi21(Context context, String tag) {
mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
}
public MediaSessionImplApi21(Object mediaSession) {
mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession);
mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
}
@Override
public void setCallback(Callback callback, Handler handler) {
MediaSessionCompatApi21.setCallback(mSessionObj, callback.mCallbackObj, handler);
}
@Override
public void setFlags(int flags) {
MediaSessionCompatApi21.setFlags(mSessionObj, flags);
}
@Override
public void setPlaybackToLocal(int stream) {
MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
}
@Override
public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
volumeProvider.getVolumeProvider());
}
@Override
public void setActive(boolean active) {
MediaSessionCompatApi21.setActive(mSessionObj, active);
}
@Override
public boolean isActive() {
return MediaSessionCompatApi21.isActive(mSessionObj);
}
@Override
public void sendSessionEvent(String event, Bundle extras) {
MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
}
@Override
public void release() {
MediaSessionCompatApi21.release(mSessionObj);
}
@Override
public Token getSessionToken() {
return mToken;
}
@Override
public void setPlaybackState(PlaybackStateCompat state) {
MediaSessionCompatApi21.setPlaybackState(mSessionObj, state.getPlaybackState());
}
@Override
public void setMetadata(MediaMetadataCompat metadata) {
MediaSessionCompatApi21.setMetadata(mSessionObj, metadata.getMediaMetadata());
}
@Override
public Object getMediaSession() {
return mSessionObj;
}
}
}