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