Home | History | Annotate | Download | only in media
      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 
     17 package android.media;
     18 
     19 import android.app.ActivityManager;
     20 import android.app.PendingIntent;
     21 import android.app.PendingIntent.CanceledException;
     22 import android.content.ComponentName;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.graphics.Bitmap;
     26 import android.media.session.MediaController;
     27 import android.media.session.MediaSession;
     28 import android.media.session.MediaSessionLegacyHelper;
     29 import android.media.session.MediaSessionManager;
     30 import android.media.session.PlaybackState;
     31 import android.os.Bundle;
     32 import android.os.Handler;
     33 import android.os.Looper;
     34 import android.os.Message;
     35 import android.os.SystemClock;
     36 import android.os.UserHandle;
     37 import android.util.DisplayMetrics;
     38 import android.util.Log;
     39 import android.view.KeyEvent;
     40 
     41 import java.lang.ref.WeakReference;
     42 import 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