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.Intent;
22import android.graphics.Bitmap;
23import android.media.session.MediaSessionLegacyHelper;
24import android.media.session.PlaybackState;
25import android.media.session.MediaSession;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.Looper;
29import android.os.Message;
30import android.os.ServiceManager;
31import android.os.SystemClock;
32import android.util.Log;
33
34import 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