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.MediaMetadataRetriever;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.Looper;
32import android.os.Message;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.os.SystemClock;
36import android.util.Log;
37
38import java.lang.IllegalArgumentException;
39
40/**
41 * RemoteControlClient enables exposing information meant to be consumed by remote controls
42 * capable of displaying metadata, artwork and media transport control buttons.
43 *
44 * <p>A remote control client object is associated with a media button event receiver. This
45 * event receiver must have been previously registered with
46 * {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)} before the
47 * RemoteControlClient can be registered through
48 * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
49 *
50 * <p>Here is an example of creating a RemoteControlClient instance after registering a media
51 * button event receiver:
52 * <pre>ComponentName myEventReceiver = new ComponentName(getPackageName(), MyRemoteControlEventReceiver.class.getName());
53 * AudioManager myAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
54 * myAudioManager.registerMediaButtonEventReceiver(myEventReceiver);
55 * // build the PendingIntent for the remote control client
56 * Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
57 * mediaButtonIntent.setComponent(myEventReceiver);
58 * PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
59 * // create and register the remote control client
60 * RemoteControlClient myRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
61 * myAudioManager.registerRemoteControlClient(myRemoteControlClient);</pre>
62 */
63public class RemoteControlClient
64{
65    private final static String TAG = "RemoteControlClient";
66
67    /**
68     * Playback state of a RemoteControlClient which is stopped.
69     *
70     * @see #setPlaybackState(int)
71     */
72    public final static int PLAYSTATE_STOPPED            = 1;
73    /**
74     * Playback state of a RemoteControlClient which is paused.
75     *
76     * @see #setPlaybackState(int)
77     */
78    public final static int PLAYSTATE_PAUSED             = 2;
79    /**
80     * Playback state of a RemoteControlClient which is playing media.
81     *
82     * @see #setPlaybackState(int)
83     */
84    public final static int PLAYSTATE_PLAYING            = 3;
85    /**
86     * Playback state of a RemoteControlClient which is fast forwarding in the media
87     *    it is currently playing.
88     *
89     * @see #setPlaybackState(int)
90     */
91    public final static int PLAYSTATE_FAST_FORWARDING    = 4;
92    /**
93     * Playback state of a RemoteControlClient which is fast rewinding in the media
94     *    it is currently playing.
95     *
96     * @see #setPlaybackState(int)
97     */
98    public final static int PLAYSTATE_REWINDING          = 5;
99    /**
100     * Playback state of a RemoteControlClient which is skipping to the next
101     *    logical chapter (such as a song in a playlist) in the media it is currently playing.
102     *
103     * @see #setPlaybackState(int)
104     */
105    public final static int PLAYSTATE_SKIPPING_FORWARDS  = 6;
106    /**
107     * Playback state of a RemoteControlClient which is skipping back to the previous
108     *    logical chapter (such as a song in a playlist) in the media it is currently playing.
109     *
110     * @see #setPlaybackState(int)
111     */
112    public final static int PLAYSTATE_SKIPPING_BACKWARDS = 7;
113    /**
114     * Playback state of a RemoteControlClient which is buffering data to play before it can
115     *    start or resume playback.
116     *
117     * @see #setPlaybackState(int)
118     */
119    public final static int PLAYSTATE_BUFFERING          = 8;
120    /**
121     * Playback state of a RemoteControlClient which cannot perform any playback related
122     *    operation because of an internal error. Examples of such situations are no network
123     *    connectivity when attempting to stream data from a server, or expired user credentials
124     *    when trying to play subscription-based content.
125     *
126     * @see #setPlaybackState(int)
127     */
128    public final static int PLAYSTATE_ERROR              = 9;
129    /**
130     * @hide
131     * The value of a playback state when none has been declared.
132     * Intentionally hidden as an application shouldn't set such a playback state value.
133     */
134    public final static int PLAYSTATE_NONE               = 0;
135
136    /**
137     * @hide
138     * The default playback type, "local", indicating the presentation of the media is happening on
139     * the same device (e.g. a phone, a tablet) as where it is controlled from.
140     */
141    public final static int PLAYBACK_TYPE_LOCAL = 0;
142    /**
143     * @hide
144     * A playback type indicating the presentation of the media is happening on
145     * a different device (i.e. the remote device) than where it is controlled from.
146     */
147    public final static int PLAYBACK_TYPE_REMOTE = 1;
148    private final static int PLAYBACK_TYPE_MIN = PLAYBACK_TYPE_LOCAL;
149    private final static int PLAYBACK_TYPE_MAX = PLAYBACK_TYPE_REMOTE;
150    /**
151     * @hide
152     * Playback information indicating the playback volume is fixed, i.e. it cannot be controlled
153     * from this object. An example of fixed playback volume is a remote player, playing over HDMI
154     * where the user prefer to control the volume on the HDMI sink, rather than attenuate at the
155     * source.
156     * @see #PLAYBACKINFO_VOLUME_HANDLING.
157     */
158    public final static int PLAYBACK_VOLUME_FIXED = 0;
159    /**
160     * @hide
161     * Playback information indicating the playback volume is variable and can be controlled from
162     * this object.
163     * @see #PLAYBACKINFO_VOLUME_HANDLING.
164     */
165    public final static int PLAYBACK_VOLUME_VARIABLE = 1;
166    /**
167     * @hide (to be un-hidden)
168     * The playback information value indicating the value of a given information type is invalid.
169     * @see #PLAYBACKINFO_VOLUME_HANDLING.
170     */
171    public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE;
172
173    //==========================================
174    // Public keys for playback information
175    /**
176     * @hide
177     * Playback information that defines the type of playback associated with this
178     * RemoteControlClient. See {@link #PLAYBACK_TYPE_LOCAL} and {@link #PLAYBACK_TYPE_REMOTE}.
179     */
180    public final static int PLAYBACKINFO_PLAYBACK_TYPE = 1;
181    /**
182     * @hide
183     * Playback information that defines at what volume the playback associated with this
184     * RemoteControlClient is performed. This information is only used when the playback type is not
185     * local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
186     */
187    public final static int PLAYBACKINFO_VOLUME = 2;
188    /**
189     * @hide
190     * Playback information that defines the maximum volume volume value that is supported
191     * by the playback associated with this RemoteControlClient. This information is only used
192     * when the playback type is not local (see {@link #PLAYBACKINFO_PLAYBACK_TYPE}).
193     */
194    public final static int PLAYBACKINFO_VOLUME_MAX = 3;
195    /**
196     * @hide
197     * Playback information that defines how volume is handled for the presentation of the media.
198     * @see #PLAYBACK_VOLUME_FIXED
199     * @see #PLAYBACK_VOLUME_VARIABLE
200     */
201    public final static int PLAYBACKINFO_VOLUME_HANDLING = 4;
202    /**
203     * @hide
204     * Playback information that defines over what stream type the media is presented.
205     */
206    public final static int PLAYBACKINFO_USES_STREAM = 5;
207
208    //==========================================
209    // Private keys for playback information
210    /**
211     * @hide
212     * Used internally to relay playback state (set by the application with
213     * {@link #setPlaybackState(int)}) to AudioService
214     */
215    public final static int PLAYBACKINFO_PLAYSTATE = 255;
216
217
218    /**
219     * Flag indicating a RemoteControlClient makes use of the "previous" media key.
220     *
221     * @see #setTransportControlFlags(int)
222     * @see android.view.KeyEvent#KEYCODE_MEDIA_PREVIOUS
223     */
224    public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
225    /**
226     * Flag indicating a RemoteControlClient makes use of the "rewind" media key.
227     *
228     * @see #setTransportControlFlags(int)
229     * @see android.view.KeyEvent#KEYCODE_MEDIA_REWIND
230     */
231    public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
232    /**
233     * Flag indicating a RemoteControlClient makes use of the "play" media key.
234     *
235     * @see #setTransportControlFlags(int)
236     * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY
237     */
238    public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
239    /**
240     * Flag indicating a RemoteControlClient makes use of the "play/pause" media key.
241     *
242     * @see #setTransportControlFlags(int)
243     * @see android.view.KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE
244     */
245    public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
246    /**
247     * Flag indicating a RemoteControlClient makes use of the "pause" media key.
248     *
249     * @see #setTransportControlFlags(int)
250     * @see android.view.KeyEvent#KEYCODE_MEDIA_PAUSE
251     */
252    public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
253    /**
254     * Flag indicating a RemoteControlClient makes use of the "stop" media key.
255     *
256     * @see #setTransportControlFlags(int)
257     * @see android.view.KeyEvent#KEYCODE_MEDIA_STOP
258     */
259    public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
260    /**
261     * Flag indicating a RemoteControlClient makes use of the "fast forward" media key.
262     *
263     * @see #setTransportControlFlags(int)
264     * @see android.view.KeyEvent#KEYCODE_MEDIA_FAST_FORWARD
265     */
266    public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
267    /**
268     * Flag indicating a RemoteControlClient makes use of the "next" media key.
269     *
270     * @see #setTransportControlFlags(int)
271     * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT
272     */
273    public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
274
275    /**
276     * @hide
277     * The flags for when no media keys are declared supported.
278     * Intentionally hidden as an application shouldn't set the transport control flags
279     *     to this value.
280     */
281    public final static int FLAGS_KEY_MEDIA_NONE = 0;
282
283    /**
284     * @hide
285     * Flag used to signal some type of metadata exposed by the RemoteControlClient is requested.
286     */
287    public final static int FLAG_INFORMATION_REQUEST_METADATA = 1 << 0;
288    /**
289     * @hide
290     * Flag used to signal that the transport control buttons supported by the
291     *     RemoteControlClient are requested.
292     * This can for instance happen when playback is at the end of a playlist, and the "next"
293     * operation is not supported anymore.
294     */
295    public final static int FLAG_INFORMATION_REQUEST_KEY_MEDIA = 1 << 1;
296    /**
297     * @hide
298     * Flag used to signal that the playback state of the RemoteControlClient is requested.
299     */
300    public final static int FLAG_INFORMATION_REQUEST_PLAYSTATE = 1 << 2;
301    /**
302     * @hide
303     * Flag used to signal that the album art for the RemoteControlClient is requested.
304     */
305    public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
306
307    /**
308     * Class constructor.
309     * @param mediaButtonIntent The intent that will be sent for the media button events sent
310     *     by remote controls.
311     *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
312     *     action, and have a component that will handle the intent (set with
313     *     {@link Intent#setComponent(ComponentName)}) registered with
314     *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
315     *     before this new RemoteControlClient can itself be registered with
316     *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
317     * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
318     * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
319     */
320    public RemoteControlClient(PendingIntent mediaButtonIntent) {
321        mRcMediaIntent = mediaButtonIntent;
322
323        Looper looper;
324        if ((looper = Looper.myLooper()) != null) {
325            mEventHandler = new EventHandler(this, looper);
326        } else if ((looper = Looper.getMainLooper()) != null) {
327            mEventHandler = new EventHandler(this, looper);
328        } else {
329            mEventHandler = null;
330            Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
331        }
332    }
333
334    /**
335     * Class constructor for a remote control client whose internal event handling
336     * happens on a user-provided Looper.
337     * @param mediaButtonIntent The intent that will be sent for the media button events sent
338     *     by remote controls.
339     *     This intent needs to have been constructed with the {@link Intent#ACTION_MEDIA_BUTTON}
340     *     action, and have a component that will handle the intent (set with
341     *     {@link Intent#setComponent(ComponentName)}) registered with
342     *     {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
343     *     before this new RemoteControlClient can itself be registered with
344     *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
345     * @param looper The Looper running the event loop.
346     * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
347     * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
348     */
349    public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
350        mRcMediaIntent = mediaButtonIntent;
351
352        mEventHandler = new EventHandler(this, looper);
353    }
354
355    private static final int[] METADATA_KEYS_TYPE_STRING = {
356        MediaMetadataRetriever.METADATA_KEY_ALBUM,
357        MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
358        MediaMetadataRetriever.METADATA_KEY_TITLE,
359        MediaMetadataRetriever.METADATA_KEY_ARTIST,
360        MediaMetadataRetriever.METADATA_KEY_AUTHOR,
361        MediaMetadataRetriever.METADATA_KEY_COMPILATION,
362        MediaMetadataRetriever.METADATA_KEY_COMPOSER,
363        MediaMetadataRetriever.METADATA_KEY_DATE,
364        MediaMetadataRetriever.METADATA_KEY_GENRE,
365        MediaMetadataRetriever.METADATA_KEY_TITLE,
366        MediaMetadataRetriever.METADATA_KEY_WRITER };
367    private static final int[] METADATA_KEYS_TYPE_LONG = {
368        MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
369        MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
370        MediaMetadataRetriever.METADATA_KEY_DURATION };
371
372    /**
373     * Class used to modify metadata in a {@link RemoteControlClient} object.
374     * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor,
375     * on which you set the metadata for the RemoteControlClient instance. Once all the information
376     * has been set, use {@link #apply()} to make it the new metadata that should be displayed
377     * for the associated client. Once the metadata has been "applied", you cannot reuse this
378     * instance of the MetadataEditor.
379     */
380    public class MetadataEditor {
381        /**
382         * @hide
383         */
384        protected boolean mMetadataChanged;
385        /**
386         * @hide
387         */
388        protected boolean mArtworkChanged;
389        /**
390         * @hide
391         */
392        protected Bitmap mEditorArtwork;
393        /**
394         * @hide
395         */
396        protected Bundle mEditorMetadata;
397        private boolean mApplied = false;
398
399        // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance
400        private MetadataEditor() { }
401        /**
402         * @hide
403         */
404        public Object clone() throws CloneNotSupportedException {
405            throw new CloneNotSupportedException();
406        }
407
408        /**
409         * The metadata key for the content artwork / album art.
410         */
411        public final static int BITMAP_KEY_ARTWORK = 100;
412        /**
413         * @hide
414         * TODO(jmtrivi) have lockscreen and music move to the new key name
415         */
416        public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK;
417
418        /**
419         * Adds textual information to be displayed.
420         * Note that none of the information added after {@link #apply()} has been called,
421         * will be displayed.
422         * @param key The identifier of a the metadata field to set. Valid values are
423         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
424         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
425         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
426         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
427         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
428         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
429         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
430         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
431         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
432         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
433         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
434         * @param value The text for the given key, or {@code null} to signify there is no valid
435         *      information for the field.
436         * @return Returns a reference to the same MetadataEditor object, so you can chain put
437         *      calls together.
438         */
439        public synchronized MetadataEditor putString(int key, String value)
440                throws IllegalArgumentException {
441            if (mApplied) {
442                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
443                return this;
444            }
445            if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) {
446                throw(new IllegalArgumentException("Invalid type 'String' for key "+ key));
447            }
448            mEditorMetadata.putString(String.valueOf(key), value);
449            mMetadataChanged = true;
450            return this;
451        }
452
453        /**
454         * Adds numerical information to be displayed.
455         * Note that none of the information added after {@link #apply()} has been called,
456         * will be displayed.
457         * @param key the identifier of a the metadata field to set. Valid values are
458         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
459         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
460         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
461         *      expressed in milliseconds),
462         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
463         * @param value The long value for the given key
464         * @return Returns a reference to the same MetadataEditor object, so you can chain put
465         *      calls together.
466         * @throws IllegalArgumentException
467         */
468        public synchronized MetadataEditor putLong(int key, long value)
469                throws IllegalArgumentException {
470            if (mApplied) {
471                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
472                return this;
473            }
474            if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) {
475                throw(new IllegalArgumentException("Invalid type 'long' for key "+ key));
476            }
477            mEditorMetadata.putLong(String.valueOf(key), value);
478            mMetadataChanged = true;
479            return this;
480        }
481
482        /**
483         * Sets the album / artwork picture to be displayed on the remote control.
484         * @param key the identifier of the bitmap to set. The only valid value is
485         *      {@link #BITMAP_KEY_ARTWORK}
486         * @param bitmap The bitmap for the artwork, or null if there isn't any.
487         * @return Returns a reference to the same MetadataEditor object, so you can chain put
488         *      calls together.
489         * @throws IllegalArgumentException
490         * @see android.graphics.Bitmap
491         */
492        public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
493                throws IllegalArgumentException {
494            if (mApplied) {
495                Log.e(TAG, "Can't edit a previously applied MetadataEditor");
496                return this;
497            }
498            if (key != BITMAP_KEY_ARTWORK) {
499                throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
500            }
501            if ((mArtworkExpectedWidth > 0) && (mArtworkExpectedHeight > 0)) {
502                mEditorArtwork = scaleBitmapIfTooBig(bitmap,
503                        mArtworkExpectedWidth, mArtworkExpectedHeight);
504            } else {
505                // no valid resize dimensions, store as is
506                mEditorArtwork = bitmap;
507            }
508            mArtworkChanged = true;
509            return this;
510        }
511
512        /**
513         * Clears all the metadata that has been set since the MetadataEditor instance was
514         *     created with {@link RemoteControlClient#editMetadata(boolean)}.
515         */
516        public synchronized void clear() {
517            if (mApplied) {
518                Log.e(TAG, "Can't clear a previously applied MetadataEditor");
519                return;
520            }
521            mEditorMetadata.clear();
522            mEditorArtwork = null;
523        }
524
525        /**
526         * Associates all the metadata that has been set since the MetadataEditor instance was
527         *     created with {@link RemoteControlClient#editMetadata(boolean)}, or since
528         *     {@link #clear()} was called, with the RemoteControlClient. Once "applied",
529         *     this MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
530         */
531        public synchronized void apply() {
532            if (mApplied) {
533                Log.e(TAG, "Can't apply a previously applied MetadataEditor");
534                return;
535            }
536            synchronized(mCacheLock) {
537                // assign the edited data
538                mMetadata = new Bundle(mEditorMetadata);
539                if ((mArtwork != null) && (!mArtwork.equals(mEditorArtwork))) {
540                    mArtwork.recycle();
541                }
542                mArtwork = mEditorArtwork;
543                mEditorArtwork = null;
544                if (mMetadataChanged & mArtworkChanged) {
545                    // send to remote control display if conditions are met
546                    sendMetadataWithArtwork_syncCacheLock();
547                } else if (mMetadataChanged) {
548                    // send to remote control display if conditions are met
549                    sendMetadata_syncCacheLock();
550                } else if (mArtworkChanged) {
551                    // send to remote control display if conditions are met
552                    sendArtwork_syncCacheLock();
553                }
554                mApplied = true;
555            }
556        }
557    }
558
559    /**
560     * Creates a {@link MetadataEditor}.
561     * @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
562     *     was previously applied to the RemoteControlClient, or true if it is to be created empty.
563     * @return a new MetadataEditor instance.
564     */
565    public MetadataEditor editMetadata(boolean startEmpty) {
566        MetadataEditor editor = new MetadataEditor();
567        if (startEmpty) {
568            editor.mEditorMetadata = new Bundle();
569            editor.mEditorArtwork = null;
570            editor.mMetadataChanged = true;
571            editor.mArtworkChanged = true;
572        } else {
573            editor.mEditorMetadata = new Bundle(mMetadata);
574            editor.mEditorArtwork = mArtwork;
575            editor.mMetadataChanged = false;
576            editor.mArtworkChanged = false;
577        }
578        return editor;
579    }
580
581    /**
582     * Sets the current playback state.
583     * @param state The current playback state, one of the following values:
584     *       {@link #PLAYSTATE_STOPPED},
585     *       {@link #PLAYSTATE_PAUSED},
586     *       {@link #PLAYSTATE_PLAYING},
587     *       {@link #PLAYSTATE_FAST_FORWARDING},
588     *       {@link #PLAYSTATE_REWINDING},
589     *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
590     *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
591     *       {@link #PLAYSTATE_BUFFERING},
592     *       {@link #PLAYSTATE_ERROR}.
593     */
594    public void setPlaybackState(int state) {
595        synchronized(mCacheLock) {
596            if (mPlaybackState != state) {
597                // store locally
598                mPlaybackState = state;
599                // keep track of when the state change occurred
600                mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime();
601
602                // send to remote control display if conditions are met
603                sendPlaybackState_syncCacheLock();
604                // update AudioService
605                sendAudioServiceNewPlaybackInfo_syncCacheLock(PLAYBACKINFO_PLAYSTATE, state);
606            }
607        }
608    }
609
610    /**
611     * Sets the flags for the media transport control buttons that this client supports.
612     * @param transportControlFlags A combination of the following flags:
613     *      {@link #FLAG_KEY_MEDIA_PREVIOUS},
614     *      {@link #FLAG_KEY_MEDIA_REWIND},
615     *      {@link #FLAG_KEY_MEDIA_PLAY},
616     *      {@link #FLAG_KEY_MEDIA_PLAY_PAUSE},
617     *      {@link #FLAG_KEY_MEDIA_PAUSE},
618     *      {@link #FLAG_KEY_MEDIA_STOP},
619     *      {@link #FLAG_KEY_MEDIA_FAST_FORWARD},
620     *      {@link #FLAG_KEY_MEDIA_NEXT}
621     */
622    public void setTransportControlFlags(int transportControlFlags) {
623        synchronized(mCacheLock) {
624            // store locally
625            mTransportControlFlags = transportControlFlags;
626
627            // send to remote control display if conditions are met
628            sendTransportControlFlags_syncCacheLock();
629        }
630    }
631
632    /** @hide */
633    public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
634    /** @hide */
635    // hard-coded to the same number of steps as AudioService.MAX_STREAM_VOLUME[STREAM_MUSIC]
636    public final static int DEFAULT_PLAYBACK_VOLUME = 15;
637
638    private int mPlaybackType = PLAYBACK_TYPE_LOCAL;
639    private int mPlaybackVolumeMax = DEFAULT_PLAYBACK_VOLUME;
640    private int mPlaybackVolume = DEFAULT_PLAYBACK_VOLUME;
641    private int mPlaybackVolumeHandling = DEFAULT_PLAYBACK_VOLUME_HANDLING;
642    private int mPlaybackStream = AudioManager.STREAM_MUSIC;
643
644    /**
645     * @hide
646     * Set information describing information related to the playback of media so the system
647     * can implement additional behavior to handle non-local playback usecases.
648     * @param what a key to specify the type of information to set. Valid keys are
649     *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
650     *        {@link #PLAYBACKINFO_USES_STREAM},
651     *        {@link #PLAYBACKINFO_VOLUME},
652     *        {@link #PLAYBACKINFO_VOLUME_MAX},
653     *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
654     * @param value the value for the supplied information to set.
655     */
656    public void setPlaybackInformation(int what, int value) {
657        synchronized(mCacheLock) {
658            switch (what) {
659                case PLAYBACKINFO_PLAYBACK_TYPE:
660                    if ((value >= PLAYBACK_TYPE_MIN) && (value <= PLAYBACK_TYPE_MAX)) {
661                        if (mPlaybackType != value) {
662                            mPlaybackType = value;
663                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
664                        }
665                    } else {
666                        Log.w(TAG, "using invalid value for PLAYBACKINFO_PLAYBACK_TYPE");
667                    }
668                    break;
669                case PLAYBACKINFO_VOLUME:
670                    if ((value > -1) && (value <= mPlaybackVolumeMax)) {
671                        if (mPlaybackVolume != value) {
672                            mPlaybackVolume = value;
673                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
674                        }
675                    } else {
676                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME");
677                    }
678                    break;
679                case PLAYBACKINFO_VOLUME_MAX:
680                    if (value > 0) {
681                        if (mPlaybackVolumeMax != value) {
682                            mPlaybackVolumeMax = value;
683                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
684                        }
685                    } else {
686                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_MAX");
687                    }
688                    break;
689                case PLAYBACKINFO_USES_STREAM:
690                    if ((value >= 0) && (value < AudioSystem.getNumStreamTypes())) {
691                        mPlaybackStream = value;
692                    } else {
693                        Log.w(TAG, "using invalid value for PLAYBACKINFO_USES_STREAM");
694                    }
695                    break;
696                case PLAYBACKINFO_VOLUME_HANDLING:
697                    if ((value >= PLAYBACK_VOLUME_FIXED) && (value <= PLAYBACK_VOLUME_VARIABLE)) {
698                        if (mPlaybackVolumeHandling != value) {
699                            mPlaybackVolumeHandling = value;
700                            sendAudioServiceNewPlaybackInfo_syncCacheLock(what, value);
701                        }
702                    } else {
703                        Log.w(TAG, "using invalid value for PLAYBACKINFO_VOLUME_HANDLING");
704                    }
705                    break;
706                default:
707                    // not throwing an exception or returning an error if more keys are to be
708                    // supported in the future
709                    Log.w(TAG, "setPlaybackInformation() ignoring unknown key " + what);
710                    break;
711            }
712        }
713    }
714
715    /**
716     * @hide
717     * Return playback information represented as an integer value.
718     * @param what a key to specify the type of information to retrieve. Valid keys are
719     *        {@link #PLAYBACKINFO_PLAYBACK_TYPE},
720     *        {@link #PLAYBACKINFO_USES_STREAM},
721     *        {@link #PLAYBACKINFO_VOLUME},
722     *        {@link #PLAYBACKINFO_VOLUME_MAX},
723     *        and {@link #PLAYBACKINFO_VOLUME_HANDLING}.
724     * @return the current value for the given information type, or
725     *   {@link #PLAYBACKINFO_INVALID_VALUE} if an error occurred or the request is invalid, or
726     *   the value is unknown.
727     */
728    public int getIntPlaybackInformation(int what) {
729        synchronized(mCacheLock) {
730            switch (what) {
731                case PLAYBACKINFO_PLAYBACK_TYPE:
732                    return mPlaybackType;
733                case PLAYBACKINFO_VOLUME:
734                    return mPlaybackVolume;
735                case PLAYBACKINFO_VOLUME_MAX:
736                    return mPlaybackVolumeMax;
737                case PLAYBACKINFO_USES_STREAM:
738                    return mPlaybackStream;
739                case PLAYBACKINFO_VOLUME_HANDLING:
740                    return mPlaybackVolumeHandling;
741                default:
742                    Log.e(TAG, "getIntPlaybackInformation() unknown key " + what);
743                    return PLAYBACKINFO_INVALID_VALUE;
744            }
745        }
746    }
747
748    /**
749     * Lock for all cached data
750     */
751    private final Object mCacheLock = new Object();
752    /**
753     * Cache for the playback state.
754     * Access synchronized on mCacheLock
755     */
756    private int mPlaybackState = PLAYSTATE_NONE;
757    /**
758     * Time of last play state change
759     * Access synchronized on mCacheLock
760     */
761    private long mPlaybackStateChangeTimeMs = 0;
762    /**
763     * Cache for the artwork bitmap.
764     * Access synchronized on mCacheLock
765     * Artwork and metadata are not kept in one Bundle because the bitmap sometimes needs to be
766     * accessed to be resized, in which case a copy will be made. This would add overhead in
767     * Bundle operations.
768     */
769    private Bitmap mArtwork;
770    private final int ARTWORK_DEFAULT_SIZE = 256;
771    private final int ARTWORK_INVALID_SIZE = -1;
772    private int mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
773    private int mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
774    /**
775     * Cache for the transport control mask.
776     * Access synchronized on mCacheLock
777     */
778    private int mTransportControlFlags = FLAGS_KEY_MEDIA_NONE;
779    /**
780     * Cache for the metadata strings.
781     * Access synchronized on mCacheLock
782     * This is re-initialized in apply() and so cannot be final.
783     */
784    private Bundle mMetadata = new Bundle();
785
786    /**
787     * The current remote control client generation ID across the system
788     */
789    private int mCurrentClientGenId = -1;
790    /**
791     * The remote control client generation ID, the last time it was told it was the current RC.
792     * If (mCurrentClientGenId == mInternalClientGenId) is true, it means that this remote control
793     * client is the "focused" one, and that whenever this client's info is updated, it needs to
794     * send it to the known IRemoteControlDisplay interfaces.
795     */
796    private int mInternalClientGenId = -2;
797
798    /**
799     * The media button intent description associated with this remote control client
800     * (can / should include target component for intent handling)
801     */
802    private final PendingIntent mRcMediaIntent;
803
804    /**
805     * The remote control display to which this client will send information.
806     * NOTE: Only one IRemoteControlDisplay supported in this implementation
807     */
808    private IRemoteControlDisplay mRcDisplay;
809
810    /**
811     * @hide
812     * Accessor to media button intent description (includes target component)
813     */
814    public PendingIntent getRcMediaIntent() {
815        return mRcMediaIntent;
816    }
817    /**
818     * @hide
819     * Accessor to IRemoteControlClient
820     */
821    public IRemoteControlClient getIRemoteControlClient() {
822        return mIRCC;
823    }
824
825    /**
826     * The IRemoteControlClient implementation
827     */
828    private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() {
829
830        public void onInformationRequested(int clientGeneration, int infoFlags,
831                int artWidth, int artHeight) {
832            // only post messages, we can't block here
833            if (mEventHandler != null) {
834                // signal new client
835                mEventHandler.removeMessages(MSG_NEW_INTERNAL_CLIENT_GEN);
836                mEventHandler.dispatchMessage(
837                        mEventHandler.obtainMessage(
838                                MSG_NEW_INTERNAL_CLIENT_GEN,
839                                artWidth, artHeight,
840                                new Integer(clientGeneration)));
841                // send the information
842                mEventHandler.removeMessages(MSG_REQUEST_PLAYBACK_STATE);
843                mEventHandler.removeMessages(MSG_REQUEST_METADATA);
844                mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL);
845                mEventHandler.removeMessages(MSG_REQUEST_ARTWORK);
846                mEventHandler.dispatchMessage(
847                        mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE));
848                mEventHandler.dispatchMessage(
849                        mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL));
850                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA));
851                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK));
852            }
853        }
854
855        public void setCurrentClientGenerationId(int clientGeneration) {
856            // only post messages, we can't block here
857            if (mEventHandler != null) {
858                mEventHandler.removeMessages(MSG_NEW_CURRENT_CLIENT_GEN);
859                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
860                        MSG_NEW_CURRENT_CLIENT_GEN, clientGeneration, 0/*ignored*/));
861            }
862        }
863
864        public void plugRemoteControlDisplay(IRemoteControlDisplay rcd) {
865            // only post messages, we can't block here
866            if (mEventHandler != null) {
867                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
868                        MSG_PLUG_DISPLAY, rcd));
869            }
870        }
871
872        public void unplugRemoteControlDisplay(IRemoteControlDisplay rcd) {
873            // only post messages, we can't block here
874            if (mEventHandler != null) {
875                mEventHandler.dispatchMessage(mEventHandler.obtainMessage(
876                        MSG_UNPLUG_DISPLAY, rcd));
877            }
878        }
879    };
880
881    /**
882     * @hide
883     * Default value for the unique identifier
884     */
885    public final static int RCSE_ID_UNREGISTERED = -1;
886    /**
887     * Unique identifier of the RemoteControlStackEntry in AudioService with which
888     * this RemoteControlClient is associated.
889     */
890    private int mRcseId = RCSE_ID_UNREGISTERED;
891    /**
892     * @hide
893     * To be only used by AudioManager after it has received the unique id from
894     * IAudioService.registerRemoteControlClient()
895     * @param id the unique identifier of the RemoteControlStackEntry in AudioService with which
896     *              this RemoteControlClient is associated.
897     */
898    public void setRcseId(int id) {
899        mRcseId = id;
900    }
901
902    /**
903     * @hide
904     */
905    public int getRcseId() {
906        return mRcseId;
907    }
908
909    private EventHandler mEventHandler;
910    private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
911    private final static int MSG_REQUEST_METADATA = 2;
912    private final static int MSG_REQUEST_TRANSPORTCONTROL = 3;
913    private final static int MSG_REQUEST_ARTWORK = 4;
914    private final static int MSG_NEW_INTERNAL_CLIENT_GEN = 5;
915    private final static int MSG_NEW_CURRENT_CLIENT_GEN = 6;
916    private final static int MSG_PLUG_DISPLAY = 7;
917    private final static int MSG_UNPLUG_DISPLAY = 8;
918
919    private class EventHandler extends Handler {
920        public EventHandler(RemoteControlClient rcc, Looper looper) {
921            super(looper);
922        }
923
924        @Override
925        public void handleMessage(Message msg) {
926            switch(msg.what) {
927                case MSG_REQUEST_PLAYBACK_STATE:
928                    synchronized (mCacheLock) {
929                        sendPlaybackState_syncCacheLock();
930                    }
931                    break;
932                case MSG_REQUEST_METADATA:
933                    synchronized (mCacheLock) {
934                        sendMetadata_syncCacheLock();
935                    }
936                    break;
937                case MSG_REQUEST_TRANSPORTCONTROL:
938                    synchronized (mCacheLock) {
939                        sendTransportControlFlags_syncCacheLock();
940                    }
941                    break;
942                case MSG_REQUEST_ARTWORK:
943                    synchronized (mCacheLock) {
944                        sendArtwork_syncCacheLock();
945                    }
946                    break;
947                case MSG_NEW_INTERNAL_CLIENT_GEN:
948                    onNewInternalClientGen((Integer)msg.obj, msg.arg1, msg.arg2);
949                    break;
950                case MSG_NEW_CURRENT_CLIENT_GEN:
951                    onNewCurrentClientGen(msg.arg1);
952                    break;
953                case MSG_PLUG_DISPLAY:
954                    onPlugDisplay((IRemoteControlDisplay)msg.obj);
955                    break;
956                case MSG_UNPLUG_DISPLAY:
957                    onUnplugDisplay((IRemoteControlDisplay)msg.obj);
958                    break;
959                default:
960                    Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
961            }
962        }
963    }
964
965    //===========================================================
966    // Communication with IRemoteControlDisplay
967
968    private void detachFromDisplay_syncCacheLock() {
969        mRcDisplay = null;
970        mArtworkExpectedWidth = ARTWORK_INVALID_SIZE;
971        mArtworkExpectedHeight = ARTWORK_INVALID_SIZE;
972    }
973
974    private void sendPlaybackState_syncCacheLock() {
975        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
976            try {
977                mRcDisplay.setPlaybackState(mInternalClientGenId, mPlaybackState,
978                        mPlaybackStateChangeTimeMs);
979            } catch (RemoteException e) {
980                Log.e(TAG, "Error in setPlaybackState(), dead display "+e);
981                detachFromDisplay_syncCacheLock();
982            }
983        }
984    }
985
986    private void sendMetadata_syncCacheLock() {
987        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
988            try {
989                mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
990            } catch (RemoteException e) {
991                Log.e(TAG, "Error in sendPlaybackState(), dead display "+e);
992                detachFromDisplay_syncCacheLock();
993            }
994        }
995    }
996
997    private void sendTransportControlFlags_syncCacheLock() {
998        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
999            try {
1000                mRcDisplay.setTransportControlFlags(mInternalClientGenId,
1001                        mTransportControlFlags);
1002            } catch (RemoteException e) {
1003                Log.e(TAG, "Error in sendTransportControlFlags(), dead display "+e);
1004                detachFromDisplay_syncCacheLock();
1005            }
1006        }
1007    }
1008
1009    private void sendArtwork_syncCacheLock() {
1010        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
1011            // even though we have already scaled in setArtwork(), when this client needs to
1012            // send the bitmap, there might be newer and smaller expected dimensions, so we have
1013            // to check again.
1014            mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
1015            try {
1016                mRcDisplay.setArtwork(mInternalClientGenId, mArtwork);
1017            } catch (RemoteException e) {
1018                Log.e(TAG, "Error in sendArtwork(), dead display "+e);
1019                detachFromDisplay_syncCacheLock();
1020            }
1021        }
1022    }
1023
1024    private void sendMetadataWithArtwork_syncCacheLock() {
1025        if ((mCurrentClientGenId == mInternalClientGenId) && (mRcDisplay != null)) {
1026            // even though we have already scaled in setArtwork(), when this client needs to
1027            // send the bitmap, there might be newer and smaller expected dimensions, so we have
1028            // to check again.
1029            mArtwork = scaleBitmapIfTooBig(mArtwork, mArtworkExpectedWidth, mArtworkExpectedHeight);
1030            try {
1031                mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, mArtwork);
1032            } catch (RemoteException e) {
1033                Log.e(TAG, "Error in setAllMetadata(), dead display "+e);
1034                detachFromDisplay_syncCacheLock();
1035            }
1036        }
1037    }
1038
1039    //===========================================================
1040    // Communication with AudioService
1041
1042    private static IAudioService sService;
1043
1044    private static IAudioService getService()
1045    {
1046        if (sService != null) {
1047            return sService;
1048        }
1049        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
1050        sService = IAudioService.Stub.asInterface(b);
1051        return sService;
1052    }
1053
1054    private void sendAudioServiceNewPlaybackInfo_syncCacheLock(int what, int value) {
1055        if (mRcseId == RCSE_ID_UNREGISTERED) {
1056            return;
1057        }
1058        //Log.d(TAG, "sending to AudioService key=" + what + ", value=" + value);
1059        IAudioService service = getService();
1060        try {
1061            service.setPlaybackInfoForRcc(mRcseId, what, value);
1062        } catch (RemoteException e) {
1063            Log.e(TAG, "Dead object in sendAudioServiceNewPlaybackInfo_syncCacheLock", e);
1064        }
1065    }
1066
1067    //===========================================================
1068    // Message handlers
1069
1070    private void onNewInternalClientGen(Integer clientGeneration, int artWidth, int artHeight) {
1071        synchronized (mCacheLock) {
1072            // this remote control client is told it is the "focused" one:
1073            // it implies that now (mCurrentClientGenId == mInternalClientGenId) is true
1074            mInternalClientGenId = clientGeneration.intValue();
1075            if (artWidth > 0) {
1076                mArtworkExpectedWidth = artWidth;
1077                mArtworkExpectedHeight = artHeight;
1078            }
1079        }
1080    }
1081
1082    private void onNewCurrentClientGen(int clientGeneration) {
1083        synchronized (mCacheLock) {
1084            mCurrentClientGenId = clientGeneration;
1085        }
1086    }
1087
1088    private void onPlugDisplay(IRemoteControlDisplay rcd) {
1089        synchronized(mCacheLock) {
1090            mRcDisplay = rcd;
1091        }
1092    }
1093
1094    private void onUnplugDisplay(IRemoteControlDisplay rcd) {
1095        synchronized(mCacheLock) {
1096            if ((mRcDisplay != null) && (mRcDisplay.asBinder().equals(rcd.asBinder()))) {
1097                mRcDisplay = null;
1098                mArtworkExpectedWidth = ARTWORK_DEFAULT_SIZE;
1099                mArtworkExpectedHeight = ARTWORK_DEFAULT_SIZE;
1100            }
1101        }
1102    }
1103
1104    //===========================================================
1105    // Internal utilities
1106
1107    /**
1108     * Scale a bitmap to fit the smallest dimension by uniformly scaling the incoming bitmap.
1109     * If the bitmap fits, then do nothing and return the original.
1110     *
1111     * @param bitmap
1112     * @param maxWidth
1113     * @param maxHeight
1114     * @return
1115     */
1116
1117    private Bitmap scaleBitmapIfTooBig(Bitmap bitmap, int maxWidth, int maxHeight) {
1118        if (bitmap != null) {
1119            final int width = bitmap.getWidth();
1120            final int height = bitmap.getHeight();
1121            if (width > maxWidth || height > maxHeight) {
1122                float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
1123                int newWidth = Math.round(scale * width);
1124                int newHeight = Math.round(scale * height);
1125                Bitmap.Config newConfig = bitmap.getConfig();
1126                if (newConfig == null) {
1127                    newConfig = Bitmap.Config.ARGB_8888;
1128                }
1129                Bitmap outBitmap = Bitmap.createBitmap(newWidth, newHeight, newConfig);
1130                Canvas canvas = new Canvas(outBitmap);
1131                Paint paint = new Paint();
1132                paint.setAntiAlias(true);
1133                paint.setFilterBitmap(true);
1134                canvas.drawBitmap(bitmap, null,
1135                        new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
1136                bitmap = outBitmap;
1137            }
1138        }
1139        return bitmap;
1140    }
1141
1142    /**
1143     *  Fast routine to go through an array of allowed keys and return whether the key is part
1144     *  of that array
1145     * @param key the key value
1146     * @param validKeys the array of valid keys for a given type
1147     * @return true if the key is part of the array, false otherwise
1148     */
1149    private static boolean validTypeForKey(int key, int[] validKeys) {
1150        try {
1151            for (int i = 0 ; ; i++) {
1152                if (key == validKeys[i]) {
1153                    return true;
1154                }
1155            }
1156        } catch (ArrayIndexOutOfBoundsException e) {
1157            return false;
1158        }
1159    }
1160}
1161