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