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