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