RemoteController.java revision 7ddd226e7c6e759feaf2747a90be1cc06acf37a3
1/*
2 * Copyright (C) 2013 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.app.PendingIntent.CanceledException;
21import android.content.Context;
22import android.content.Intent;
23import android.graphics.Bitmap;
24import android.media.IRemoteControlDisplay;
25import android.media.MediaMetadataEditor;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.Looper;
29import android.os.Message;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.util.Log;
33import android.view.KeyEvent;
34
35/**
36 * @hide
37 * CANDIDATE FOR PUBLIC API
38 * The RemoteController class is used to control media playback, display and update media metadata
39 * and playback status, published by applications using the {@link RemoteControlClient} class.
40 * <p>
41 * A RemoteController shall be registered through
42 * {@link AudioManager#registerRemoteController(RemoteController)} in order for the system to send
43 * media event updates to the listener set in
44 * {@link #setOnClientUpdateListener(OnClientUpdateListener)}. This listener is a subclass of
45 * the {@link OnClientUpdateListener} abstract class. Override its methods to receive the
46 * information published by the active {@link RemoteControlClient} instances.
47 * By default an {@link OnClientUpdateListener} implementation will not receive bitmaps for album
48 * art. Use {@link #setBitmapConfiguration(boolean, int, int)} to receive images as well.
49 * <p>
50 * A RemoteController can also be used without being registered, when it is only meant to send
51 * media key events (for play or stop events for instance),
52 * with {@link #sendMediaKeyEvent(KeyEvent)}.
53 */
54public class RemoteController
55{
56    private final static int MAX_BITMAP_DIMENSION = 512;
57    private final static int TRANSPORT_UNKNOWN = 0;
58    private RcDisplay mRcd;
59    private final static String TAG = "RemoteController";
60    private final static boolean DEBUG = false;
61    private final static Object mGenLock = new Object();
62    private final static Object mInfoLock = new Object();
63    private Context mContext;
64    private AudioManager mAudioManager;
65    private MetadataEditor mMetadataEditor;
66
67    /**
68     * Synchronized on mGenLock
69     */
70    private int mClientGenerationIdCurrent = 0;
71
72    /**
73     * Synchronized on mInfoLock
74     */
75    private boolean mIsRegistered = false;
76    private PendingIntent mClientPendingIntentCurrent;
77    private OnClientUpdateListener mOnClientUpdateListener;
78    private PlaybackInfo mLastPlaybackInfo;
79    private int mLastTransportControlFlags = TRANSPORT_UNKNOWN;
80
81    /**
82     * @hide
83     * CANDIDATE FOR PUBLIC API
84     * @param ctxt non-null {@link Context}
85     * @throws java.lang.IllegalArgumentException
86     */
87    public RemoteController(Context ctxt) throws IllegalArgumentException {
88        if (ctxt == null) {
89            throw new IllegalArgumentException("Invalid null Context");
90        }
91        Looper looper;
92        if ((looper = Looper.myLooper()) != null) {
93            mEventHandler = new EventHandler(this, looper);
94        } else if ((looper = Looper.getMainLooper()) != null) {
95            mEventHandler = new EventHandler(this, looper);
96        } else {
97            mEventHandler = null;
98            Log.e(TAG, "RemoteController() couldn't find main application thread");
99        }
100        mContext = ctxt;
101        mRcd = new RcDisplay();
102        mAudioManager = (AudioManager) ctxt.getSystemService(Context.AUDIO_SERVICE);
103    }
104
105    /**
106     * @hide
107     * CANDIDATE FOR PUBLIC API
108     * @param looper
109     * @param ctxt non-null {@link Context}
110     * @throws java.lang.IllegalArgumentException
111     */
112    public RemoteController(Looper looper, Context ctxt) throws IllegalArgumentException {
113        if (ctxt == null) {
114            throw new IllegalArgumentException("Invalid null Context");
115        }
116        mEventHandler = new EventHandler(this, looper);
117        mContext = ctxt;
118        mRcd = new RcDisplay();
119        mAudioManager = (AudioManager) ctxt.getSystemService(Context.AUDIO_SERVICE);
120    }
121
122
123    /**
124     * @hide
125     * CANDIDATE FOR PUBLIC API
126     */
127    public static abstract class OnClientUpdateListener {
128        /**
129         * @hide
130         * CANDIDATE FOR PUBLIC API
131         * @param clearing
132         */
133        public void onClientReset(boolean clearing) { }
134        /**
135         * @hide
136         * CANDIDATE FOR PUBLIC API
137         * @param state
138         */
139        public void onClientPlaybackStateUpdate(int state) { }
140        /**
141         * @hide
142         * CANDIDATE FOR PUBLIC API
143         * @param state
144         * @param stateChangeTimeMs
145         * @param currentPosMs
146         * @param speed
147         */
148        public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
149                long currentPosMs, float speed) { }
150        /**
151         * @hide
152         * CANDIDATE FOR PUBLIC API
153         * @param transportControlFlags
154         * @param posCapabilities
155         */
156        public void onClientTransportControlUpdate(int transportControlFlags) { }
157        /**
158         * @hide
159         * CANDIDATE FOR PUBLIC API
160         * @param metadataEditor
161         */
162        public void onClientMetadataUpdate(MetadataEditor metadataEditor) { }
163    };
164
165    /**
166     * @hide
167     * CANDIDATE FOR PUBLIC API
168     * @param l
169     */
170    public void setOnClientUpdateListener(OnClientUpdateListener l) {
171        synchronized(mInfoLock) {
172            mOnClientUpdateListener = l;
173            if (!mIsRegistered) {
174                // since the object is not registered, it hasn't received any information from
175                // RemoteControlClients yet, so we can exit here.
176                return;
177            }
178            if (mLastPlaybackInfo != null) {
179                sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
180                        mClientGenerationIdCurrent /*arg1*/, 0,
181                        mLastPlaybackInfo /*obj*/, 0 /*delay*/);
182            }
183            if (mLastTransportControlFlags != TRANSPORT_UNKNOWN) {
184                sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
185                        mClientGenerationIdCurrent /*arg1*/, mLastTransportControlFlags /*arg2*/,
186                        null /*obj*/, 0 /*delay*/);
187            }
188            if (mMetadataEditor != null) {
189                sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
190                        mClientGenerationIdCurrent /*arg1*/, 0 /*arg2*/,
191                        mMetadataEditor /*obj*/, 0 /*delay*/);
192            }
193        }
194    }
195
196
197    /**
198     * @hide
199     * CANDIDATE FOR PUBLIC API
200     * Send a simulated key event for a media button.
201     * May be used without registering the RemoteController
202     * with {@link AudioManager#registerRemoteController(RemoteController)}. To simulate a key
203     * press, you must first send a KeyEvent built with a {@link KeyEvent#ACTION_DOWN} action, then
204     * another event with the {@link KeyEvent#ACTION_UP} action.
205     * <p> When used from a registered RemoteController, the key event will be sent to the
206     * application currently promoted to publish its media metadata and playback state (there may be
207     * none under some circumstances). With an unregistered RemoteController, the key event will be
208     * sent to the current media key event consumer
209     * (see {@link AudioManager#registerMediaButtonEventReceiver(PendingIntent)}).
210     * @param keyEvent a {@link KeyEvent} instance whose key code is one of
211     *     {@link KeyEvent.KEYCODE_MUTE},
212     *     {@link KeyEvent.KEYCODE_HEADSETHOOK},
213     *     {@link KeyEvent.KEYCODE_MEDIA_PLAY},
214     *     {@link KeyEvent.KEYCODE_MEDIA_PAUSE},
215     *     {@link KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
216     *     {@link KeyEvent.KEYCODE_MEDIA_STOP},
217     *     {@link KeyEvent.KEYCODE_MEDIA_NEXT},
218     *     {@link KeyEvent.KEYCODE_MEDIA_PREVIOUS},
219     *     {@link KeyEvent.KEYCODE_MEDIA_REWIND},
220     *     {@link KeyEvent.KEYCODE_MEDIA_RECORD},
221     *     {@link KeyEvent.KEYCODE_MEDIA_FAST_FORWARD},
222     *     {@link KeyEvent.KEYCODE_MEDIA_CLOSE},
223     *     {@link KeyEvent.KEYCODE_MEDIA_EJECT},
224     *     or {@link KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK}.
225     */
226    public int sendMediaKeyEvent(KeyEvent keyEvent) {
227        if (!MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode())) {
228            Log.e(TAG, "Cannot use sendMediaKeyEvent() for a non-media key event");
229            return ERROR_BAD_VALUE;
230        }
231        boolean registered = false;
232        final PendingIntent pi;
233        synchronized(mInfoLock) {
234            registered = mIsRegistered;
235            pi = mClientPendingIntentCurrent;
236        }
237        if (registered) {
238            if (pi != null) {
239                Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
240                intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
241                try {
242                    pi.send(mContext, 0, intent);
243                } catch (CanceledException e) {
244                    Log.e(TAG, "Error sending intent for media button down: ", e);
245                    return ERROR;
246                }
247            } else {
248                Log.i(TAG, "No-op when sending key click, no receiver right now");
249                return ERROR;
250            }
251        } else {
252            mAudioManager.dispatchMediaKeyEvent(keyEvent);
253        }
254        return SUCCESS;
255    }
256
257
258    // Error codes
259    /**
260     * @hide
261     * CANDIDATE FOR PUBLIC API
262     * Successful operation.
263     */
264    public  static final int SUCCESS            = 0;
265    /**
266     * @hide
267     * CANDIDATE FOR PUBLIC API
268     * Unspecified error.
269     */
270    public  static final int ERROR              = -1;
271    /**
272     * @hide
273     * CANDIDATE FOR PUBLIC API
274     * Operation failed due to bad parameter value.
275     */
276    public  static final int ERROR_BAD_VALUE    = -2;
277
278
279    /**
280     * @hide
281     * CANDIDATE FOR PUBLIC API
282     * @param timeMs
283     * @return {@link #SUCCESS}, {@link #ERROR} or {@link #ERROR_BAD_VALUE}
284     */
285    public int seekTo(long timeMs) {
286        if (timeMs < 0) {
287            return ERROR_BAD_VALUE;
288        }
289        final int genId;
290        synchronized (mGenLock) {
291            genId = mClientGenerationIdCurrent;
292        }
293        mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs);
294        return SUCCESS;
295    }
296
297
298    /**
299     * @hide
300     * must be called on a registered RemoteController
301     * @param wantBitmap
302     * @param width
303     * @param height
304     * @return {@link #SUCCESS}, {@link #ERROR} or {@link #ERROR_BAD_VALUE}
305     */
306    public int setBitmapConfiguration(boolean wantBitmap, int width, int height) {
307        synchronized (mInfoLock) {
308            if (!mIsRegistered) {
309                Log.e(TAG, "Cannot specify bitmap configuration on unregistered RemoteController");
310                return ERROR;
311            }
312        }
313        if (wantBitmap) {
314            if ((width > 0) && (height > 0)) {
315                if (width > MAX_BITMAP_DIMENSION) { width = MAX_BITMAP_DIMENSION; }
316                if (height > MAX_BITMAP_DIMENSION) { height = MAX_BITMAP_DIMENSION; }
317                mAudioManager.remoteControlDisplayUsesBitmapSize(mRcd, width, height);
318            } else {
319                Log.e(TAG, "Invalid dimensions");
320                return ERROR_BAD_VALUE;
321            }
322        } else {
323            mAudioManager.remoteControlDisplayUsesBitmapSize(mRcd, -1, -1);
324        }
325        return SUCCESS;
326    }
327
328    /**
329     * @hide
330     * CANDIDATE FOR PUBLIC API
331     * must be called on a registered RemoteController
332     * @param width
333     * @param height
334     * @return {@link #SUCCESS}, {@link #ERROR} or {@link #ERROR_BAD_VALUE}
335     */
336    public int setBitmapConfiguration(int width, int height) {
337        return setBitmapConfiguration(true, width, height);
338    }
339
340    /**
341     * @hide
342     * CANDIDATE FOR PUBLIC API
343     * must be called on a registered RemoteController
344     * @return {@link #SUCCESS}, {@link #ERROR}
345     */
346    public int setBitmapConfigurationNone() {
347        return setBitmapConfiguration(false, -1, -1);
348    }
349
350
351    /**
352     * @hide
353     * CANDIDATE FOR PUBLIC API
354     * Default playback position synchronization mode where the RemoteControlClient is not
355     * asked regularly for its playback position to see if it has drifted from the estimated
356     * position.
357     */
358    public static final int POSITION_SYNCHRONIZATION_NONE = 0;
359
360    /**
361     * @hide
362     * CANDIDATE FOR PUBLIC API
363     * The playback position synchronization mode where the RemoteControlClient instances which
364     * expose their playback position to the framework, will be regularly polled to check
365     * whether any drift has been noticed between their estimated position and the one they report.
366     * Note that this mode should only ever be used when needing to display very accurate playback
367     * position, as regularly polling a RemoteControlClient for its position may have an impact
368     * on battery life (if applicable) when this query will trigger network transactions in the
369     * case of remote playback.
370     */
371    public static final int POSITION_SYNCHRONIZATION_CHECK = 1;
372
373    /**
374     * @hide
375     * CANDIDATE FOR PUBLIC API
376     * Set the playback position synchronization mode.
377     * Must be called on a registered RemoteController.
378     * @param sync {@link #POSITION_SYNCHRONIZATION_NONE} or {@link #POSITION_SYNCHRONIZATION_CHECK}
379     * @return {@link #SUCCESS}, {@link #ERROR} or {@link #ERROR_BAD_VALUE}
380     */
381    public int setSynchronizationMode(int sync) {
382        if ((sync != POSITION_SYNCHRONIZATION_NONE) || (sync != POSITION_SYNCHRONIZATION_CHECK)) {
383            Log.e(TAG, "Unknown synchronization mode");
384            return ERROR_BAD_VALUE;
385        }
386        if (!mIsRegistered) {
387            Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController");
388            return ERROR;
389        }
390        mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd,
391                POSITION_SYNCHRONIZATION_CHECK == sync);
392        return SUCCESS;
393    }
394
395
396    /**
397     * @hide
398     * CANDIDATE FOR PUBLIC API
399     * Creates a {@link MetadataEditor} for updating metadata values of the editable keys of
400     * the current {@link RemoteControlClient}.
401     * @return a new MetadataEditor instance.
402     */
403    public MetadataEditor editMetadata() {
404        MetadataEditor editor = new MetadataEditor();
405        editor.mEditorMetadata = new Bundle();
406        editor.mEditorArtwork = null;
407        editor.mMetadataChanged = true;
408        editor.mArtworkChanged = true;
409        editor.mEditableKeys = 0;
410        return editor;
411    }
412
413
414    /**
415     * @hide
416     * CANDIDATE FOR PUBLIC API
417     * Used to read the metadata published by a {@link RemoteControlClient}, or send a
418     * {@link RemoteControlClient} new values for keys that can be edited.
419     */
420    public class MetadataEditor extends MediaMetadataEditor {
421        /**
422         * @hide
423         */
424        protected MetadataEditor() { }
425
426        /**
427         * @hide
428         */
429        protected MetadataEditor(Bundle metadata, long editableKeys) {
430            mEditorMetadata = metadata;
431            mEditableKeys = editableKeys;
432            mEditorArtwork = null;
433            mMetadataChanged = true;
434            mArtworkChanged = true;
435            mApplied = false;
436        }
437
438        /**
439         * @hide
440         * CANDIDATE FOR PUBLIC API
441         */
442        public synchronized void apply() {
443            // "applying" a metadata bundle in RemoteController is only for sending edited
444            // key values back to the RemoteControlClient, so here we only care about the only
445            // editable key we support: RATING_KEY_BY_USER
446            if (!mMetadataChanged) {
447                return;
448            }
449            final int genId;
450            synchronized(mGenLock) {
451                genId = mClientGenerationIdCurrent;
452            }
453            synchronized(mInfoLock) {
454                if (mEditorMetadata.containsKey(
455                        String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
456                    Rating rating = (Rating) getObject(
457                            MediaMetadataEditor.RATING_KEY_BY_USER, null);
458                    mAudioManager.updateRemoteControlClientMetadata(genId,
459                          MediaMetadataEditor.RATING_KEY_BY_USER,
460                          rating);
461                } else {
462                    Log.e(TAG, "no metadata to apply");
463                }
464                // NOT setting mApplied to true as this type of MetadataEditor will be applied
465                // multiple times, whenever the user of a RemoteController needs to change the
466                // metadata (e.g. user changes the rating of a song more than once during playback)
467                mApplied = false;
468            }
469        }
470
471    }
472
473
474    //==================================================
475    // Implementation of IRemoteControlDisplay interface
476    private class RcDisplay extends IRemoteControlDisplay.Stub {
477        /**
478         * @hide
479         */
480        public void setCurrentClientId(int genId, PendingIntent clientMediaIntent,
481                boolean clearing) {
482            boolean isNew = false;
483            synchronized(mGenLock) {
484                if (mClientGenerationIdCurrent != genId) {
485                    mClientGenerationIdCurrent = genId;
486                    isNew = true;
487                }
488            }
489            if (clientMediaIntent != null) {
490                sendMsg(mEventHandler, MSG_NEW_PENDING_INTENT, SENDMSG_REPLACE,
491                        genId /*arg1*/, 0, clientMediaIntent /*obj*/, 0 /*delay*/);
492            }
493            if (isNew || clearing) {
494                sendMsg(mEventHandler, MSG_CLIENT_RESET, SENDMSG_REPLACE,
495                        genId /*arg1*/, clearing ? 1 : 0, null /*obj*/, 0 /*delay*/);
496            }
497        }
498
499        /**
500         * @hide
501         */
502        public void setPlaybackState(int genId, int state,
503                long stateChangeTimeMs, long currentPosMs, float speed) {
504            if (DEBUG) {
505                Log.d(TAG, "> new playback state: genId="+genId
506                        + " state="+ state
507                        + " changeTime="+ stateChangeTimeMs
508                        + " pos=" + currentPosMs
509                        + "ms speed=" + speed);
510            }
511
512            synchronized(mGenLock) {
513                if (mClientGenerationIdCurrent != genId) {
514                    return;
515                }
516            }
517            final PlaybackInfo playbackInfo =
518                    new PlaybackInfo(state, stateChangeTimeMs, currentPosMs, speed);
519            sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
520                    genId /*arg1*/, 0, playbackInfo /*obj*/, 0 /*delay*/);
521
522        }
523
524        /**
525         * @hide
526         */
527        public void setTransportControlInfo(int genId, int transportControlFlags,
528                int posCapabilities) {
529            synchronized(mGenLock) {
530                if (mClientGenerationIdCurrent != genId) {
531                    return;
532                }
533            }
534            sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
535                    genId /*arg1*/, transportControlFlags /*arg2*/,
536                    null /*obj*/, 0 /*delay*/);
537        }
538
539        /**
540         * @hide
541         */
542        public void setMetadata(int genId, Bundle metadata) {
543            if (DEBUG) { Log.e(TAG, "setMetadata("+genId+")"); }
544            if (metadata == null) {
545                return;
546            }
547            synchronized(mGenLock) {
548                if (mClientGenerationIdCurrent != genId) {
549                    return;
550                }
551            }
552            sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
553                    genId /*arg1*/, 0 /*arg2*/,
554                    metadata /*obj*/, 0 /*delay*/);
555        }
556
557        /**
558         * @hide
559         */
560        public void setArtwork(int genId, Bitmap artwork) {
561            if (DEBUG) { Log.v(TAG, "setArtwork("+genId+")"); }
562            if (artwork == null) {
563                return;
564            }
565            synchronized(mGenLock) {
566                if (mClientGenerationIdCurrent != genId) {
567                    return;
568                }
569            }
570            Bundle metadata = new Bundle(1);
571            metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), artwork);
572            sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
573                    genId /*arg1*/, 0 /*arg2*/,
574                    metadata /*obj*/, 0 /*delay*/);
575        }
576
577        /**
578         * @hide
579         */
580        public void setAllMetadata(int genId, Bundle metadata, Bitmap artwork) {
581            if (DEBUG) { Log.e(TAG, "setAllMetadata("+genId+")"); }
582            if ((metadata == null) && (artwork == null)) {
583                return;
584            }
585            synchronized(mGenLock) {
586                if (mClientGenerationIdCurrent != genId) {
587                    return;
588                }
589            }
590            if (metadata == null) {
591                metadata = new Bundle(1);
592            }
593            if (artwork != null) {
594                metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
595                        artwork);
596            }
597            sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
598                    genId /*arg1*/, 0 /*arg2*/,
599                    metadata /*obj*/, 0 /*delay*/);
600        }
601    }
602
603    //==================================================
604    // Event handling
605    private EventHandler mEventHandler;
606    private final static int MSG_NEW_PENDING_INTENT = 0;
607    private final static int MSG_NEW_PLAYBACK_INFO =  1;
608    private final static int MSG_NEW_TRANSPORT_INFO = 2;
609    private final static int MSG_NEW_METADATA       = 3; // msg always has non-null obj parameter
610    private final static int MSG_CLIENT_RESET       = 4;
611
612    private class EventHandler extends Handler {
613
614        public EventHandler(RemoteController rc, Looper looper) {
615            super(looper);
616        }
617
618        @Override
619        public void handleMessage(Message msg) {
620            switch(msg.what) {
621                case MSG_NEW_PENDING_INTENT:
622                    onNewPendingIntent(msg.arg1, (PendingIntent) msg.obj);
623                    break;
624                case MSG_NEW_PLAYBACK_INFO:
625                    onNewPlaybackInfo(msg.arg1, (PlaybackInfo) msg.obj);
626                    break;
627                case MSG_NEW_TRANSPORT_INFO:
628                    onNewTransportInfo(msg.arg1, msg.arg2);
629                    break;
630                case MSG_NEW_METADATA:
631                    onNewMetadata(msg.arg1, (Bundle)msg.obj);
632                    break;
633                case MSG_CLIENT_RESET:
634                    onClientReset(msg.arg1, msg.arg2 == 1);
635                    break;
636                default:
637                    Log.e(TAG, "unknown event " + msg.what);
638            }
639        }
640    }
641
642    /** If the msg is already queued, replace it with this one. */
643    private static final int SENDMSG_REPLACE = 0;
644    /** If the msg is already queued, ignore this one and leave the old. */
645    private static final int SENDMSG_NOOP = 1;
646    /** If the msg is already queued, queue this one and leave the old. */
647    private static final int SENDMSG_QUEUE = 2;
648
649    private static void sendMsg(Handler handler, int msg, int existingMsgPolicy,
650            int arg1, int arg2, Object obj, int delayMs) {
651        if (handler == null) {
652            Log.e(TAG, "null event handler, will not deliver message " + msg);
653            return;
654        }
655        if (existingMsgPolicy == SENDMSG_REPLACE) {
656            handler.removeMessages(msg);
657        } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
658            return;
659        }
660        handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs);
661    }
662
663    private void onNewPendingIntent(int genId, PendingIntent pi) {
664        synchronized(mGenLock) {
665            if (mClientGenerationIdCurrent != genId) {
666                return;
667            }
668        }
669        synchronized(mInfoLock) {
670            mClientPendingIntentCurrent = pi;
671        }
672    }
673
674    private void onNewPlaybackInfo(int genId, PlaybackInfo pi) {
675        synchronized(mGenLock) {
676            if (mClientGenerationIdCurrent != genId) {
677                return;
678            }
679        }
680        final OnClientUpdateListener l;
681        synchronized(mInfoLock) {
682            l = this.mOnClientUpdateListener;
683            mLastPlaybackInfo = pi;
684        }
685        if (l != null) {
686            if (pi.mCurrentPosMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
687                l.onClientPlaybackStateUpdate(pi.mState);
688            } else {
689                l.onClientPlaybackStateUpdate(pi.mState, pi.mStateChangeTimeMs, pi.mCurrentPosMs,
690                        pi.mSpeed);
691            }
692        }
693    }
694
695    private void onNewTransportInfo(int genId, int transportControlFlags) {
696        synchronized(mGenLock) {
697            if (mClientGenerationIdCurrent != genId) {
698                return;
699            }
700        }
701        final OnClientUpdateListener l;
702        synchronized(mInfoLock) {
703            l = mOnClientUpdateListener;
704            mLastTransportControlFlags = transportControlFlags;
705        }
706        if (l != null) {
707            l.onClientTransportControlUpdate(transportControlFlags);
708        }
709    }
710
711    /**
712     * @param genId
713     * @param metadata guaranteed to be always non-null
714     */
715    private void onNewMetadata(int genId, Bundle metadata) {
716        synchronized(mGenLock) {
717            if (mClientGenerationIdCurrent != genId) {
718                return;
719            }
720        }
721        final OnClientUpdateListener l;
722        final MetadataEditor metadataEditor;
723        // prepare the received Bundle to be used inside a MetadataEditor
724        final long editableKeys = metadata.getLong(
725                String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK), 0);
726        if (editableKeys != 0) {
727            metadata.remove(String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK));
728        }
729        synchronized(mInfoLock) {
730            l = mOnClientUpdateListener;
731            if ((mMetadataEditor != null) && (mMetadataEditor.mEditorMetadata != null)) {
732                if (mMetadataEditor.mEditorMetadata != metadata) {
733                    // existing metadata, merge existing and new
734                    mMetadataEditor.mEditorMetadata.putAll(metadata);
735                }
736            } else {
737                mMetadataEditor = new MetadataEditor(metadata, editableKeys);
738            }
739            metadataEditor = mMetadataEditor;
740        }
741        if (l != null) {
742            l.onClientMetadataUpdate(metadataEditor);
743        }
744    }
745
746    private void onClientReset(int genId, boolean clearing) {
747        synchronized(mGenLock) {
748            if (mClientGenerationIdCurrent != genId) {
749                return;
750            }
751        }
752        final OnClientUpdateListener l;
753        synchronized(mInfoLock) {
754            l = mOnClientUpdateListener;
755        }
756        if (l != null) {
757            l.onClientReset(clearing);
758        }
759    }
760
761
762    //==================================================
763    private static class PlaybackInfo {
764        int mState;
765        long mStateChangeTimeMs;
766        long mCurrentPosMs;
767        float mSpeed;
768
769        PlaybackInfo(int state, long stateChangeTimeMs, long currentPosMs, float speed) {
770            mState = state;
771            mStateChangeTimeMs = stateChangeTimeMs;
772            mCurrentPosMs = currentPosMs;
773            mSpeed = speed;
774        }
775    }
776
777    /**
778     * @hide
779     * Used by AudioManager to mark this instance as registered.
780     * @param registered
781     */
782    protected void setIsRegistered(boolean registered) {
783        synchronized (mInfoLock) {
784            mIsRegistered = registered;
785        }
786    }
787
788    /**
789     * @hide
790     * Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl
791     * @return
792     */
793    protected RcDisplay getRcDisplay() {
794        return mRcd;
795    }
796}
797