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