RemoteController.java revision a259d35073ada384a5810f2a0f4f92f5fd27d85f
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.ActivityManager;
20import android.app.PendingIntent;
21import android.app.PendingIntent.CanceledException;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.graphics.Bitmap;
26import android.media.IRemoteControlDisplay;
27import android.media.MediaMetadataEditor;
28import android.media.session.MediaController;
29import android.media.session.MediaSession;
30import android.media.session.MediaSessionLegacyHelper;
31import android.media.session.MediaSessionManager;
32import android.media.session.PlaybackState;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.Looper;
36import android.os.Message;
37import android.os.SystemClock;
38import android.os.UserHandle;
39import android.util.DisplayMetrics;
40import android.util.Log;
41import android.view.KeyEvent;
42
43import java.lang.ref.WeakReference;
44import java.util.List;
45
46/**
47 * The RemoteController class is used to control media playback, display and update media metadata
48 * and playback status, published by applications using the {@link RemoteControlClient} class.
49 * <p>
50 * A RemoteController shall be registered through
51 * {@link AudioManager#registerRemoteController(RemoteController)} in order for the system to send
52 * media event updates to the {@link OnClientUpdateListener} listener set in the class constructor.
53 * Implement the methods of the interface to receive the information published by the active
54 * {@link RemoteControlClient} instances.
55 * <br>By default an {@link OnClientUpdateListener} implementation will not receive bitmaps for
56 * album art. Use {@link #setArtworkConfiguration(int, int)} to receive images as well.
57 * <p>
58 * Registration requires the {@link OnClientUpdateListener} listener to be one of the enabled
59 * notification listeners (see {@link android.service.notification.NotificationListenerService}).
60 *
61 * @deprecated Use {@link MediaController} instead.
62 */
63@Deprecated public final class RemoteController
64{
65    private final static int MAX_BITMAP_DIMENSION = 512;
66    private final static int TRANSPORT_UNKNOWN = 0;
67    private final static String TAG = "RemoteController";
68    private final static boolean DEBUG = false;
69    private final static boolean USE_SESSIONS = true;
70    private final static Object mGenLock = new Object();
71    private final static Object mInfoLock = new Object();
72    private final RcDisplay mRcd;
73    private final Context mContext;
74    private final AudioManager mAudioManager;
75    private final int mMaxBitmapDimension;
76    private MetadataEditor mMetadataEditor;
77
78    private MediaSessionManager mSessionManager;
79    private MediaSessionManager.SessionListener mSessionListener
80            = new TopTransportSessionListener();
81    private MediaController.Callback mSessionCb = new MediaControllerCallback();
82
83    /**
84     * Synchronized on mGenLock
85     */
86    private int mClientGenerationIdCurrent = 0;
87
88    /**
89     * Synchronized on mInfoLock
90     */
91    private boolean mIsRegistered = false;
92    private PendingIntent mClientPendingIntentCurrent;
93    private OnClientUpdateListener mOnClientUpdateListener;
94    private PlaybackInfo mLastPlaybackInfo;
95    private int mArtworkWidth = -1;
96    private int mArtworkHeight = -1;
97    private boolean mEnabled = true;
98    // synchronized on mInfoLock, for USE_SESSION apis.
99    private MediaController mCurrentSession;
100
101    /**
102     * Class constructor.
103     * @param context the {@link Context}, must be non-null.
104     * @param updateListener the listener to be called whenever new client information is available,
105     *     must be non-null.
106     * @throws IllegalArgumentException
107     */
108    public RemoteController(Context context, OnClientUpdateListener updateListener)
109            throws IllegalArgumentException {
110        this(context, updateListener, null);
111    }
112
113    /**
114     * Class constructor.
115     * @param context the {@link Context}, must be non-null.
116     * @param updateListener the listener to be called whenever new client information is available,
117     *     must be non-null.
118     * @param looper the {@link Looper} on which to run the event loop,
119     *     or null to use the current thread's looper.
120     * @throws java.lang.IllegalArgumentException
121     */
122    public RemoteController(Context context, OnClientUpdateListener updateListener, Looper looper)
123            throws IllegalArgumentException {
124        if (context == null) {
125            throw new IllegalArgumentException("Invalid null Context");
126        }
127        if (updateListener == null) {
128            throw new IllegalArgumentException("Invalid null OnClientUpdateListener");
129        }
130        if (looper != null) {
131            mEventHandler = new EventHandler(this, looper);
132        } else {
133            Looper l = Looper.myLooper();
134            if (l != null) {
135                mEventHandler = new EventHandler(this, l);
136            } else {
137                throw new IllegalArgumentException("Calling thread not associated with a looper");
138            }
139        }
140        mOnClientUpdateListener = updateListener;
141        mContext = context;
142        mRcd = new RcDisplay(this);
143        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
144        mSessionManager = (MediaSessionManager) context
145                .getSystemService(Context.MEDIA_SESSION_SERVICE);
146
147        if (ActivityManager.isLowRamDeviceStatic()) {
148            mMaxBitmapDimension = MAX_BITMAP_DIMENSION;
149        } else {
150            final DisplayMetrics dm = context.getResources().getDisplayMetrics();
151            mMaxBitmapDimension = Math.max(dm.widthPixels, dm.heightPixels);
152        }
153    }
154
155
156    /**
157     * Interface definition for the callbacks to be invoked whenever media events, metadata
158     * and playback status are available.
159     */
160    public interface OnClientUpdateListener {
161        /**
162         * Called whenever all information, previously received through the other
163         * methods of the listener, is no longer valid and is about to be refreshed.
164         * This is typically called whenever a new {@link RemoteControlClient} has been selected
165         * by the system to have its media information published.
166         * @param clearing true if there is no selected RemoteControlClient and no information
167         *     is available.
168         */
169        public void onClientChange(boolean clearing);
170
171        /**
172         * Called whenever the playback state has changed.
173         * It is called when no information is known about the playback progress in the media and
174         * the playback speed.
175         * @param state one of the playback states authorized
176         *     in {@link RemoteControlClient#setPlaybackState(int)}.
177         */
178        public void onClientPlaybackStateUpdate(int state);
179        /**
180         * Called whenever the playback state has changed, and playback position
181         * and speed are known.
182         * @param state one of the playback states authorized
183         *     in {@link RemoteControlClient#setPlaybackState(int)}.
184         * @param stateChangeTimeMs the system time at which the state change was reported,
185         *     expressed in ms. Based on {@link android.os.SystemClock#elapsedRealtime()}.
186         * @param currentPosMs a positive value for the current media playback position expressed
187         *     in ms, a negative value if the position is temporarily unknown.
188         * @param speed  a value expressed as a ratio of 1x playback: 1.0f is normal playback,
189         *    2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is
190         *    playing (e.g. when state is {@link RemoteControlClient#PLAYSTATE_ERROR}).
191         */
192        public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
193                long currentPosMs, float speed);
194        /**
195         * Called whenever the transport control flags have changed.
196         * @param transportControlFlags one of the flags authorized
197         *     in {@link RemoteControlClient#setTransportControlFlags(int)}.
198         */
199        public void onClientTransportControlUpdate(int transportControlFlags);
200        /**
201         * Called whenever new metadata is available.
202         * See the {@link MediaMetadataEditor#putLong(int, long)},
203         *  {@link MediaMetadataEditor#putString(int, String)},
204         *  {@link MediaMetadataEditor#putBitmap(int, Bitmap)}, and
205         *  {@link MediaMetadataEditor#putObject(int, Object)} methods for the various keys that
206         *  can be queried.
207         * @param metadataEditor the container of the new metadata.
208         */
209        public void onClientMetadataUpdate(MetadataEditor metadataEditor);
210    };
211
212
213    /**
214     * @hide
215     */
216    public String getRemoteControlClientPackageName() {
217        if (USE_SESSIONS) {
218            synchronized (mInfoLock) {
219                return mCurrentSession != null ? mCurrentSession.getSessionInfo().getPackageName()
220                        : null;
221            }
222        } else {
223            return mClientPendingIntentCurrent != null ?
224                    mClientPendingIntentCurrent.getCreatorPackage() : null;
225        }
226    }
227
228    /**
229     * Return the estimated playback position of the current media track or a negative value
230     * if not available.
231     *
232     * <p>The value returned is estimated by the current process and may not be perfect.
233     * The time returned by this method is calculated from the last state change time based
234     * on the current play position at that time and the last known playback speed.
235     * An application may call {@link #setSynchronizationMode(int)} to apply
236     * a synchronization policy that will periodically re-sync the estimated position
237     * with the RemoteControlClient.</p>
238     *
239     * @return the current estimated playback position in milliseconds or a negative value
240     *         if not available
241     *
242     * @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float)
243     */
244    public long getEstimatedMediaPosition() {
245        if (USE_SESSIONS) {
246            synchronized (mInfoLock) {
247                if (mCurrentSession != null) {
248                    PlaybackState state = mCurrentSession.getPlaybackState();
249                    if (state != null) {
250                        return state.getPosition();
251                    }
252                }
253            }
254        } else {
255            final PlaybackInfo lastPlaybackInfo;
256            synchronized (mInfoLock) {
257                lastPlaybackInfo = mLastPlaybackInfo;
258            }
259            if (lastPlaybackInfo != null) {
260                if (!RemoteControlClient.playbackPositionShouldMove(lastPlaybackInfo.mState)) {
261                    return lastPlaybackInfo.mCurrentPosMs;
262                }
263
264                // Take the current position at the time of state change and
265                // estimate.
266                final long thenPos = lastPlaybackInfo.mCurrentPosMs;
267                if (thenPos < 0) {
268                    return -1;
269                }
270
271                final long now = SystemClock.elapsedRealtime();
272                final long then = lastPlaybackInfo.mStateChangeTimeMs;
273                final long sinceThen = now - then;
274                final long scaledSinceThen = (long) (sinceThen * lastPlaybackInfo.mSpeed);
275                return thenPos + scaledSinceThen;
276            }
277        }
278        return -1;
279    }
280
281
282    /**
283     * Send a simulated key event for a media button to be received by the current client.
284     * To simulate a key press, you must first send a KeyEvent built with
285     * a {@link KeyEvent#ACTION_DOWN} action, then another event with the {@link KeyEvent#ACTION_UP}
286     * action.
287     * <p>The key event will be sent to the registered receiver
288     * (see {@link AudioManager#registerMediaButtonEventReceiver(PendingIntent)}) whose associated
289     * {@link RemoteControlClient}'s metadata and playback state is published (there may be
290     * none under some circumstances).
291     * @param keyEvent a {@link KeyEvent} instance whose key code is one of
292     *     {@link KeyEvent#KEYCODE_MUTE},
293     *     {@link KeyEvent#KEYCODE_HEADSETHOOK},
294     *     {@link KeyEvent#KEYCODE_MEDIA_PLAY},
295     *     {@link KeyEvent#KEYCODE_MEDIA_PAUSE},
296     *     {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE},
297     *     {@link KeyEvent#KEYCODE_MEDIA_STOP},
298     *     {@link KeyEvent#KEYCODE_MEDIA_NEXT},
299     *     {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS},
300     *     {@link KeyEvent#KEYCODE_MEDIA_REWIND},
301     *     {@link KeyEvent#KEYCODE_MEDIA_RECORD},
302     *     {@link KeyEvent#KEYCODE_MEDIA_FAST_FORWARD},
303     *     {@link KeyEvent#KEYCODE_MEDIA_CLOSE},
304     *     {@link KeyEvent#KEYCODE_MEDIA_EJECT},
305     *     or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}.
306     * @return true if the event was successfully sent, false otherwise.
307     * @throws IllegalArgumentException
308     */
309    public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException {
310        if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
311            throw new IllegalArgumentException("not a media key event");
312        }
313        if (USE_SESSIONS) {
314            synchronized (mInfoLock) {
315                if (mCurrentSession != null) {
316                    return mCurrentSession.dispatchMediaButtonEvent(keyEvent);
317                }
318                return false;
319            }
320        } else {
321            final PendingIntent pi;
322            synchronized (mInfoLock) {
323                if (!mIsRegistered) {
324                    Log.e(TAG,
325                            "Cannot use sendMediaKeyEvent() from an unregistered RemoteController");
326                    return false;
327                }
328                if (!mEnabled) {
329                    Log.e(TAG, "Cannot use sendMediaKeyEvent() from a disabled RemoteController");
330                    return false;
331                }
332                pi = mClientPendingIntentCurrent;
333            }
334            if (pi != null) {
335                Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
336                intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
337                try {
338                    pi.send(mContext, 0, intent);
339                } catch (CanceledException e) {
340                    Log.e(TAG, "Error sending intent for media button down: ", e);
341                    return false;
342                }
343            } else {
344                Log.i(TAG, "No-op when sending key click, no receiver right now");
345                return false;
346            }
347        }
348        return true;
349    }
350
351
352    /**
353     * Sets the new playback position.
354     * This method can only be called on a registered RemoteController.
355     * @param timeMs a 0 or positive value for the new playback position, expressed in ms.
356     * @return true if the command to set the playback position was successfully sent.
357     * @throws IllegalArgumentException
358     */
359    public boolean seekTo(long timeMs) throws IllegalArgumentException {
360        if (!mEnabled) {
361            Log.e(TAG, "Cannot use seekTo() from a disabled RemoteController");
362            return false;
363        }
364        if (timeMs < 0) {
365            throw new IllegalArgumentException("illegal negative time value");
366        }
367        synchronized (mInfoLock) {
368            if (mCurrentSession != null) {
369                mCurrentSession.getTransportControls().seekTo(timeMs);
370            }
371        }
372        return true;
373    }
374
375
376    /**
377     * @hide
378     * @param wantBitmap
379     * @param width
380     * @param height
381     * @return true if successful
382     * @throws IllegalArgumentException
383     */
384    public boolean setArtworkConfiguration(boolean wantBitmap, int width, int height)
385            throws IllegalArgumentException {
386        synchronized (mInfoLock) {
387            if (wantBitmap) {
388                if ((width > 0) && (height > 0)) {
389                    if (width > mMaxBitmapDimension) { width = mMaxBitmapDimension; }
390                    if (height > mMaxBitmapDimension) { height = mMaxBitmapDimension; }
391                    mArtworkWidth = width;
392                    mArtworkHeight = height;
393                } else {
394                    throw new IllegalArgumentException("Invalid dimensions");
395                }
396            } else {
397                mArtworkWidth = -1;
398                mArtworkHeight = -1;
399            }
400        }
401        return true;
402    }
403
404    /**
405     * Set the maximum artwork image dimensions to be received in the metadata.
406     * No bitmaps will be received unless this has been specified.
407     * @param width the maximum width in pixels
408     * @param height  the maximum height in pixels
409     * @return true if the artwork dimension was successfully set.
410     * @throws IllegalArgumentException
411     */
412    public boolean setArtworkConfiguration(int width, int height) throws IllegalArgumentException {
413        return setArtworkConfiguration(true, width, height);
414    }
415
416    /**
417     * Prevents this RemoteController from receiving artwork images.
418     * @return true if receiving artwork images was successfully disabled.
419     */
420    public boolean clearArtworkConfiguration() {
421        return setArtworkConfiguration(false, -1, -1);
422    }
423
424
425    /**
426     * Default playback position synchronization mode where the RemoteControlClient is not
427     * asked regularly for its playback position to see if it has drifted from the estimated
428     * position.
429     */
430    public static final int POSITION_SYNCHRONIZATION_NONE = 0;
431
432    /**
433     * The playback position synchronization mode where the RemoteControlClient instances which
434     * expose their playback position to the framework, will be regularly polled to check
435     * whether any drift has been noticed between their estimated position and the one they report.
436     * Note that this mode should only ever be used when needing to display very accurate playback
437     * position, as regularly polling a RemoteControlClient for its position may have an impact
438     * on battery life (if applicable) when this query will trigger network transactions in the
439     * case of remote playback.
440     */
441    public static final int POSITION_SYNCHRONIZATION_CHECK = 1;
442
443    /**
444     * Set the playback position synchronization mode.
445     * Must be called on a registered RemoteController.
446     * @param sync {@link #POSITION_SYNCHRONIZATION_NONE} or {@link #POSITION_SYNCHRONIZATION_CHECK}
447     * @return true if the synchronization mode was successfully set.
448     * @throws IllegalArgumentException
449     */
450    public boolean setSynchronizationMode(int sync) throws IllegalArgumentException {
451        if ((sync != POSITION_SYNCHRONIZATION_NONE) && (sync != POSITION_SYNCHRONIZATION_CHECK)) {
452            throw new IllegalArgumentException("Unknown synchronization mode " + sync);
453        }
454        if (!mIsRegistered) {
455            Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController");
456            return false;
457        }
458        mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd,
459                POSITION_SYNCHRONIZATION_CHECK == sync);
460        return true;
461    }
462
463
464    /**
465     * Creates a {@link MetadataEditor} for updating metadata values of the editable keys of
466     * the current {@link RemoteControlClient}.
467     * This method can only be called on a registered RemoteController.
468     * @return a new MetadataEditor instance.
469     */
470    public MetadataEditor editMetadata() {
471        MetadataEditor editor = new MetadataEditor();
472        editor.mEditorMetadata = new Bundle();
473        editor.mEditorArtwork = null;
474        editor.mMetadataChanged = true;
475        editor.mArtworkChanged = true;
476        editor.mEditableKeys = 0;
477        return editor;
478    }
479
480    /**
481     * A class to read the metadata published by a {@link RemoteControlClient}, or send a
482     * {@link RemoteControlClient} new values for keys that can be edited.
483     */
484    public class MetadataEditor extends MediaMetadataEditor {
485        /**
486         * @hide
487         */
488        protected MetadataEditor() { }
489
490        /**
491         * @hide
492         */
493        protected MetadataEditor(Bundle metadata, long editableKeys) {
494            mEditorMetadata = metadata;
495            mEditableKeys = editableKeys;
496
497            mEditorArtwork = (Bitmap) metadata.getParcelable(
498                    String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK));
499            if (mEditorArtwork != null) {
500                cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK);
501            }
502
503            mMetadataChanged = true;
504            mArtworkChanged = true;
505            mApplied = false;
506        }
507
508        private void cleanupBitmapFromBundle(int key) {
509            if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) == METADATA_TYPE_BITMAP) {
510                mEditorMetadata.remove(String.valueOf(key));
511            }
512        }
513
514        /**
515         * Applies all of the metadata changes that have been set since the MediaMetadataEditor
516         * instance was created with {@link RemoteController#editMetadata()}
517         * or since {@link #clear()} was called.
518         */
519        public synchronized void apply() {
520            // "applying" a metadata bundle in RemoteController is only for sending edited
521            // key values back to the RemoteControlClient, so here we only care about the only
522            // editable key we support: RATING_KEY_BY_USER
523            if (!mMetadataChanged) {
524                return;
525            }
526            synchronized (mInfoLock) {
527                if (mCurrentSession != null) {
528                    if (mEditorMetadata.containsKey(
529                            String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
530                        Rating rating = (Rating) getObject(
531                                MediaMetadataEditor.RATING_KEY_BY_USER, null);
532                        if (rating != null) {
533                            mCurrentSession.getTransportControls().setRating(rating);
534                        }
535                    }
536                }
537            }
538            // NOT setting mApplied to true as this type of MetadataEditor will be applied
539            // multiple times, whenever the user of a RemoteController needs to change the
540            // metadata (e.g. user changes the rating of a song more than once during playback)
541            mApplied = false;
542        }
543
544    }
545
546
547    //==================================================
548    // Implementation of IRemoteControlDisplay interface
549    private static class RcDisplay extends IRemoteControlDisplay.Stub {
550        private final WeakReference<RemoteController> mController;
551
552        RcDisplay(RemoteController rc) {
553            mController = new WeakReference<RemoteController>(rc);
554        }
555
556        public void setCurrentClientId(int genId, PendingIntent clientMediaIntent,
557                boolean clearing) {
558            final RemoteController rc = mController.get();
559            if (rc == null) {
560                return;
561            }
562            boolean isNew = false;
563            synchronized(mGenLock) {
564                if (rc.mClientGenerationIdCurrent != genId) {
565                    rc.mClientGenerationIdCurrent = genId;
566                    isNew = true;
567                }
568            }
569            if (clientMediaIntent != null) {
570                sendMsg(rc.mEventHandler, MSG_NEW_PENDING_INTENT, SENDMSG_REPLACE,
571                        genId /*arg1*/, 0, clientMediaIntent /*obj*/, 0 /*delay*/);
572            }
573            if (isNew || clearing) {
574                sendMsg(rc.mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
575                        genId /*arg1*/, clearing ? 1 : 0, null /*obj*/, 0 /*delay*/);
576            }
577        }
578
579        public void setEnabled(boolean enabled) {
580            final RemoteController rc = mController.get();
581            if (rc == null) {
582                return;
583            }
584            sendMsg(rc.mEventHandler, MSG_DISPLAY_ENABLE, SENDMSG_REPLACE,
585                    enabled ? 1 : 0 /*arg1*/, 0, null /*obj*/, 0 /*delay*/);
586        }
587
588        public void setPlaybackState(int genId, int state,
589                long stateChangeTimeMs, long currentPosMs, float speed) {
590            final RemoteController rc = mController.get();
591            if (rc == null) {
592                return;
593            }
594            if (DEBUG) {
595                Log.d(TAG, "> new playback state: genId="+genId
596                        + " state="+ state
597                        + " changeTime="+ stateChangeTimeMs
598                        + " pos=" + currentPosMs
599                        + "ms speed=" + speed);
600            }
601
602            synchronized(mGenLock) {
603                if (rc.mClientGenerationIdCurrent != genId) {
604                    return;
605                }
606            }
607            final PlaybackInfo playbackInfo =
608                    new PlaybackInfo(state, stateChangeTimeMs, currentPosMs, speed);
609            sendMsg(rc.mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
610                    genId /*arg1*/, 0, playbackInfo /*obj*/, 0 /*delay*/);
611
612        }
613
614        public void setTransportControlInfo(int genId, int transportControlFlags,
615                int posCapabilities) {
616            final RemoteController rc = mController.get();
617            if (rc == null) {
618                return;
619            }
620            synchronized(mGenLock) {
621                if (rc.mClientGenerationIdCurrent != genId) {
622                    return;
623                }
624            }
625            sendMsg(rc.mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
626                    genId /*arg1*/, transportControlFlags /*arg2*/,
627                    null /*obj*/, 0 /*delay*/);
628        }
629
630        public void setMetadata(int genId, Bundle metadata) {
631            final RemoteController rc = mController.get();
632            if (rc == null) {
633                return;
634            }
635            if (DEBUG) { Log.e(TAG, "setMetadata("+genId+")"); }
636            if (metadata == null) {
637                return;
638            }
639            synchronized(mGenLock) {
640                if (rc.mClientGenerationIdCurrent != genId) {
641                    return;
642                }
643            }
644            sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
645                    genId /*arg1*/, 0 /*arg2*/,
646                    metadata /*obj*/, 0 /*delay*/);
647        }
648
649        public void setArtwork(int genId, Bitmap artwork) {
650            final RemoteController rc = mController.get();
651            if (rc == null) {
652                return;
653            }
654            if (DEBUG) { Log.v(TAG, "setArtwork("+genId+")"); }
655            synchronized(mGenLock) {
656                if (rc.mClientGenerationIdCurrent != genId) {
657                    return;
658                }
659            }
660            Bundle metadata = new Bundle(1);
661            metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), artwork);
662            sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
663                    genId /*arg1*/, 0 /*arg2*/,
664                    metadata /*obj*/, 0 /*delay*/);
665        }
666
667        public void setAllMetadata(int genId, Bundle metadata, Bitmap artwork) {
668            final RemoteController rc = mController.get();
669            if (rc == null) {
670                return;
671            }
672            if (DEBUG) { Log.e(TAG, "setAllMetadata("+genId+")"); }
673            if ((metadata == null) && (artwork == null)) {
674                return;
675            }
676            synchronized(mGenLock) {
677                if (rc.mClientGenerationIdCurrent != genId) {
678                    return;
679                }
680            }
681            if (metadata == null) {
682                metadata = new Bundle(1);
683            }
684            if (artwork != null) {
685                metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
686                        artwork);
687            }
688            sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
689                    genId /*arg1*/, 0 /*arg2*/,
690                    metadata /*obj*/, 0 /*delay*/);
691        }
692    }
693
694    /**
695     * This receives updates when the current session changes. This is
696     * registered to receive the updates on the handler thread so it can call
697     * directly into the appropriate methods.
698     */
699    private class MediaControllerCallback extends MediaController.Callback {
700        @Override
701        public void onPlaybackStateChanged(PlaybackState state) {
702            onNewPlaybackState(state);
703        }
704
705        @Override
706        public void onMetadataChanged(MediaMetadata metadata) {
707            onNewMediaMetadata(metadata);
708        }
709    }
710
711    /**
712     * Listens for changes to the active session stack and replaces the
713     * currently tracked session if it has changed.
714     */
715    private class TopTransportSessionListener extends MediaSessionManager.SessionListener {
716        @Override
717        public void onActiveSessionsChanged(List<MediaController> controllers) {
718            int size = controllers.size();
719            for (int i = 0; i < size; i++) {
720                MediaController controller = controllers.get(i);
721                long flags = controller.getFlags();
722                // We only care about sessions that handle transport controls,
723                // which will be true for apps using RCC
724                if ((flags & MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
725                    updateController(controller);
726                    return;
727                }
728            }
729            updateController(null);
730        }
731
732    }
733
734    //==================================================
735    // Event handling
736    private final EventHandler mEventHandler;
737    private final static int MSG_NEW_PENDING_INTENT = 0;
738    private final static int MSG_NEW_PLAYBACK_INFO =  1;
739    private final static int MSG_NEW_TRANSPORT_INFO = 2;
740    private final static int MSG_NEW_METADATA       = 3; // msg always has non-null obj parameter
741    private final static int MSG_CLIENT_CHANGE      = 4;
742    private final static int MSG_DISPLAY_ENABLE     = 5;
743    private final static int MSG_NEW_PLAYBACK_STATE = 6;
744    private final static int MSG_NEW_MEDIA_METADATA = 7;
745
746    private class EventHandler extends Handler {
747
748        public EventHandler(RemoteController rc, Looper looper) {
749            super(looper);
750        }
751
752        @Override
753        public void handleMessage(Message msg) {
754            switch(msg.what) {
755                case MSG_NEW_PENDING_INTENT:
756                    onNewPendingIntent(msg.arg1, (PendingIntent) msg.obj);
757                    break;
758                case MSG_NEW_PLAYBACK_INFO:
759                    onNewPlaybackInfo(msg.arg1, (PlaybackInfo) msg.obj);
760                    break;
761                case MSG_NEW_TRANSPORT_INFO:
762                    onNewTransportInfo(msg.arg1, msg.arg2);
763                    break;
764                case MSG_NEW_METADATA:
765                    onNewMetadata(msg.arg1, (Bundle)msg.obj);
766                    break;
767                case MSG_CLIENT_CHANGE:
768                    onClientChange(msg.arg1, msg.arg2 == 1);
769                    break;
770                case MSG_DISPLAY_ENABLE:
771                    onDisplayEnable(msg.arg1 == 1);
772                    break;
773                case MSG_NEW_PLAYBACK_STATE:
774                    // same as new playback info but using new apis
775                    onNewPlaybackState((PlaybackState) msg.obj);
776                    break;
777                case MSG_NEW_MEDIA_METADATA:
778                    onNewMediaMetadata((MediaMetadata) msg.obj);
779                    break;
780                default:
781                    Log.e(TAG, "unknown event " + msg.what);
782            }
783        }
784    }
785
786    /**
787     * @hide
788     */
789    void startListeningToSessions() {
790        final ComponentName listenerComponent = new ComponentName(mContext,
791                mOnClientUpdateListener.getClass());
792        mSessionManager.addActiveSessionsListener(mSessionListener, listenerComponent,
793                UserHandle.myUserId());
794        mSessionListener.onActiveSessionsChanged(mSessionManager
795                .getActiveSessions(listenerComponent));
796        if (DEBUG) {
797            Log.d(TAG, "Registered session listener with component " + listenerComponent
798                    + " for user " + UserHandle.myUserId());
799        }
800    }
801
802    /**
803     * @hide
804     */
805    void stopListeningToSessions() {
806        mSessionManager.removeActiveSessionsListener(mSessionListener);
807        if (DEBUG) {
808            Log.d(TAG, "Unregistered session listener for user "
809                    + UserHandle.myUserId());
810        }
811    }
812
813    /** If the msg is already queued, replace it with this one. */
814    private static final int SENDMSG_REPLACE = 0;
815    /** If the msg is already queued, ignore this one and leave the old. */
816    private static final int SENDMSG_NOOP = 1;
817    /** If the msg is already queued, queue this one and leave the old. */
818    private static final int SENDMSG_QUEUE = 2;
819
820    private static void sendMsg(Handler handler, int msg, int existingMsgPolicy,
821            int arg1, int arg2, Object obj, int delayMs) {
822        if (handler == null) {
823            Log.e(TAG, "null event handler, will not deliver message " + msg);
824            return;
825        }
826        if (existingMsgPolicy == SENDMSG_REPLACE) {
827            handler.removeMessages(msg);
828        } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
829            return;
830        }
831        handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs);
832    }
833
834    ///////////// These calls are used by the old APIs with RCC and RCD //////////////////////
835    private void onNewPendingIntent(int genId, PendingIntent pi) {
836        synchronized(mGenLock) {
837            if (mClientGenerationIdCurrent != genId) {
838                return;
839            }
840        }
841        synchronized(mInfoLock) {
842            mClientPendingIntentCurrent = pi;
843        }
844    }
845
846    private void onNewPlaybackInfo(int genId, PlaybackInfo pi) {
847        synchronized(mGenLock) {
848            if (mClientGenerationIdCurrent != genId) {
849                return;
850            }
851        }
852        final OnClientUpdateListener l;
853        synchronized(mInfoLock) {
854            l = this.mOnClientUpdateListener;
855            mLastPlaybackInfo = pi;
856        }
857        if (l != null) {
858            if (pi.mCurrentPosMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
859                l.onClientPlaybackStateUpdate(pi.mState);
860            } else {
861                l.onClientPlaybackStateUpdate(pi.mState, pi.mStateChangeTimeMs, pi.mCurrentPosMs,
862                        pi.mSpeed);
863            }
864        }
865    }
866
867    private void onNewTransportInfo(int genId, int transportControlFlags) {
868        synchronized(mGenLock) {
869            if (mClientGenerationIdCurrent != genId) {
870                return;
871            }
872        }
873        final OnClientUpdateListener l;
874        synchronized(mInfoLock) {
875            l = mOnClientUpdateListener;
876        }
877        if (l != null) {
878            l.onClientTransportControlUpdate(transportControlFlags);
879        }
880    }
881
882    /**
883     * @param genId
884     * @param metadata guaranteed to be always non-null
885     */
886    private void onNewMetadata(int genId, Bundle metadata) {
887        synchronized(mGenLock) {
888            if (mClientGenerationIdCurrent != genId) {
889                return;
890            }
891        }
892        final OnClientUpdateListener l;
893        final MetadataEditor metadataEditor;
894        // prepare the received Bundle to be used inside a MetadataEditor
895        final long editableKeys = metadata.getLong(
896                String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK), 0);
897        if (editableKeys != 0) {
898            metadata.remove(String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK));
899        }
900        synchronized(mInfoLock) {
901            l = mOnClientUpdateListener;
902            if ((mMetadataEditor != null) && (mMetadataEditor.mEditorMetadata != null)) {
903                if (mMetadataEditor.mEditorMetadata != metadata) {
904                    // existing metadata, merge existing and new
905                    mMetadataEditor.mEditorMetadata.putAll(metadata);
906                }
907
908                mMetadataEditor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK,
909                        (Bitmap)metadata.getParcelable(
910                                String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK)));
911                mMetadataEditor.cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK);
912            } else {
913                mMetadataEditor = new MetadataEditor(metadata, editableKeys);
914            }
915            metadataEditor = mMetadataEditor;
916        }
917        if (l != null) {
918            l.onClientMetadataUpdate(metadataEditor);
919        }
920    }
921
922    private void onClientChange(int genId, boolean clearing) {
923        synchronized(mGenLock) {
924            if (mClientGenerationIdCurrent != genId) {
925                return;
926            }
927        }
928        final OnClientUpdateListener l;
929        synchronized(mInfoLock) {
930            l = mOnClientUpdateListener;
931            mMetadataEditor = null;
932        }
933        if (l != null) {
934            l.onClientChange(clearing);
935        }
936    }
937
938    private void onDisplayEnable(boolean enabled) {
939        final OnClientUpdateListener l;
940        synchronized(mInfoLock) {
941            mEnabled = enabled;
942            l = this.mOnClientUpdateListener;
943        }
944        if (!enabled) {
945            // when disabling, reset all info sent to the user
946            final int genId;
947            synchronized (mGenLock) {
948                genId = mClientGenerationIdCurrent;
949            }
950            // send "stopped" state, happened "now", playback position is 0, speed 0.0f
951            final PlaybackInfo pi = new PlaybackInfo(RemoteControlClient.PLAYSTATE_STOPPED,
952                    SystemClock.elapsedRealtime() /*stateChangeTimeMs*/,
953                    0 /*currentPosMs*/, 0.0f /*speed*/);
954            sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
955                    genId /*arg1*/, 0 /*arg2, ignored*/, pi /*obj*/, 0 /*delay*/);
956            // send "blank" transport control info: no controls are supported
957            sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
958                    genId /*arg1*/, 0 /*arg2, no flags*/,
959                    null /*obj, ignored*/, 0 /*delay*/);
960            // send dummy metadata with empty string for title and artist, duration of 0
961            Bundle metadata = new Bundle(3);
962            metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE), "");
963            metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST), "");
964            metadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION), 0);
965            sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
966                    genId /*arg1*/, 0 /*arg2, ignored*/, metadata /*obj*/, 0 /*delay*/);
967        }
968    }
969
970    ///////////// These calls are used by the new APIs with Sessions //////////////////////
971    private void updateController(MediaController controller) {
972        if (DEBUG) {
973            Log.d(TAG, "Updating controller to " + controller + " previous controller is "
974                    + mCurrentSession);
975        }
976        synchronized (mInfoLock) {
977            if (controller == null) {
978                if (mCurrentSession != null) {
979                    mCurrentSession.removeCallback(mSessionCb);
980                    mCurrentSession = null;
981                    sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
982                            0 /* genId */, 1 /* clearing */, null /* obj */, 0 /* delay */);
983                }
984            } else if (mCurrentSession == null
985                    || !controller.getSessionInfo().getId()
986                            .equals(mCurrentSession.getSessionInfo().getId())) {
987                if (mCurrentSession != null) {
988                    mCurrentSession.removeCallback(mSessionCb);
989                }
990                sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
991                        0 /* genId */, 0 /* clearing */, null /* obj */, 0 /* delay */);
992                mCurrentSession = controller;
993                mCurrentSession.addCallback(mSessionCb, mEventHandler);
994
995                PlaybackState state = controller.getPlaybackState();
996                sendMsg(mEventHandler, MSG_NEW_PLAYBACK_STATE, SENDMSG_REPLACE,
997                        0 /* genId */, 0, state /* obj */, 0 /* delay */);
998
999                MediaMetadata metadata = controller.getMetadata();
1000                sendMsg(mEventHandler, MSG_NEW_MEDIA_METADATA, SENDMSG_REPLACE,
1001                        0 /* arg1 */, 0 /* arg2 */, metadata /* obj */, 0 /* delay */);
1002            }
1003            // else same controller, no need to update
1004        }
1005    }
1006
1007    private void onNewPlaybackState(PlaybackState state) {
1008        final OnClientUpdateListener l;
1009        synchronized (mInfoLock) {
1010            l = this.mOnClientUpdateListener;
1011        }
1012        if (l != null) {
1013            int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE : PlaybackState
1014                    .getRccStateFromState(state.getState());
1015            if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) {
1016                l.onClientPlaybackStateUpdate(playstate);
1017            } else {
1018                l.onClientPlaybackStateUpdate(playstate, state.getLastPositionUpdateTime(),
1019                        state.getPosition(), state.getPlaybackSpeed());
1020            }
1021            if (state != null) {
1022                l.onClientTransportControlUpdate(PlaybackState.getRccControlFlagsFromActions(state
1023                        .getActions()));
1024            }
1025        }
1026    }
1027
1028    private void onNewMediaMetadata(MediaMetadata metadata) {
1029        if (metadata == null) {
1030            // RemoteController only handles non-null metadata
1031            return;
1032        }
1033        final OnClientUpdateListener l;
1034        final MetadataEditor metadataEditor;
1035        // prepare the received Bundle to be used inside a MetadataEditor
1036        synchronized(mInfoLock) {
1037            l = mOnClientUpdateListener;
1038            boolean canRate = mCurrentSession != null
1039                    && mCurrentSession.getRatingType() != Rating.RATING_NONE;
1040            long editableKeys = canRate ? MediaMetadataEditor.RATING_KEY_BY_USER : 0;
1041            Bundle legacyMetadata = MediaSessionLegacyHelper.getOldMetadata(metadata,
1042                    mArtworkWidth, mArtworkHeight);
1043            mMetadataEditor = new MetadataEditor(legacyMetadata, editableKeys);
1044            metadataEditor = mMetadataEditor;
1045        }
1046        if (l != null) {
1047            l.onClientMetadataUpdate(metadataEditor);
1048        }
1049    }
1050
1051    //==================================================
1052    private static class PlaybackInfo {
1053        int mState;
1054        long mStateChangeTimeMs;
1055        long mCurrentPosMs;
1056        float mSpeed;
1057
1058        PlaybackInfo(int state, long stateChangeTimeMs, long currentPosMs, float speed) {
1059            mState = state;
1060            mStateChangeTimeMs = stateChangeTimeMs;
1061            mCurrentPosMs = currentPosMs;
1062            mSpeed = speed;
1063        }
1064    }
1065
1066    /**
1067     * @hide
1068     * Used by AudioManager to mark this instance as registered.
1069     * @param registered
1070     */
1071    void setIsRegistered(boolean registered) {
1072        synchronized (mInfoLock) {
1073            mIsRegistered = registered;
1074        }
1075    }
1076
1077    /**
1078     * @hide
1079     * Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl
1080     * @return
1081     */
1082    RcDisplay getRcDisplay() {
1083        return mRcd;
1084    }
1085
1086    /**
1087     * @hide
1088     * Used by AudioManager to read the current artwork dimension
1089     * @return array containing width (index 0) and height (index 1) of currently set artwork size
1090     */
1091    int[] getArtworkSize() {
1092        synchronized (mInfoLock) {
1093            int[] size = { mArtworkWidth, mArtworkHeight };
1094            return size;
1095        }
1096    }
1097
1098    /**
1099     * @hide
1100     * Used by AudioManager to access user listener receiving the client update notifications
1101     * @return
1102     */
1103    OnClientUpdateListener getUpdateListener() {
1104        return mOnClientUpdateListener;
1105    }
1106}
1107