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