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