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