Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media;
     18 
     19 import android.app.PendingIntent;
     20 import android.content.ComponentName;
     21 import android.content.Intent;
     22 import android.graphics.Bitmap;
     23 import android.media.session.MediaSessionLegacyHelper;
     24 import android.media.session.PlaybackState;
     25 import android.media.session.MediaSession;
     26 import android.os.Bundle;
     27 import android.os.Handler;
     28 import android.os.Looper;
     29 import android.os.Message;
     30 import android.os.ServiceManager;
     31 import android.os.SystemClock;
     32 import android.util.Log;
     33 
     34 import java.lang.IllegalArgumentException;
     35 
     36 /**
     37  * RemoteControlClient enables exposing information meant to be consumed by remote controls
     38  * capable of displaying metadata, artwork and media transport control buttons.
     39  *
     40  * <p>A remote control client object is associated with a media button event receiver. This
     41  * event receiver must have been previously registered with
     42  * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the
     43  * RemoteControlClient can be registered through
     44  * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
     45  *
     46  * <p>Here is an example of creating a RemoteControlClient instance after registering a media
     47  * button event receiver:
     48  * <pre>ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName());
     49  * AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
     50  * myAudioManager.registerMediaButtonEventReceiver(myEventReceiver);
     51  * // build the PendingIntent for the remote control client
     52  * Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
     53  * mediaButtonIntent.setComponent(myEventReceiver);
     54  * PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
     55  * // create and register the remote control client
     56  * RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
     57  * myAudioManager.registerRemoteControlClient(myRemoteControlClient);</pre>
     58  *
     59  * @deprecated Use {@link MediaSession} instead.
     60  */
     61 @Deprecated public class RemoteControlClient
     62 {
     63     private final static String TAG = "RemoteControlClient";
     64     private final static boolean DEBUG = false;
     65 
     66     /**
     67      * Playback state of a RemoteControlClient which is stopped.
     68      *
     69      * @see #setPlaybackState(int)
     70      */
     71     public final static int PLAYSTATE_STOPPED            = 1;
     72     /**
     73      * Playback state of a RemoteControlClient which is paused.
     74      *
     75      * @see #setPlaybackState(int)
     76      */
     77     public final static int PLAYSTATE_PAUSED             = 2;
     78     /**
     79      * Playback state of a RemoteControlClient which is playing media.
     80      *
     81      * @see #setPlaybackState(int)
     82      */
     83     public final static int PLAYSTATE_PLAYING            = 3;
     84     /**
     85      * Playback state of a RemoteControlClient which is fast forwarding in the media
     86      *    it is currently playing.
     87      *
     88      * @see #setPlaybackState(int)
     89      */
     90     public final static int PLAYSTATE_FAST_FORWARDING    = 4;
     91     /**
     92      * Playback state of a RemoteControlClient which is fast rewinding in the media
     93      *    it is currently playing.
     94      *
     95      * @see #setPlaybackState(int)
     96      */
     97     public final static int PLAYSTATE_REWINDING          = 5;
     98     /**
     99      * Playback state of a RemoteControlClient which is skipping to the next
    100      *    logical chapter (such as a song in a playlist) in the media it is currently playing.
    101      *
    102      * @see #setPlaybackState(int)
    103      */
    104     public final static int PLAYSTATE_SKIPPING_FORWARDS  = 6;
    105     /**
    106      * Playback state of a RemoteControlClient which is skipping back to the previous
    107      *    logical chapter (such as a song in a playlist) in the media it is currently playing.
    108      *
    109      * @see #setPlaybackState(int)
    110      */
    111     public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7;
    112     /**
    113      * Playback state of a RemoteControlClient which is buffering data to play before it can
    114      *    start or resume playback.
    115      *
    116      * @see #setPlaybackState(int)
    117      */
    118     public final static int PLAYSTATE_BUFFERING          = 8;
    119     /**
    120      * Playback state of a RemoteControlClient which cannot perform any playback related
    121      *    operation because of an internal error. Examples of such situations are no network
    122      *    connectivity when attempting to stream data from a server, or expired user credentials
    123      *    when trying to play subscription-based content.
    124      *
    125      * @see #setPlaybackState(int)
    126      */
    127     public final static int PLAYSTATE_ERROR              = 9;
    128     /**
    129      * @hide
    130      * The value of a playback state when none has been declared.
    131      * Intentionally hidden as an application shouldn't set such a playback state value.
    132      */
    133     public final static int PLAYSTATE_NONE               = 0;
    134 
    135     /**
    136      * @hide
    137      * The default playback type, "local", indicating the presentation of the media is happening on
    138      * the same device (e.g. a phone, a tablet) as where it is controlled from.
    139      */
    140     public final static int PLAYBACK_TYPE_LOCAL = 0;
    141     /**
    142      * @hide
    143      * A playback type indicating the presentation of the media is happening on
    144      * a different device (i.e. the remote device) than where it is controlled from.
    145      */
    146     public final static int PLAYBACK_TYPE_REMOTE = 1;
    147     private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL;
    148     private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE;
    149     /**
    150      * @hide
    151      * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled
    152      * from this object. An example of fixed playback volume is a remote player, playing over HDMI
    153      * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the
    154      * source.
    155      * @see #PLAYBACKINFO_VOLUME_HANDLING.
    156      */
    157     public final static int PLAYBACK_VOLUME_FIXED = 0;
    158     /**
    159      * @hide
    160      * Playback information indicating the playback volume is variable and can be controlled from
    161      * this object.
    162      * @see #PLAYBACKINFO_VOLUME_HANDLING.
    163      */
    164     public final static int PLAYBACK_VOLUME_VARIABLE = 1;
    165     /**
    166      * @hide (to be un-hidden)
    167      * The playback information value indicating the value of a given information type is invalid.
    168      * @see #PLAYBACKINFO_VOLUME_HANDLING.
    169      */
    170     public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE;
    171 
    172     /**
    173      * @hide
    174      * An unknown or invalid playback position value.
    175      */
    176     public final static long PLAYBACK_POSITION_INVALID = -1;
    177     /**
    178      * @hide
    179      * An invalid playback position value associated with the use of {@link #setPlaybackState(int)}
    180      * used to indicate that playback position will remain unknown.
    181      */
    182     public final static long PLAYBACK_POSITION_ALWAYS_UNKNOWN = 0x8019771980198300L;
    183     /**
    184      * @hide
    185      * The default playback speed, 1x.
    186      */
    187     public final static float PLAYBACK_SPEED_1X = 1.0f;
    188 
    189     //==========================================
    190     // Public keys for playback information
    191     /**
    192      * @hide
    193      * Playback information that defines the type of playback associated with this
    194      * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}.
    195      */
    196     public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1;
    197     /**
    198      * @hide
    199      * Playback information that defines at what volume the playback associated with this
    200      * RemoteControlClient is performed. This information is only used when the playback type is not
    201      * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
    202      */
    203     public final static int PLAYBACKINFO_VOLUME = 2;
    204     /**
    205      * @hide
    206      * Playback information that defines the maximum volume volume value that is supported
    207      * by the playback associated with this RemoteControlClient. This information is only used
    208      * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
    209      */
    210     public final static int PLAYBACKINFO_VOLUME_MAX = 3;
    211     /**
    212      * @hide
    213      * Playback information that defines how volume is handled for the presentation of the media.
    214      * @see #PLAYBACK_VOLUME_FIXED
    215      * @see #PLAYBACK_VOLUME_VARIABLE
    216      */
    217     public final static int PLAYBACKINFO_VOLUME_HANDLING = 4;
    218     /**
    219      * @hide
    220      * Playback information that defines over what stream type the media is presented.
    221      */
    222     public final static int PLAYBACKINFO_USES_STREAM = 5;
    223 
    224     //==========================================
    225     // Public flags for the supported transport control capabilities
    226     /**
    227      * Flag indicating a RemoteControlClient makes use of the "previous" media key.
    228      *
    229      * @see #setTransportControlFlags(int)
    230      * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS
    231      */
    232     public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
    233     /**
    234      * Flag indicating a RemoteControlClient makes use of the "rewind" media key.
    235      *
    236      * @see #setTransportControlFlags(int)
    237      * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND
    238      */
    239     public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
    240     /**
    241      * Flag indicating a RemoteControlClient makes use of the "play" media key.
    242      *
    243      * @see #setTransportControlFlags(int)
    244      * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY
    245      */
    246     public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
    247     /**
    248      * Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
    249      *
    250      * @see #setTransportControlFlags(int)
    251      * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE
    252      */
    253     public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
    254     /**
    255      * Flag indicating a RemoteControlClient makes use of the "pause" media key.
    256      *
    257      * @see #setTransportControlFlags(int)
    258      * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE
    259      */
    260     public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
    261     /**
    262      * Flag indicating a RemoteControlClient makes use of the "stop" media key.
    263      *
    264      * @see #setTransportControlFlags(int)
    265      * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP
    266      */
    267     public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
    268     /**
    269      * Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
    270      *
    271      * @see #setTransportControlFlags(int)
    272      * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD
    273      */
    274     public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
    275     /**
    276      * Flag indicating a RemoteControlClient makes use of the "next" media key.
    277      *
    278      * @see #setTransportControlFlags(int)
    279      * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT
    280      */
    281     public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
    282     /**
    283      * Flag indicating a RemoteControlClient can receive changes in the media playback position
    284      * through the {@link OnPlaybackPositionUpdateListener} interface. This flag must be set
    285      * in order for components that display the RemoteControlClient information, to display and
    286      * let the user control media playback position.
    287      * @see #setTransportControlFlags(int)
    288      * @see #setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener)
    289      * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener)
    290      */
    291     public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8;
    292     /**
    293      * Flag indicating a RemoteControlClient supports ratings.
    294      * This flag must be set in order for components that display the RemoteControlClient
    295      * information, to display ratings information, and, if ratings are declared editable
    296      * (by calling {@link MediaMetadataEditor#addEditableKey(int)} with the
    297      * {@link MediaMetadataEditor#RATING_KEY_BY_USER} key), it will enable the user to rate
    298      * the media, with values being received through the interface set with
    299      * {@link #setMetadataUpdateListener(OnMetadataUpdateListener)}.
    300      * @see #setTransportControlFlags(int)
    301      */
    302     public final static int FLAG_KEY_MEDIA_RATING = 1 << 9;
    303 
    304     /**
    305      * @hide
    306      * The flags for when no media keys are declared supported.
    307      * Intentionally hidden as an application shouldn't set the transport control flags
    308      *     to this value.
    309      */
    310     public final static int FLAGS_KEY_MEDIA_NONE = 0;
    311 
    312     /**
    313      * @hide
    314      * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested.
    315      */
    316     public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0;
    317     /**
    318      * @hide
    319      * Flag used to signal that the transport control buttons supported by the
    320      *     RemoteControlClient are requested.
    321      * This can for instance happen when playback is at the end of a playlist, and the "next"
    322      * operation is not supported anymore.
    323      */
    324     public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1;
    325     /**
    326      * @hide
    327      * Flag used to signal that the playback state of the RemoteControlClient is requested.
    328      */
    329     public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2;
    330     /**
    331      * @hide
    332      * Flag used to signal that the album art for the RemoteControlClient is requested.
    333      */
    334     public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
    335 
    336     private MediaSession mSession;
    337 
    338     /**
    339      * Class constructor.
    340      * @param mediaButtonIntent The intent that will be sent for the media button events sent
    341      *     by remote controls.
    342      *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
    343      *     action, and have a component that will handle the intent (set with
    344      *     {@link Intent#setComponent(ComponentName)}) registered with
    345      *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
    346      *     before this new RemoteControlClient can itself be registered with
    347      *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
    348      * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
    349      * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
    350      */
    351     public RemoteControlClient(PendingIntent mediaButtonIntent) {
    352         mRcMediaIntent = mediaButtonIntent;
    353 
    354         Looper looper;
    355         if ((looper = Looper.myLooper()) != null) {
    356             mEventHandler = new EventHandler(this, looper);
    357         } else if ((looper = Looper.getMainLooper()) != null) {
    358             mEventHandler = new EventHandler(this, looper);
    359         } else {
    360             mEventHandler = null;
    361             Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
    362         }
    363     }
    364 
    365     /**
    366      * Class constructor for a remote control client whose internal event handling
    367      * happens on a user-provided Looper.
    368      * @param mediaButtonIntent The intent that will be sent for the media button events sent
    369      *     by remote controls.
    370      *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
    371      *     action, and have a component that will handle the intent (set with
    372      *     {@link Intent#setComponent(ComponentName)}) registered with
    373      *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
    374      *     before this new RemoteControlClient can itself be registered with
    375      *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
    376      * @param looper The Looper running the event loop.
    377      * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
    378      * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
    379      */
    380     public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
    381         mRcMediaIntent = mediaButtonIntent;
    382 
    383         mEventHandler = new EventHandler(this, looper);
    384     }
    385 
    386     /**
    387      * @hide
    388      */
    389     public void registerWithSession(MediaSessionLegacyHelper helper) {
    390         helper.addRccListener(mRcMediaIntent, mTransportListener);
    391         mSession = helper.getSession(mRcMediaIntent);
    392         setTransportControlFlags(mTransportControlFlags);
    393     }
    394 
    395     /**
    396      * @hide
    397      */
    398     public void unregisterWithSession(MediaSessionLegacyHelper helper) {
    399         helper.removeRccListener(mRcMediaIntent);
    400         mSession = null;
    401     }
    402 
    403     /**
    404      * Get a {@link MediaSession} associated with this RCC. It will only have a
    405      * session while it is registered with
    406      * {@link AudioManager#registerRemoteControlClient}. The session returned
    407      * should not be modified directly by the application but may be used with
    408      * other APIs that require a session.
    409      *
    410      * @return A media session object or null.
    411      */
    412     public MediaSession getMediaSession() {
    413         return mSession;
    414     }
    415 
    416     /**
    417      * Class used to modify metadata in a {@link RemoteControlClient} object.
    418      * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor,
    419      * on which you set the metadata for the RemoteControlClient instance. Once all the information
    420      * has been set, use {@link #apply()} to make it the new metadata that should be displayed
    421      * for the associated client. Once the metadata has been "applied", you cannot reuse this
    422      * instance of the MetadataEditor.
    423      *
    424      * @deprecated Use {@link MediaMetadata} and {@link MediaSession} instead.
    425      */
    426     @Deprecated public class MetadataEditor extends MediaMetadataEditor {
    427 
    428         // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance
    429         private MetadataEditor() { }
    430         /**
    431          * @hide
    432          */
    433         public Object clone() throws CloneNotSupportedException {
    434             throw new CloneNotSupportedException();
    435         }
    436 
    437         /**
    438          * The metadata key for the content artwork / album art.
    439          */
    440         public final static int BITMAP_KEY_ARTWORK = 100;
    441 
    442         /**
    443          * @hide
    444          * TODO(jmtrivi) have lockscreen move to the new key name and remove
    445          */
    446         public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK;
    447 
    448         /**
    449          * Adds textual information to be displayed.
    450          * Note that none of the information added after {@link #apply()} has been called,
    451          * will be displayed.
    452          * @param key The identifier of a the metadata field to set. Valid values are
    453          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
    454          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
    455          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
    456          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
    457          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
    458          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
    459          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
    460          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
    461          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
    462          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
    463          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
    464          * @param value The text for the given key, or {@code null} to signify there is no valid
    465          *      information for the field.
    466          * @return Returns a reference to the same MetadataEditor object, so you can chain put
    467          *      calls together.
    468          */
    469         public synchronized MetadataEditor putString(int key, String value)
    470                 throws IllegalArgumentException {
    471             super.putString(key, value);
    472             if (mMetadataBuilder != null) {
    473                 // MediaMetadata supports all the same fields as MetadataEditor
    474                 String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
    475                 // But just in case, don't add things we don't understand
    476                 if (metadataKey != null) {
    477                     mMetadataBuilder.putText(metadataKey, value);
    478                 }
    479             }
    480 
    481             return this;
    482         }
    483 
    484         /**
    485          * Adds numerical information to be displayed.
    486          * Note that none of the information added after {@link #apply()} has been called,
    487          * will be displayed.
    488          * @param key the identifier of a the metadata field to set. Valid values are
    489          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
    490          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
    491          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
    492          *      expressed in milliseconds),
    493          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
    494          * @param value The long value for the given key
    495          * @return Returns a reference to the same MetadataEditor object, so you can chain put
    496          *      calls together.
    497          * @throws IllegalArgumentException
    498          */
    499         public synchronized MetadataEditor putLong(int key, long value)
    500                 throws IllegalArgumentException {
    501             super.putLong(key, value);
    502             if (mMetadataBuilder != null) {
    503                 // MediaMetadata supports all the same fields as MetadataEditor
    504                 String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
    505                 // But just in case, don't add things we don't understand
    506                 if (metadataKey != null) {
    507                     mMetadataBuilder.putLong(metadataKey, value);
    508                 }
    509             }
    510             return this;
    511         }
    512 
    513         /**
    514          * Sets the album / artwork picture to be displayed on the remote control.
    515          * @param key the identifier of the bitmap to set. The only valid value is
    516          *      {@link #BITMAP_KEY_ARTWORK}
    517          * @param bitmap The bitmap for the artwork, or null if there isn't any.
    518          * @return Returns a reference to the same MetadataEditor object, so you can chain put
    519          *      calls together.
    520          * @throws IllegalArgumentException
    521          * @see android.graphics.Bitmap
    522          */
    523         @Override
    524         public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
    525                 throws IllegalArgumentException {
    526             super.putBitmap(key, bitmap);
    527             if (mMetadataBuilder != null) {
    528                 // MediaMetadata supports all the same fields as MetadataEditor
    529                 String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
    530                 // But just in case, don't add things we don't understand
    531                 if (metadataKey != null) {
    532                     mMetadataBuilder.putBitmap(metadataKey, bitmap);
    533                 }
    534             }
    535             return this;
    536         }
    537 
    538         @Override
    539         public synchronized MetadataEditor putObject(int key, Object object)
    540                 throws IllegalArgumentException {
    541             super.putObject(key, object);
    542             if (mMetadataBuilder != null &&
    543                     (key == MediaMetadataEditor.RATING_KEY_BY_USER ||
    544                     key == MediaMetadataEditor.RATING_KEY_BY_OTHERS)) {
    545                 String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
    546                 if (metadataKey != null) {
    547                     mMetadataBuilder.putRating(metadataKey, (Rating) object);
    548                 }
    549             }
    550             return this;
    551         }
    552 
    553         /**
    554          * Clears all the metadata that has been set since the MetadataEditor instance was created
    555          * (with {@link RemoteControlClient#editMetadata(boolean)}).
    556          * Note that clearing the metadata doesn't reset the editable keys
    557          * (use {@link MediaMetadataEditor#removeEditableKeys()} instead).
    558          */
    559         @Override
    560         public synchronized void clear() {
    561             super.clear();
    562         }
    563 
    564         /**
    565          * Associates all the metadata that has been set since the MetadataEditor instance was
    566          *     created with {@link RemoteControlClient#editMetadata(boolean)}, or since
    567          *     {@link #clear()} was called, with the RemoteControlClient. Once "applied",
    568          *     this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
    569          */
    570         public synchronized void apply() {
    571             if (mApplied) {
    572                 Log.e(TAG, "Can't apply a previously applied MetadataEditor");
    573                 return;
    574             }
    575             synchronized (mCacheLock) {
    576                 // Still build the old metadata so when creating a new editor
    577                 // you get the expected values.
    578                 // assign the edited data
    579                 mMetadata = new Bundle(mEditorMetadata);
    580                 // add the information about editable keys
    581                 mMetadata.putLong(String.valueOf(KEY_EDITABLE_MASK), mEditableKeys);
    582                 if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) {
    583                     mOriginalArtwork.recycle();
    584                 }
    585                 mOriginalArtwork = mEditorArtwork;
    586                 mEditorArtwork = null;
    587 
    588                 // USE_SESSIONS
    589                 if (mSession != null && mMetadataBuilder != null) {
    590                     mMediaMetadata = mMetadataBuilder.build();
    591                     mSession.setMetadata(mMediaMetadata);
    592                 }
    593                 mApplied = true;
    594             }
    595         }
    596     }
    597 
    598     /**
    599      * Creates a {@link MetadataEditor}.
    600      * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
    601      *     was previously applied to the RemoteControlClient, or true if it is to be created empty.
    602      * @return a new MetadataEditor instance.
    603      */
    604     public MetadataEditor editMetadata(boolean startEmpty) {
    605         MetadataEditor editor = new MetadataEditor();
    606         if (startEmpty) {
    607             editor.mEditorMetadata = new Bundle();
    608             editor.mEditorArtwork = null;
    609             editor.mMetadataChanged = true;
    610             editor.mArtworkChanged = true;
    611             editor.mEditableKeys = 0;
    612         } else {
    613             editor.mEditorMetadata = new Bundle(mMetadata);
    614             editor.mEditorArtwork = mOriginalArtwork;
    615             editor.mMetadataChanged = false;
    616             editor.mArtworkChanged = false;
    617         }
    618         // USE_SESSIONS
    619         if (startEmpty || mMediaMetadata == null) {
    620             editor.mMetadataBuilder = new MediaMetadata.Builder();
    621         } else {
    622             editor.mMetadataBuilder = new MediaMetadata.Builder(mMediaMetadata);
    623         }
    624         return editor;
    625     }
    626 
    627     /**
    628      * Sets the current playback state.
    629      * @param state The current playback state, one of the following values:
    630      *       {@link #PLAYSTATE_STOPPED},
    631      *       {@link #PLAYSTATE_PAUSED},
    632      *       {@link #PLAYSTATE_PLAYING},
    633      *       {@link #PLAYSTATE_FAST_FORWARDING},
    634      *       {@link #PLAYSTATE_REWINDING},
    635      *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
    636      *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
    637      *       {@link #PLAYSTATE_BUFFERING},
    638      *       {@link #PLAYSTATE_ERROR}.
    639      */
    640     public void setPlaybackState(int state) {
    641         setPlaybackStateInt(state, PLAYBACK_POSITION_ALWAYS_UNKNOWN, PLAYBACK_SPEED_1X,
    642                 false /* legacy API, converting to method with position and speed */);
    643     }
    644 
    645     /**
    646      * Sets the current playback state and the matching media position for the current playback
    647      *   speed.
    648      * @param state The current playback state, one of the following values:
    649      *       {@link #PLAYSTATE_STOPPED},
    650      *       {@link #PLAYSTATE_PAUSED},
    651      *       {@link #PLAYSTATE_PLAYING},
    652      *       {@link #PLAYSTATE_FAST_FORWARDING},
    653      *       {@link #PLAYSTATE_REWINDING},
    654      *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
    655      *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
    656      *       {@link #PLAYSTATE_BUFFERING},
    657      *       {@link #PLAYSTATE_ERROR}.
    658      * @param timeInMs a 0 or positive value for the current media position expressed in ms
    659      *    (same unit as for when sending the media duration, if applicable, with
    660      *    {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the
    661      *    {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not
    662      *    known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state
    663      *    is {@link #PLAYSTATE_BUFFERING} and nothing had played yet).
    664      * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback,
    665      *    2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is
    666      *    playing (e.g. when state is {@link #PLAYSTATE_ERROR}).
    667      */
    668     public void setPlaybackState(int state, long timeInMs, float playbackSpeed) {
    669         setPlaybackStateInt(state, timeInMs, playbackSpeed, true);
    670     }
    671 
    672     private void setPlaybackStateInt(int state, long timeInMs, float playbackSpeed,
    673             boolean hasPosition) {
    674         synchronized(mCacheLock) {
    675             if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs)
    676                     || (mPlaybackSpeed != playbackSpeed)) {
    677                 // store locally
    678                 mPlaybackState = state;
    679                 // distinguish between an application not knowing the current playback position
    680                 // at the moment and an application using the API where only the playback state
    681                 // is passed, not the playback position.
    682                 if (hasPosition) {
    683                     if (timeInMs < 0) {
    684                         mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
    685                     } else {
    686                         mPlaybackPositionMs = timeInMs;
    687                     }
    688                 } else {
    689                     mPlaybackPositionMs = PLAYBACK_POSITION_ALWAYS_UNKNOWN;
    690                 }
    691                 mPlaybackSpeed = playbackSpeed;
    692                 // keep track of when the state change occurred
    693                 mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime();
    694 
    695                 // USE_SESSIONS
    696                 if (mSession != null) {
    697                     int pbState = PlaybackState.getStateFromRccState(state);
    698                     long position = hasPosition ? mPlaybackPositionMs
    699                             : PlaybackState.PLAYBACK_POSITION_UNKNOWN;
    700 
    701                     PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
    702                     bob.setState(pbState, position, playbackSpeed, SystemClock.elapsedRealtime());
    703                     bob.setErrorMessage(null);
    704                     mSessionPlaybackState = bob.build();
    705                     mSession.setPlaybackState(mSessionPlaybackState);
    706                 }
    707             }
    708         }
    709     }
    710 
    711     // TODO investigate if we still need position drift checking
    712     private void onPositionDriftCheck() {
    713         if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); }
    714         synchronized(mCacheLock) {
    715             if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) {
    716                 return;
    717             }
    718             if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) {
    719                 if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); }
    720                 return;
    721             }
    722             long estPos = mPlaybackPositionMs + (long)
    723                     ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed);
    724             long actPos = mPositionProvider.onGetPlaybackPosition();
    725             if (actPos >= 0) {
    726                 if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) {
    727                     // drift happened, report the new position
    728                     if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +"  est=" +estPos); }
    729                     setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed);
    730                 } else {
    731                     if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +"  est=" + estPos); }
    732                     // no drift, schedule the next drift check
    733                     mEventHandler.sendMessageDelayed(
    734                             mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
    735                             getCheckPeriodFromSpeed(mPlaybackSpeed));
    736                 }
    737             } else {
    738                 // invalid position (negative value), can't check for drift
    739                 mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK);
    740             }
    741         }
    742     }
    743 
    744     /**
    745      * Sets the flags for the media transport control buttons that this client supports.
    746      * @param transportControlFlags A combination of the following flags:
    747      *      {@link #FLAG_KEY_MEDIA_PREVIOUS},
    748      *      {@link #FLAG_KEY_MEDIA_REWIND},
    749      *      {@link #FLAG_KEY_MEDIA_PLAY},
    750      *      {@link #FLAG_KEY_MEDIA_PLAY_PAUSE},
    751      *      {@link #FLAG_KEY_MEDIA_PAUSE},
    752      *      {@link #FLAG_KEY_MEDIA_STOP},
    753      *      {@link #FLAG_KEY_MEDIA_FAST_FORWARD},
    754      *      {@link #FLAG_KEY_MEDIA_NEXT},
    755      *      {@link #FLAG_KEY_MEDIA_POSITION_UPDATE},
    756      *      {@link #FLAG_KEY_MEDIA_RATING}.
    757      */
    758     public void setTransportControlFlags(int transportControlFlags) {
    759         synchronized(mCacheLock) {
    760             // store locally
    761             mTransportControlFlags = transportControlFlags;
    762 
    763             // USE_SESSIONS
    764             if (mSession != null) {
    765                 PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
    766                 bob.setActions(
    767                         PlaybackState.getActionsFromRccControlFlags(transportControlFlags));
    768                 mSessionPlaybackState = bob.build();
    769                 mSession.setPlaybackState(mSessionPlaybackState);
    770             }
    771         }
    772     }
    773 
    774     /**
    775      * Interface definition for a callback to be invoked when one of the metadata values has
    776      * been updated.
    777      * Implement this interface to receive metadata updates after registering your listener
    778      * through {@link RemoteControlClient#setMetadataUpdateListener(OnMetadataUpdateListener)}.
    779      */
    780     public interface OnMetadataUpdateListener {
    781         /**
    782          * Called on the implementer to notify that the metadata field for the given key has
    783          * been updated to the new value.
    784          * @param key the identifier of the updated metadata field.
    785          * @param newValue the Object storing the new value for the key.
    786          */
    787         public abstract void onMetadataUpdate(int key, Object newValue);
    788     }
    789 
    790     /**
    791      * Sets the listener to be called whenever the metadata is updated.
    792      * New metadata values will be received in the same thread as the one in which
    793      * RemoteControlClient was created.
    794      * @param l the metadata update listener
    795      */
    796     public void setMetadataUpdateListener(OnMetadataUpdateListener l) {
    797         synchronized(mCacheLock) {
    798             mMetadataUpdateListener = l;
    799         }
    800     }
    801 
    802 
    803     /**
    804      * Interface definition for a callback to be invoked when the media playback position is
    805      * requested to be updated.
    806      * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE
    807      */
    808     public interface OnPlaybackPositionUpdateListener {
    809         /**
    810          * Called on the implementer to notify it that the playback head should be set at the given
    811          * position. If the position can be changed from its current value, the implementor of
    812          * the interface must also update the playback position using
    813          * {@link #setPlaybackState(int, long, float)} to reflect the actual new
    814          * position being used, regardless of whether it differs from the requested position.
    815          * Failure to do so would cause the system to not know the new actual playback position,
    816          * and user interface components would fail to show the user where playback resumed after
    817          * the position was updated.
    818          * @param newPositionMs the new requested position in the current media, expressed in ms.
    819          */
    820         void onPlaybackPositionUpdate(long newPositionMs);
    821     }
    822 
    823     /**
    824      * Interface definition for a callback to be invoked when the media playback position is
    825      * queried.
    826      * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE
    827      */
    828     public interface OnGetPlaybackPositionListener {
    829         /**
    830          * Called on the implementer of the interface to query the current playback position.
    831          * @return a negative value if the current playback position (or the last valid playback
    832          *     position) is not known, or a zero or positive value expressed in ms indicating the
    833          *     current position, or the last valid known position.
    834          */
    835         long onGetPlaybackPosition();
    836     }
    837 
    838     /**
    839      * Sets the listener to be called whenever the media playback position is requested
    840      * to be updated.
    841      * Notifications will be received in the same thread as the one in which RemoteControlClient
    842      * was created.
    843      * @param l the position update listener to be called
    844      */
    845     public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) {
    846         synchronized(mCacheLock) {
    847             mPositionUpdateListener = l;
    848         }
    849     }
    850 
    851     /**
    852      * Sets the listener to be called whenever the media current playback position is needed.
    853      * Queries will be received in the same thread as the one in which RemoteControlClient
    854      * was created.
    855      * @param l the listener to be called to retrieve the playback position
    856      */
    857     public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) {
    858         synchronized(mCacheLock) {
    859             mPositionProvider = l;
    860             if ((mPositionProvider != null) && (mEventHandler != null)
    861                     && playbackPositionShouldMove(mPlaybackState)) {
    862                 // playback position is already moving, but now we have a position provider,
    863                 // so schedule a drift check right now
    864                 mEventHandler.sendMessageDelayed(
    865                         mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
    866                         0 /*check now*/);
    867             }
    868         }
    869     }
    870 
    871     /**
    872      * @hide
    873      * Flag to reflect that the application controlling this RemoteControlClient sends playback
    874      * position updates. The playback position being "readable" is considered from the application's
    875      * point of view.
    876      */
    877     public static int MEDIA_POSITION_READABLE = 1 << 0;
    878     /**
    879      * @hide
    880      * Flag to reflect that the application controlling this RemoteControlClient can receive
    881      * playback position updates. The playback position being "writable"
    882      * is considered from the application's point of view.
    883      */
    884     public static int MEDIA_POSITION_WRITABLE = 1 << 1;
    885 
    886     /** @hide */
    887     public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
    888     /** @hide */
    889     // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC]
    890     public final static int DEFAULT_PLAYBACK_VOLUME = 15;
    891 
    892     /**
    893      * Lock for all cached data
    894      */
    895     private final Object mCacheLock = new Object();
    896     /**
    897      * Cache for the playback state.
    898      * Access synchronized on mCacheLock
    899      */
    900     private int mPlaybackState = PLAYSTATE_NONE;
    901     /**
    902      * Time of last play state change
    903      * Access synchronized on mCacheLock
    904      */
    905     private long mPlaybackStateChangeTimeMs = 0;
    906     /**
    907      * Last playback position in ms reported by the user
    908      */
    909     private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
    910     /**
    911      * Last playback speed reported by the user
    912      */
    913     private float mPlaybackSpeed = PLAYBACK_SPEED_1X;
    914     /**
    915      * Cache for the artwork bitmap.
    916      * Access synchronized on mCacheLock
    917      * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be
    918      * accessed to be resized, in which case a copy will be made. This would add overhead in
    919      * Bundle operations.
    920      */
    921     private Bitmap mOriginalArtwork;
    922     /**
    923      * Cache for the transport control mask.
    924      * Access synchronized on mCacheLock
    925      */
    926     private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE;
    927     /**
    928      * Cache for the metadata strings.
    929      * Access synchronized on mCacheLock
    930      * This is re-initialized in apply() and so cannot be final.
    931      */
    932     private Bundle mMetadata = new Bundle();
    933     /**
    934      * Listener registered by user of RemoteControlClient to receive requests for playback position
    935      * update requests.
    936      */
    937     private OnPlaybackPositionUpdateListener mPositionUpdateListener;
    938     /**
    939      * Provider registered by user of RemoteControlClient to provide the current playback position.
    940      */
    941     private OnGetPlaybackPositionListener mPositionProvider;
    942     /**
    943      * Listener registered by user of RemoteControlClient to receive edit changes to metadata
    944      * it exposes.
    945      */
    946     private OnMetadataUpdateListener mMetadataUpdateListener;
    947     /**
    948      * The current remote control client generation ID across the system, as known by this object
    949      */
    950     private int mCurrentClientGenId = -1;
    951 
    952     /**
    953      * The media button intent description associated with this remote control client
    954      * (can / should include target component for intent handling, used when persisting media
    955      *    button event receiver across reboots).
    956      */
    957     private final PendingIntent mRcMediaIntent;
    958 
    959     /**
    960      * Reflects whether any "plugged in" IRemoteControlDisplay has mWantsPositonSync set to true.
    961      */
    962     // TODO consider using a ref count for IRemoteControlDisplay requiring sync instead
    963     private boolean mNeedsPositionSync = false;
    964 
    965     /**
    966      * Cache for the current playback state using Session APIs.
    967      */
    968     private PlaybackState mSessionPlaybackState = null;
    969 
    970     /**
    971      * Cache for metadata using Session APIs. This is re-initialized in apply().
    972      */
    973     private MediaMetadata mMediaMetadata;
    974 
    975     /**
    976      * @hide
    977      * Accessor to media button intent description (includes target component)
    978      */
    979     public PendingIntent getRcMediaIntent() {
    980         return mRcMediaIntent;
    981     }
    982 
    983     /**
    984      * @hide
    985      * Default value for the unique identifier
    986      */
    987     public final static int RCSE_ID_UNREGISTERED = -1;
    988 
    989     // USE_SESSIONS
    990     private MediaSession.Callback mTransportListener = new MediaSession.Callback() {
    991 
    992         @Override
    993         public void onSeekTo(long pos) {
    994             RemoteControlClient.this.onSeekTo(mCurrentClientGenId, pos);
    995         }
    996 
    997         @Override
    998         public void onSetRating(Rating rating) {
    999             if ((mTransportControlFlags & FLAG_KEY_MEDIA_RATING) != 0) {
   1000                 onUpdateMetadata(mCurrentClientGenId, MetadataEditor.RATING_KEY_BY_USER, rating);
   1001             }
   1002         }
   1003     };
   1004 
   1005     private EventHandler mEventHandler;
   1006     private final static int MSG_POSITION_DRIFT_CHECK = 11;
   1007 
   1008     private class EventHandler extends Handler {
   1009         public EventHandler(RemoteControlClient rcc, Looper looper) {
   1010             super(looper);
   1011         }
   1012 
   1013         @Override
   1014         public void handleMessage(Message msg) {
   1015             switch(msg.what) {
   1016                 case MSG_POSITION_DRIFT_CHECK:
   1017                     onPositionDriftCheck();
   1018                     break;
   1019                 default:
   1020                     Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
   1021             }
   1022         }
   1023     }
   1024 
   1025     //===========================================================
   1026     // Message handlers
   1027 
   1028     private void onSeekTo(int generationId, long timeMs) {
   1029         synchronized (mCacheLock) {
   1030             if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) {
   1031                 mPositionUpdateListener.onPlaybackPositionUpdate(timeMs);
   1032             }
   1033         }
   1034     }
   1035 
   1036     private void onUpdateMetadata(int generationId, int key, Object value) {
   1037         synchronized (mCacheLock) {
   1038             if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) {
   1039                 mMetadataUpdateListener.onMetadataUpdate(key, value);
   1040             }
   1041         }
   1042     }
   1043 
   1044     //===========================================================
   1045     // Internal utilities
   1046 
   1047     /**
   1048      * Returns whether, for the given playback state, the playback position is expected to
   1049      * be changing.
   1050      * @param playstate the playback state to evaluate
   1051      * @return true during any form of playback, false if it's not playing anything while in this
   1052      *     playback state
   1053      */
   1054     static boolean playbackPositionShouldMove(int playstate) {
   1055         switch(playstate) {
   1056             case PLAYSTATE_STOPPED:
   1057             case PLAYSTATE_PAUSED:
   1058             case PLAYSTATE_BUFFERING:
   1059             case PLAYSTATE_ERROR:
   1060             case PLAYSTATE_SKIPPING_FORWARDS:
   1061             case PLAYSTATE_SKIPPING_BACKWARDS:
   1062                 return false;
   1063             case PLAYSTATE_PLAYING:
   1064             case PLAYSTATE_FAST_FORWARDING:
   1065             case PLAYSTATE_REWINDING:
   1066             default:
   1067                 return true;
   1068         }
   1069     }
   1070 
   1071     /**
   1072      * Period for playback position drift checks, 15s when playing at 1x or slower.
   1073      */
   1074     private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000;
   1075     /**
   1076      * Minimum period for playback position drift checks, never more often when every 2s, when
   1077      * fast forwarding or rewinding.
   1078      */
   1079     private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000;
   1080     /**
   1081      * The value above which the difference between client-reported playback position and
   1082      * estimated position is considered a drift.
   1083      */
   1084     private final static long POSITION_DRIFT_MAX_MS = 500;
   1085     /**
   1086      * Compute the period at which the estimated playback position should be compared against the
   1087      * actual playback position. Is a funciton of playback speed.
   1088      * @param speed 1.0f is normal playback speed
   1089      * @return the period in ms
   1090      */
   1091     private static long getCheckPeriodFromSpeed(float speed) {
   1092         if (Math.abs(speed) <= 1.0f) {
   1093             return POSITION_REFRESH_PERIOD_PLAYING_MS;
   1094         } else {
   1095             return Math.max((long)(POSITION_REFRESH_PERIOD_PLAYING_MS / Math.abs(speed)),
   1096                     POSITION_REFRESH_PERIOD_MIN_MS);
   1097         }
   1098     }
   1099 }
   1100