/*
 * Copyright (C) 2011 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.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.session.MediaSessionLegacyHelper;
import android.media.session.PlaybackState;
import android.media.session.MediaSession;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;

import java.lang.IllegalArgumentException;

RemoteControlClient enables exposing information meant to be consumed by remote controls capable of displaying metadata, artwork and media transport control buttons.

A remote control client object is associated with a media button event receiver. This event receiver must have been previously registered with AudioManager.registerMediaButtonEventReceiver(ComponentName) before the RemoteControlClient can be registered through AudioManager.registerRemoteControlClient(RemoteControlClient).

Here is an example of creating a RemoteControlClient instance after registering a media button event receiver:

ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName());
AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
myAudioManager.registerMediaButtonEventReceiver(myEventReceiver);
// build the PendingIntent for the remote control client
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(myEventReceiver);
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
// create and register the remote control client
RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
myAudioManager.registerRemoteControlClient(myRemoteControlClient);
Deprecated:Use MediaSession instead.
/** * RemoteControlClient enables exposing information meant to be consumed by remote controls * capable of displaying metadata, artwork and media transport control buttons. * * <p>A remote control client object is associated with a media button event receiver. This * event receiver must have been previously registered with * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the * RemoteControlClient can be registered through * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. * * <p>Here is an example of creating a RemoteControlClient instance after registering a media * button event receiver: * <pre>ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName()); * AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); * myAudioManager.registerMediaButtonEventReceiver(myEventReceiver); * // build the PendingIntent for the remote control client * Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); * mediaButtonIntent.setComponent(myEventReceiver); * PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); * // create and register the remote control client * RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent); * myAudioManager.registerRemoteControlClient(myRemoteControlClient);</pre> * * @deprecated Use {@link MediaSession} instead. */
@Deprecated public class RemoteControlClient { private final static String TAG = "RemoteControlClient"; private final static boolean DEBUG = false;
Playback state of a RemoteControlClient which is stopped.
See Also:
  • setPlaybackState(int)
/** * Playback state of a RemoteControlClient which is stopped. * * @see #setPlaybackState(int) */
public final static int PLAYSTATE_STOPPED = 1;
Playback state of a RemoteControlClient which is paused.
See Also:
  • setPlaybackState(int)
/** * Playback state of a RemoteControlClient which is paused. * * @see #setPlaybackState(int) */
public final static int PLAYSTATE_PAUSED = 2;
Playback state of a RemoteControlClient which is playing media.
See Also:
  • setPlaybackState(int)
/** * Playback state of a RemoteControlClient which is playing media. * * @see #setPlaybackState(int) */
public final static int PLAYSTATE_PLAYING = 3;
Playback state of a RemoteControlClient which is fast forwarding in the media it is currently playing.
See Also:
  • setPlaybackState(int)
/** * Playback state of a RemoteControlClient which is fast forwarding in the media * it is currently playing. * * @see #setPlaybackState(int) */
public final static int PLAYSTATE_FAST_FORWARDING = 4;
Playback state of a RemoteControlClient which is fast rewinding in the media it is currently playing.
See Also:
  • setPlaybackState(int)
/** * Playback state of a RemoteControlClient which is fast rewinding in the media * it is currently playing. * * @see #setPlaybackState(int) */
public final static int PLAYSTATE_REWINDING = 5;
Playback state of a RemoteControlClient which is skipping to the next logical chapter (such as a song in a playlist) in the media it is currently playing.
See Also:
  • setPlaybackState(int)
/** * Playback state of a RemoteControlClient which is skipping to the next * logical chapter (such as a song in a playlist) in the media it is currently playing. * * @see #setPlaybackState(int) */
public final static int PLAYSTATE_SKIPPING_FORWARDS = 6;
Playback state of a RemoteControlClient which is skipping back to the previous logical chapter (such as a song in a playlist) in the media it is currently playing.
See Also:
  • setPlaybackState(int)
/** * Playback state of a RemoteControlClient which is skipping back to the previous * logical chapter (such as a song in a playlist) in the media it is currently playing. * * @see #setPlaybackState(int) */
public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7;
Playback state of a RemoteControlClient which is buffering data to play before it can start or resume playback.
See Also:
  • setPlaybackState(int)
/** * Playback state of a RemoteControlClient which is buffering data to play before it can * start or resume playback. * * @see #setPlaybackState(int) */
public final static int PLAYSTATE_BUFFERING = 8;
Playback state of a RemoteControlClient which cannot perform any playback related operation because of an internal error. Examples of such situations are no network connectivity when attempting to stream data from a server, or expired user credentials when trying to play subscription-based content.
See Also:
  • setPlaybackState(int)
/** * Playback state of a RemoteControlClient which cannot perform any playback related * operation because of an internal error. Examples of such situations are no network * connectivity when attempting to stream data from a server, or expired user credentials * when trying to play subscription-based content. * * @see #setPlaybackState(int) */
public final static int PLAYSTATE_ERROR = 9;
@hide The value of a playback state when none has been declared. Intentionally hidden as an application shouldn't set such a playback state value.
/** * @hide * The value of a playback state when none has been declared. * Intentionally hidden as an application shouldn't set such a playback state value. */
public final static int PLAYSTATE_NONE = 0;
@hide The default playback type, "local", indicating the presentation of the media is happening on the same device (e.g. a phone, a tablet) as where it is controlled from.
/** * @hide * The default playback type, "local", indicating the presentation of the media is happening on * the same device (e.g. a phone, a tablet) as where it is controlled from. */
public final static int PLAYBACK_TYPE_LOCAL = 0;
@hide A playback type indicating the presentation of the media is happening on a different device (i.e. the remote device) than where it is controlled from.
/** * @hide * A playback type indicating the presentation of the media is happening on * a different device (i.e. the remote device) than where it is controlled from. */
public final static int PLAYBACK_TYPE_REMOTE = 1; private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL; private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE;
See Also:
  • #PLAYBACKINFO_VOLUME_HANDLING.
@hide Playback information indicating the playback volume is fixed, i.e. it cannot be controlled from this object. An example of fixed playback volume is a remote player, playing over HDMI where the user prefer to control the volume on the HDMI sink, rather than attenuate at the source.
/** * @hide * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled * from this object. An example of fixed playback volume is a remote player, playing over HDMI * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the * source. * @see #PLAYBACKINFO_VOLUME_HANDLING. */
public final static int PLAYBACK_VOLUME_FIXED = 0;
See Also:
  • #PLAYBACKINFO_VOLUME_HANDLING.
@hide Playback information indicating the playback volume is variable and can be controlled from this object.
/** * @hide * Playback information indicating the playback volume is variable and can be controlled from * this object. * @see #PLAYBACKINFO_VOLUME_HANDLING. */
public final static int PLAYBACK_VOLUME_VARIABLE = 1;
See Also:
  • #PLAYBACKINFO_VOLUME_HANDLING.
@hide(to be un-hidden) The playback information value indicating the value of a given information type is invalid.
/** * @hide (to be un-hidden) * The playback information value indicating the value of a given information type is invalid. * @see #PLAYBACKINFO_VOLUME_HANDLING. */
public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE;
@hide An unknown or invalid playback position value.
/** * @hide * An unknown or invalid playback position value. */
public final static long PLAYBACK_POSITION_INVALID = -1;
@hide An invalid playback position value associated with the use of setPlaybackState(int) used to indicate that playback position will remain unknown.
/** * @hide * An invalid playback position value associated with the use of {@link #setPlaybackState(int)} * used to indicate that playback position will remain unknown. */
public final static long PLAYBACK_POSITION_ALWAYS_UNKNOWN = 0x8019771980198300L;
@hide The default playback speed, 1x.
/** * @hide * The default playback speed, 1x. */
public final static float PLAYBACK_SPEED_1X = 1.0f; //========================================== // Public keys for playback information
@hide Playback information that defines the type of playback associated with this RemoteControlClient. See PLAYBACK_TYPE_LOCAL and PLAYBACK_TYPE_REMOTE.
/** * @hide * Playback information that defines the type of playback associated with this * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}. */
public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1;
@hide Playback information that defines at what volume the playback associated with this RemoteControlClient is performed. This information is only used when the playback type is not local (see PLAYBACKINFO_PLAYBACK_TYPE).
/** * @hide * Playback information that defines at what volume the playback associated with this * RemoteControlClient is performed. This information is only used when the playback type is not * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}). */
public final static int PLAYBACKINFO_VOLUME = 2;
@hide Playback information that defines the maximum volume volume value that is supported by the playback associated with this RemoteControlClient. This information is only used when the playback type is not local (see PLAYBACKINFO_PLAYBACK_TYPE).
/** * @hide * Playback information that defines the maximum volume volume value that is supported * by the playback associated with this RemoteControlClient. This information is only used * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}). */
public final static int PLAYBACKINFO_VOLUME_MAX = 3;
See Also:
@hide Playback information that defines how volume is handled for the presentation of the media.
/** * @hide * Playback information that defines how volume is handled for the presentation of the media. * @see #PLAYBACK_VOLUME_FIXED * @see #PLAYBACK_VOLUME_VARIABLE */
public final static int PLAYBACKINFO_VOLUME_HANDLING = 4;
@hide Playback information that defines over what stream type the media is presented.
/** * @hide * Playback information that defines over what stream type the media is presented. */
public final static int PLAYBACKINFO_USES_STREAM = 5; //========================================== // Public flags for the supported transport control capabilities
Flag indicating a RemoteControlClient makes use of the "previous" media key.
See Also:
/** * Flag indicating a RemoteControlClient makes use of the "previous" media key. * * @see #setTransportControlFlags(int) * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS */
public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
Flag indicating a RemoteControlClient makes use of the "rewind" media key.
See Also:
/** * Flag indicating a RemoteControlClient makes use of the "rewind" media key. * * @see #setTransportControlFlags(int) * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND */
public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
Flag indicating a RemoteControlClient makes use of the "play" media key.
See Also:
/** * Flag indicating a RemoteControlClient makes use of the "play" media key. * * @see #setTransportControlFlags(int) * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY */
public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
See Also:
/** * Flag indicating a RemoteControlClient makes use of the "play/pause" media key. * * @see #setTransportControlFlags(int) * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE */
public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
Flag indicating a RemoteControlClient makes use of the "pause" media key.
See Also:
/** * Flag indicating a RemoteControlClient makes use of the "pause" media key. * * @see #setTransportControlFlags(int) * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE */
public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
Flag indicating a RemoteControlClient makes use of the "stop" media key.
See Also:
/** * Flag indicating a RemoteControlClient makes use of the "stop" media key. * * @see #setTransportControlFlags(int) * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP */
public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
See Also:
/** * Flag indicating a RemoteControlClient makes use of the "fast forward" media key. * * @see #setTransportControlFlags(int) * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD */
public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
Flag indicating a RemoteControlClient makes use of the "next" media key.
See Also:
/** * Flag indicating a RemoteControlClient makes use of the "next" media key. * * @see #setTransportControlFlags(int) * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT */
public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
Flag indicating a RemoteControlClient can receive changes in the media playback position through the OnPlaybackPositionUpdateListener interface. This flag must be set in order for components that display the RemoteControlClient information, to display and let the user control media playback position.
See Also:
/** * Flag indicating a RemoteControlClient can receive changes in the media playback position * through the {@link OnPlaybackPositionUpdateListener} interface. This flag must be set * in order for components that display the RemoteControlClient information, to display and * let the user control media playback position. * @see #setTransportControlFlags(int) * @see #setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener) * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener) */
public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8;
Flag indicating a RemoteControlClient supports ratings. This flag must be set in order for components that display the RemoteControlClient information, to display ratings information, and, if ratings are declared editable (by calling MediaMetadataEditor.addEditableKey(int) with the MediaMetadataEditor.RATING_KEY_BY_USER key), it will enable the user to rate the media, with values being received through the interface set with setMetadataUpdateListener(OnMetadataUpdateListener).
See Also:
/** * Flag indicating a RemoteControlClient supports ratings. * This flag must be set in order for components that display the RemoteControlClient * information, to display ratings information, and, if ratings are declared editable * (by calling {@link MediaMetadataEditor#addEditableKey(int)} with the * {@link MediaMetadataEditor#RATING_KEY_BY_USER} key), it will enable the user to rate * the media, with values being received through the interface set with * {@link #setMetadataUpdateListener(OnMetadataUpdateListener)}. * @see #setTransportControlFlags(int) */
public final static int FLAG_KEY_MEDIA_RATING = 1 << 9;
@hide The flags for when no media keys are declared supported. Intentionally hidden as an application shouldn't set the transport control flags to this value.
/** * @hide * The flags for when no media keys are declared supported. * Intentionally hidden as an application shouldn't set the transport control flags * to this value. */
public final static int FLAGS_KEY_MEDIA_NONE = 0;
@hide Flag used to signal some type of metadata exposed by the RemoteControlClient is requested.
/** * @hide * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested. */
public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0;
@hide Flag used to signal that the transport control buttons supported by the RemoteControlClient are requested. This can for instance happen when playback is at the end of a playlist, and the "next" operation is not supported anymore.
/** * @hide * Flag used to signal that the transport control buttons supported by the * RemoteControlClient are requested. * This can for instance happen when playback is at the end of a playlist, and the "next" * operation is not supported anymore. */
public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1;
@hide Flag used to signal that the playback state of the RemoteControlClient is requested.
/** * @hide * Flag used to signal that the playback state of the RemoteControlClient is requested. */
public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2;
@hide Flag used to signal that the album art for the RemoteControlClient is requested.
/** * @hide * Flag used to signal that the album art for the RemoteControlClient is requested. */
public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3; private MediaSession mSession;
Class constructor.
Params:
See Also:
/** * Class constructor. * @param mediaButtonIntent The intent that will be sent for the media button events sent * by remote controls. * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} * action, and have a component that will handle the intent (set with * {@link Intent#setComponent(ComponentName)}) registered with * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} * before this new RemoteControlClient can itself be registered with * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) * @see AudioManager#registerRemoteControlClient(RemoteControlClient) */
public RemoteControlClient(PendingIntent mediaButtonIntent) { mRcMediaIntent = mediaButtonIntent; }
Class constructor for a remote control client whose internal event handling happens on a user-provided Looper.
Params:
See Also:
/** * Class constructor for a remote control client whose internal event handling * happens on a user-provided Looper. * @param mediaButtonIntent The intent that will be sent for the media button events sent * by remote controls. * This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON} * action, and have a component that will handle the intent (set with * {@link Intent#setComponent(ComponentName)}) registered with * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} * before this new RemoteControlClient can itself be registered with * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}. * @param looper The Looper running the event loop. * @see AudioManager#registerMediaButtonEventReceiver(ComponentName) * @see AudioManager#registerRemoteControlClient(RemoteControlClient) */
public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) { mRcMediaIntent = mediaButtonIntent; }
@hide
/** * @hide */
public void registerWithSession(MediaSessionLegacyHelper helper) { helper.addRccListener(mRcMediaIntent, mTransportListener); mSession = helper.getSession(mRcMediaIntent); setTransportControlFlags(mTransportControlFlags); }
@hide
/** * @hide */
public void unregisterWithSession(MediaSessionLegacyHelper helper) { helper.removeRccListener(mRcMediaIntent); mSession = null; }
Get a MediaSession associated with this RCC. It will only have a session while it is registered with AudioManager.registerRemoteControlClient. The session returned should not be modified directly by the application but may be used with other APIs that require a session.
Returns:A media session object or null.
/** * Get a {@link MediaSession} associated with this RCC. It will only have a * session while it is registered with * {@link AudioManager#registerRemoteControlClient}. The session returned * should not be modified directly by the application but may be used with * other APIs that require a session. * * @return A media session object or null. */
public MediaSession getMediaSession() { return mSession; }
Class used to modify metadata in a RemoteControlClient object. Use RemoteControlClient.editMetadata(boolean) to create an instance of an editor, on which you set the metadata for the RemoteControlClient instance. Once all the information has been set, use apply() to make it the new metadata that should be displayed for the associated client. Once the metadata has been "applied", you cannot reuse this instance of the MetadataEditor.
Deprecated:Use MediaMetadata and MediaSession instead.
/** * Class used to modify metadata in a {@link RemoteControlClient} object. * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor, * on which you set the metadata for the RemoteControlClient instance. Once all the information * has been set, use {@link #apply()} to make it the new metadata that should be displayed * for the associated client. Once the metadata has been "applied", you cannot reuse this * instance of the MetadataEditor. * * @deprecated Use {@link MediaMetadata} and {@link MediaSession} instead. */
@Deprecated public class MetadataEditor extends MediaMetadataEditor { // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance private MetadataEditor() { }
@hide
/** * @hide */
public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); }
The metadata key for the content artwork / album art.
/** * The metadata key for the content artwork / album art. */
public final static int BITMAP_KEY_ARTWORK = 100;
@hide TODO(jmtrivi) have lockscreen move to the new key name and remove
/** * @hide * TODO(jmtrivi) have lockscreen move to the new key name and remove */
public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK;
Adds textual information to be displayed. Note that none of the information added after apply() has been called, will be displayed.
Params:
Returns:Returns a reference to the same MetadataEditor object, so you can chain put calls together.
/** * Adds textual information to be displayed. * Note that none of the information added after {@link #apply()} has been called, * will be displayed. * @param key The identifier of a the metadata field to set. Valid values are * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}. * @param value The text for the given key, or {@code null} to signify there is no valid * information for the field. * @return Returns a reference to the same MetadataEditor object, so you can chain put * calls together. */
public synchronized MetadataEditor putString(int key, String value) throws IllegalArgumentException { super.putString(key, value); if (mMetadataBuilder != null) { // MediaMetadata supports all the same fields as MetadataEditor String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key); // But just in case, don't add things we don't understand if (metadataKey != null) { mMetadataBuilder.putText(metadataKey, value); } } return this; }
Adds numerical information to be displayed. Note that none of the information added after apply() has been called, will be displayed.
Params:
Throws:
Returns:Returns a reference to the same MetadataEditor object, so you can chain put calls together.
/** * Adds numerical information to be displayed. * Note that none of the information added after {@link #apply()} has been called, * will be displayed. * @param key the identifier of a the metadata field to set. Valid values are * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}, * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value * expressed in milliseconds), * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}. * @param value The long value for the given key * @return Returns a reference to the same MetadataEditor object, so you can chain put * calls together. * @throws IllegalArgumentException */
public synchronized MetadataEditor putLong(int key, long value) throws IllegalArgumentException { super.putLong(key, value); if (mMetadataBuilder != null) { // MediaMetadata supports all the same fields as MetadataEditor String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key); // But just in case, don't add things we don't understand if (metadataKey != null) { mMetadataBuilder.putLong(metadataKey, value); } } return this; }
Sets the album / artwork picture to be displayed on the remote control.
Params:
  • key – the identifier of the bitmap to set. The only valid value is BITMAP_KEY_ARTWORK
  • bitmap – The bitmap for the artwork, or null if there isn't any.
Throws:
See Also:
Returns:Returns a reference to the same MetadataEditor object, so you can chain put calls together.
/** * Sets the album / artwork picture to be displayed on the remote control. * @param key the identifier of the bitmap to set. The only valid value is * {@link #BITMAP_KEY_ARTWORK} * @param bitmap The bitmap for the artwork, or null if there isn't any. * @return Returns a reference to the same MetadataEditor object, so you can chain put * calls together. * @throws IllegalArgumentException * @see android.graphics.Bitmap */
@Override public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap) throws IllegalArgumentException { super.putBitmap(key, bitmap); if (mMetadataBuilder != null) { // MediaMetadata supports all the same fields as MetadataEditor String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key); // But just in case, don't add things we don't understand if (metadataKey != null) { mMetadataBuilder.putBitmap(metadataKey, bitmap); } } return this; } @Override public synchronized MetadataEditor putObject(int key, Object object) throws IllegalArgumentException { super.putObject(key, object); if (mMetadataBuilder != null && (key == MediaMetadataEditor.RATING_KEY_BY_USER || key == MediaMetadataEditor.RATING_KEY_BY_OTHERS)) { String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key); if (metadataKey != null) { mMetadataBuilder.putRating(metadataKey, (Rating) object); } } return this; }
Clears all the metadata that has been set since the MetadataEditor instance was created (with RemoteControlClient.editMetadata(boolean)). Note that clearing the metadata doesn't reset the editable keys (use MediaMetadataEditor.removeEditableKeys() instead).
/** * Clears all the metadata that has been set since the MetadataEditor instance was created * (with {@link RemoteControlClient#editMetadata(boolean)}). * Note that clearing the metadata doesn't reset the editable keys * (use {@link MediaMetadataEditor#removeEditableKeys()} instead). */
@Override public synchronized void clear() { super.clear(); }
Associates all the metadata that has been set since the MetadataEditor instance was created with RemoteControlClient.editMetadata(boolean), or since clear() was called, with the RemoteControlClient. Once "applied", this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
/** * Associates all the metadata that has been set since the MetadataEditor instance was * created with {@link RemoteControlClient#editMetadata(boolean)}, or since * {@link #clear()} was called, with the RemoteControlClient. Once "applied", * this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata. */
public synchronized void apply() { if (mApplied) { Log.e(TAG, "Can't apply a previously applied MetadataEditor"); return; } synchronized (mCacheLock) { // Still build the old metadata so when creating a new editor // you get the expected values. // assign the edited data mMetadata = new Bundle(mEditorMetadata); // add the information about editable keys mMetadata.putLong(String.valueOf(KEY_EDITABLE_MASK), mEditableKeys); if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) { mOriginalArtwork.recycle(); } mOriginalArtwork = mEditorArtwork; mEditorArtwork = null; // USE_SESSIONS if (mSession != null && mMetadataBuilder != null) { mMediaMetadata = mMetadataBuilder.build(); mSession.setMetadata(mMediaMetadata); } mApplied = true; } } }
Creates a MetadataEditor.
Params:
  • startEmpty – Set to false if you want the MetadataEditor to contain the metadata that was previously applied to the RemoteControlClient, or true if it is to be created empty.
Returns:a new MetadataEditor instance.
/** * Creates a {@link MetadataEditor}. * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that * was previously applied to the RemoteControlClient, or true if it is to be created empty. * @return a new MetadataEditor instance. */
public MetadataEditor editMetadata(boolean startEmpty) { MetadataEditor editor = new MetadataEditor(); if (startEmpty) { editor.mEditorMetadata = new Bundle(); editor.mEditorArtwork = null; editor.mMetadataChanged = true; editor.mArtworkChanged = true; editor.mEditableKeys = 0; } else { editor.mEditorMetadata = new Bundle(mMetadata); editor.mEditorArtwork = mOriginalArtwork; editor.mMetadataChanged = false; editor.mArtworkChanged = false; } // USE_SESSIONS if (startEmpty || mMediaMetadata == null) { editor.mMetadataBuilder = new MediaMetadata.Builder(); } else { editor.mMetadataBuilder = new MediaMetadata.Builder(mMediaMetadata); } return editor; }
Sets the current playback state.
Params:
/** * Sets the current playback state. * @param state The current playback state, one of the following values: * {@link #PLAYSTATE_STOPPED}, * {@link #PLAYSTATE_PAUSED}, * {@link #PLAYSTATE_PLAYING}, * {@link #PLAYSTATE_FAST_FORWARDING}, * {@link #PLAYSTATE_REWINDING}, * {@link #PLAYSTATE_SKIPPING_FORWARDS}, * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, * {@link #PLAYSTATE_BUFFERING}, * {@link #PLAYSTATE_ERROR}. */
public void setPlaybackState(int state) { setPlaybackStateInt(state, PLAYBACK_POSITION_ALWAYS_UNKNOWN, PLAYBACK_SPEED_1X, false /* legacy API, converting to method with position and speed */); }
Sets the current playback state and the matching media position for the current playback speed.
Params:
/** * Sets the current playback state and the matching media position for the current playback * speed. * @param state The current playback state, one of the following values: * {@link #PLAYSTATE_STOPPED}, * {@link #PLAYSTATE_PAUSED}, * {@link #PLAYSTATE_PLAYING}, * {@link #PLAYSTATE_FAST_FORWARDING}, * {@link #PLAYSTATE_REWINDING}, * {@link #PLAYSTATE_SKIPPING_FORWARDS}, * {@link #PLAYSTATE_SKIPPING_BACKWARDS}, * {@link #PLAYSTATE_BUFFERING}, * {@link #PLAYSTATE_ERROR}. * @param timeInMs a 0 or positive value for the current media position expressed in ms * (same unit as for when sending the media duration, if applicable, with * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the * {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not * known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state * is {@link #PLAYSTATE_BUFFERING} and nothing had played yet). * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback, * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is * playing (e.g. when state is {@link #PLAYSTATE_ERROR}). */
public void setPlaybackState(int state, long timeInMs, float playbackSpeed) { setPlaybackStateInt(state, timeInMs, playbackSpeed, true); } private void setPlaybackStateInt(int state, long timeInMs, float playbackSpeed, boolean hasPosition) { synchronized(mCacheLock) { if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs) || (mPlaybackSpeed != playbackSpeed)) { // store locally mPlaybackState = state; // distinguish between an application not knowing the current playback position // at the moment and an application using the API where only the playback state // is passed, not the playback position. if (hasPosition) { if (timeInMs < 0) { mPlaybackPositionMs = PLAYBACK_POSITION_INVALID; } else { mPlaybackPositionMs = timeInMs; } } else { mPlaybackPositionMs = PLAYBACK_POSITION_ALWAYS_UNKNOWN; } mPlaybackSpeed = playbackSpeed; // keep track of when the state change occurred mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime(); // USE_SESSIONS if (mSession != null) { int pbState = PlaybackState.getStateFromRccState(state); long position = hasPosition ? mPlaybackPositionMs : PlaybackState.PLAYBACK_POSITION_UNKNOWN; PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState); bob.setState(pbState, position, playbackSpeed, SystemClock.elapsedRealtime()); bob.setErrorMessage(null); mSessionPlaybackState = bob.build(); mSession.setPlaybackState(mSessionPlaybackState); } } } }
Sets the flags for the media transport control buttons that this client supports.
Params:
/** * Sets the flags for the media transport control buttons that this client supports. * @param transportControlFlags A combination of the following flags: * {@link #FLAG_KEY_MEDIA_PREVIOUS}, * {@link #FLAG_KEY_MEDIA_REWIND}, * {@link #FLAG_KEY_MEDIA_PLAY}, * {@link #FLAG_KEY_MEDIA_PLAY_PAUSE}, * {@link #FLAG_KEY_MEDIA_PAUSE}, * {@link #FLAG_KEY_MEDIA_STOP}, * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, * {@link #FLAG_KEY_MEDIA_NEXT}, * {@link #FLAG_KEY_MEDIA_POSITION_UPDATE}, * {@link #FLAG_KEY_MEDIA_RATING}. */
public void setTransportControlFlags(int transportControlFlags) { synchronized(mCacheLock) { // store locally mTransportControlFlags = transportControlFlags; // USE_SESSIONS if (mSession != null) { PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState); bob.setActions( PlaybackState.getActionsFromRccControlFlags(transportControlFlags)); mSessionPlaybackState = bob.build(); mSession.setPlaybackState(mSessionPlaybackState); } } }
Interface definition for a callback to be invoked when one of the metadata values has been updated. Implement this interface to receive metadata updates after registering your listener through RemoteControlClient.setMetadataUpdateListener(OnMetadataUpdateListener).
/** * Interface definition for a callback to be invoked when one of the metadata values has * been updated. * Implement this interface to receive metadata updates after registering your listener * through {@link RemoteControlClient#setMetadataUpdateListener(OnMetadataUpdateListener)}. */
public interface OnMetadataUpdateListener {
Called on the implementer to notify that the metadata field for the given key has been updated to the new value.
Params:
  • key – the identifier of the updated metadata field.
  • newValue – the Object storing the new value for the key.
/** * Called on the implementer to notify that the metadata field for the given key has * been updated to the new value. * @param key the identifier of the updated metadata field. * @param newValue the Object storing the new value for the key. */
public abstract void onMetadataUpdate(int key, Object newValue); }
Sets the listener to be called whenever the metadata is updated. New metadata values will be received in the same thread as the one in which RemoteControlClient was created.
Params:
  • l – the metadata update listener
/** * Sets the listener to be called whenever the metadata is updated. * New metadata values will be received in the same thread as the one in which * RemoteControlClient was created. * @param l the metadata update listener */
public void setMetadataUpdateListener(OnMetadataUpdateListener l) { synchronized(mCacheLock) { mMetadataUpdateListener = l; } }
Interface definition for a callback to be invoked when the media playback position is requested to be updated.
See Also:
  • FLAG_KEY_MEDIA_POSITION_UPDATE.FLAG_KEY_MEDIA_POSITION_UPDATE
/** * Interface definition for a callback to be invoked when the media playback position is * requested to be updated. * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE */
public interface OnPlaybackPositionUpdateListener {
Called on the implementer to notify it that the playback head should be set at the given position. If the position can be changed from its current value, the implementor of the interface must also update the playback position using RemoteControlClient.setPlaybackState(int, long, float) to reflect the actual new position being used, regardless of whether it differs from the requested position. Failure to do so would cause the system to not know the new actual playback position, and user interface components would fail to show the user where playback resumed after the position was updated.
Params:
  • newPositionMs – the new requested position in the current media, expressed in ms.
/** * Called on the implementer to notify it that the playback head should be set at the given * position. If the position can be changed from its current value, the implementor of * the interface must also update the playback position using * {@link #setPlaybackState(int, long, float)} to reflect the actual new * position being used, regardless of whether it differs from the requested position. * Failure to do so would cause the system to not know the new actual playback position, * and user interface components would fail to show the user where playback resumed after * the position was updated. * @param newPositionMs the new requested position in the current media, expressed in ms. */
void onPlaybackPositionUpdate(long newPositionMs); }
Interface definition for a callback to be invoked when the media playback position is queried.
See Also:
  • FLAG_KEY_MEDIA_POSITION_UPDATE.FLAG_KEY_MEDIA_POSITION_UPDATE
/** * Interface definition for a callback to be invoked when the media playback position is * queried. * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE */
public interface OnGetPlaybackPositionListener {
Called on the implementer of the interface to query the current playback position.
Returns:a negative value if the current playback position (or the last valid playback position) is not known, or a zero or positive value expressed in ms indicating the current position, or the last valid known position.
/** * Called on the implementer of the interface to query the current playback position. * @return a negative value if the current playback position (or the last valid playback * position) is not known, or a zero or positive value expressed in ms indicating the * current position, or the last valid known position. */
long onGetPlaybackPosition(); }
Sets the listener to be called whenever the media playback position is requested to be updated. Notifications will be received in the same thread as the one in which RemoteControlClient was created.
Params:
  • l – the position update listener to be called
/** * Sets the listener to be called whenever the media playback position is requested * to be updated. * Notifications will be received in the same thread as the one in which RemoteControlClient * was created. * @param l the position update listener to be called */
public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) { synchronized(mCacheLock) { mPositionUpdateListener = l; } }
Sets the listener to be called whenever the media current playback position is needed. Queries will be received in the same thread as the one in which RemoteControlClient was created.
Params:
  • l – the listener to be called to retrieve the playback position
/** * Sets the listener to be called whenever the media current playback position is needed. * Queries will be received in the same thread as the one in which RemoteControlClient * was created. * @param l the listener to be called to retrieve the playback position */
public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) { synchronized(mCacheLock) { mPositionProvider = l; } }
@hide Flag to reflect that the application controlling this RemoteControlClient sends playback position updates. The playback position being "readable" is considered from the application's point of view.
/** * @hide * Flag to reflect that the application controlling this RemoteControlClient sends playback * position updates. The playback position being "readable" is considered from the application's * point of view. */
public static int MEDIA_POSITION_READABLE = 1 << 0;
@hide Flag to reflect that the application controlling this RemoteControlClient can receive playback position updates. The playback position being "writable" is considered from the application's point of view.
/** * @hide * Flag to reflect that the application controlling this RemoteControlClient can receive * playback position updates. The playback position being "writable" * is considered from the application's point of view. */
public static int MEDIA_POSITION_WRITABLE = 1 << 1;
@hide
/** @hide */
public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
@hide
/** @hide */
// hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC] public final static int DEFAULT_PLAYBACK_VOLUME = 15;
Lock for all cached data
/** * Lock for all cached data */
private final Object mCacheLock = new Object();
Cache for the playback state. Access synchronized on mCacheLock
/** * Cache for the playback state. * Access synchronized on mCacheLock */
private int mPlaybackState = PLAYSTATE_NONE;
Time of last play state change Access synchronized on mCacheLock
/** * Time of last play state change * Access synchronized on mCacheLock */
private long mPlaybackStateChangeTimeMs = 0;
Last playback position in ms reported by the user
/** * Last playback position in ms reported by the user */
private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
Last playback speed reported by the user
/** * Last playback speed reported by the user */
private float mPlaybackSpeed = PLAYBACK_SPEED_1X;
Cache for the artwork bitmap. Access synchronized on mCacheLock Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be accessed to be resized, in which case a copy will be made. This would add overhead in Bundle operations.
/** * Cache for the artwork bitmap. * Access synchronized on mCacheLock * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be * accessed to be resized, in which case a copy will be made. This would add overhead in * Bundle operations. */
private Bitmap mOriginalArtwork;
Cache for the transport control mask. Access synchronized on mCacheLock
/** * Cache for the transport control mask. * Access synchronized on mCacheLock */
private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE;
Cache for the metadata strings. Access synchronized on mCacheLock This is re-initialized in apply() and so cannot be final.
/** * Cache for the metadata strings. * Access synchronized on mCacheLock * This is re-initialized in apply() and so cannot be final. */
private Bundle mMetadata = new Bundle();
Listener registered by user of RemoteControlClient to receive requests for playback position update requests.
/** * Listener registered by user of RemoteControlClient to receive requests for playback position * update requests. */
private OnPlaybackPositionUpdateListener mPositionUpdateListener;
Provider registered by user of RemoteControlClient to provide the current playback position.
/** * Provider registered by user of RemoteControlClient to provide the current playback position. */
private OnGetPlaybackPositionListener mPositionProvider;
Listener registered by user of RemoteControlClient to receive edit changes to metadata it exposes.
/** * Listener registered by user of RemoteControlClient to receive edit changes to metadata * it exposes. */
private OnMetadataUpdateListener mMetadataUpdateListener;
The current remote control client generation ID across the system, as known by this object
/** * The current remote control client generation ID across the system, as known by this object */
private int mCurrentClientGenId = -1;
The media button intent description associated with this remote control client (can / should include target component for intent handling, used when persisting media button event receiver across reboots).
/** * The media button intent description associated with this remote control client * (can / should include target component for intent handling, used when persisting media * button event receiver across reboots). */
private final PendingIntent mRcMediaIntent;
Reflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true.
/** * Reflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true. */
// TODO consider using a ref count for IRemoteControlDisplay requiring sync instead private boolean mNeedsPositionSync = false;
Cache for the current playback state using Session APIs.
/** * Cache for the current playback state using Session APIs. */
private PlaybackState mSessionPlaybackState = null;
Cache for metadata using Session APIs. This is re-initialized in apply().
/** * Cache for metadata using Session APIs. This is re-initialized in apply(). */
private MediaMetadata mMediaMetadata;
@hide Accessor to media button intent description (includes target component)
/** * @hide * Accessor to media button intent description (includes target component) */
public PendingIntent getRcMediaIntent() { return mRcMediaIntent; }
@hide Default value for the unique identifier
/** * @hide * Default value for the unique identifier */
public final static int RCSE_ID_UNREGISTERED = -1; // USE_SESSIONS private MediaSession.Callback mTransportListener = new MediaSession.Callback() { @Override public void onSeekTo(long pos) { RemoteControlClient.this.onSeekTo(mCurrentClientGenId, pos); } @Override public void onSetRating(Rating rating) { if ((mTransportControlFlags & FLAG_KEY_MEDIA_RATING) != 0) { onUpdateMetadata(mCurrentClientGenId, MetadataEditor.RATING_KEY_BY_USER, rating); } } }; //=========================================================== // Message handlers private void onSeekTo(int generationId, long timeMs) { synchronized (mCacheLock) { if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) { mPositionUpdateListener.onPlaybackPositionUpdate(timeMs); } } } private void onUpdateMetadata(int generationId, int key, Object value) { synchronized (mCacheLock) { if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) { mMetadataUpdateListener.onMetadataUpdate(key, value); } } } //=========================================================== // Internal utilities
Returns whether, for the given playback state, the playback position is expected to be changing.
Params:
  • playstate – the playback state to evaluate
Returns:true during any form of playback, false if it's not playing anything while in this playback state
/** * Returns whether, for the given playback state, the playback position is expected to * be changing. * @param playstate the playback state to evaluate * @return true during any form of playback, false if it's not playing anything while in this * playback state */
static boolean playbackPositionShouldMove(int playstate) { switch(playstate) { case PLAYSTATE_STOPPED: case PLAYSTATE_PAUSED: case PLAYSTATE_BUFFERING: case PLAYSTATE_ERROR: case PLAYSTATE_SKIPPING_FORWARDS: case PLAYSTATE_SKIPPING_BACKWARDS: return false; case PLAYSTATE_PLAYING: case PLAYSTATE_FAST_FORWARDING: case PLAYSTATE_REWINDING: default: return true; } }
Period for playback position drift checks, 15s when playing at 1x or slower.
/** * Period for playback position drift checks, 15s when playing at 1x or slower. */
private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000;
Minimum period for playback position drift checks, never more often when every 2s, when fast forwarding or rewinding.
/** * Minimum period for playback position drift checks, never more often when every 2s, when * fast forwarding or rewinding. */
private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000;
The value above which the difference between client-reported playback position and estimated position is considered a drift.
/** * The value above which the difference between client-reported playback position and * estimated position is considered a drift. */
private final static long POSITION_DRIFT_MAX_MS = 500;
Compute the period at which the estimated playback position should be compared against the actual playback position. Is a funciton of playback speed.
Params:
  • speed – 1.0f is normal playback speed
Returns:the period in ms
/** * Compute the period at which the estimated playback position should be compared against the * actual playback position. Is a funciton of playback speed. * @param speed 1.0f is normal playback speed * @return the period in ms */
private static long getCheckPeriodFromSpeed(float speed) { if (Math.abs(speed) <= 1.0f) { return POSITION_REFRESH_PERIOD_PLAYING_MS; } else { return Math.max((long)(POSITION_REFRESH_PERIOD_PLAYING_MS / Math.abs(speed)), POSITION_REFRESH_PERIOD_MIN_MS); } } }