MediaSessionRecord.java revision a8f951462791a16f47e8c07e552232f31dcefac5
1/* 2 * Copyright (C) 2014 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 com.android.server.media; 18 19import android.content.Intent; 20import android.content.pm.PackageManager; 21import android.media.routeprovider.RouteRequest; 22import android.media.session.ISessionController; 23import android.media.session.ISessionControllerCallback; 24import android.media.session.ISession; 25import android.media.session.ISessionCallback; 26import android.media.session.SessionController; 27import android.media.session.MediaMetadata; 28import android.media.session.RouteCommand; 29import android.media.session.RouteInfo; 30import android.media.session.RouteOptions; 31import android.media.session.RouteEvent; 32import android.media.session.Session; 33import android.media.session.SessionInfo; 34import android.media.session.RouteInterface; 35import android.media.session.PlaybackState; 36import android.media.Rating; 37import android.os.Bundle; 38import android.os.Handler; 39import android.os.IBinder; 40import android.os.Looper; 41import android.os.Message; 42import android.os.RemoteException; 43import android.os.ResultReceiver; 44import android.os.SystemClock; 45import android.text.TextUtils; 46import android.util.Log; 47import android.util.Pair; 48import android.util.Slog; 49import android.view.KeyEvent; 50 51import java.io.PrintWriter; 52import java.util.ArrayList; 53import java.util.List; 54import java.util.UUID; 55 56/** 57 * This is the system implementation of a Session. Apps will interact with the 58 * MediaSession wrapper class instead. 59 */ 60public class MediaSessionRecord implements IBinder.DeathRecipient { 61 private static final String TAG = "MediaSessionRecord"; 62 63 /** 64 * These are the playback states that count as currently active. 65 */ 66 private static final int[] ACTIVE_STATES = { 67 PlaybackState.PLAYSTATE_FAST_FORWARDING, 68 PlaybackState.PLAYSTATE_REWINDING, 69 PlaybackState.PLAYSTATE_SKIPPING_BACKWARDS, 70 PlaybackState.PLAYSTATE_SKIPPING_FORWARDS, 71 PlaybackState.PLAYSTATE_BUFFERING, 72 PlaybackState.PLAYSTATE_CONNECTING, 73 PlaybackState.PLAYSTATE_PLAYING }; 74 75 /** 76 * The length of time a session will still be considered active after 77 * pausing in ms. 78 */ 79 private static final int ACTIVE_BUFFER = 30000; 80 81 private final MessageHandler mHandler; 82 83 private final int mPid; 84 private final SessionInfo mSessionInfo; 85 private final String mTag; 86 private final ControllerStub mController; 87 private final SessionStub mSession; 88 private final SessionCb mSessionCb; 89 private final MediaSessionService mService; 90 91 private final Object mLock = new Object(); 92 private final ArrayList<ISessionControllerCallback> mControllerCallbacks = 93 new ArrayList<ISessionControllerCallback>(); 94 private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>(); 95 96 private RouteInfo mRoute; 97 private RouteOptions mRequest; 98 private RouteConnectionRecord mConnection; 99 // TODO define a RouteState class with relevant info 100 private int mRouteState; 101 private long mFlags; 102 103 // TransportPerformer fields 104 105 private MediaMetadata mMetadata; 106 private PlaybackState mPlaybackState; 107 private int mRatingType; 108 private long mLastActiveTime; 109 // End TransportPerformer fields 110 111 private boolean mIsActive = false; 112 113 public MediaSessionRecord(int pid, String packageName, ISessionCallback cb, String tag, 114 MediaSessionService service, Handler handler) { 115 mPid = pid; 116 mSessionInfo = new SessionInfo(UUID.randomUUID().toString(), packageName); 117 mTag = tag; 118 mController = new ControllerStub(); 119 mSession = new SessionStub(); 120 mSessionCb = new SessionCb(cb); 121 mService = service; 122 mHandler = new MessageHandler(handler.getLooper()); 123 } 124 125 /** 126 * Get the binder for the {@link Session}. 127 * 128 * @return The session binder apps talk to. 129 */ 130 public ISession getSessionBinder() { 131 return mSession; 132 } 133 134 /** 135 * Get the binder for the {@link SessionController}. 136 * 137 * @return The controller binder apps talk to. 138 */ 139 public ISessionController getControllerBinder() { 140 return mController; 141 } 142 143 /** 144 * Get the set of route requests this session is interested in. 145 * 146 * @return The list of RouteRequests 147 */ 148 public List<RouteRequest> getRouteRequests() { 149 return mRequests; 150 } 151 152 /** 153 * Get the route this session is currently on. 154 * 155 * @return The route the session is on. 156 */ 157 public RouteInfo getRoute() { 158 return mRoute; 159 } 160 161 /** 162 * Get the info for this session. 163 * 164 * @return Info that identifies this session. 165 */ 166 public SessionInfo getSessionInfo() { 167 return mSessionInfo; 168 } 169 170 /** 171 * Get this session's flags. 172 * 173 * @return The flags for this session. 174 */ 175 public long getFlags() { 176 return mFlags; 177 } 178 179 /** 180 * Check if this session has the specified flag. 181 * 182 * @param flag The flag to check. 183 * @return True if this session has that flag set, false otherwise. 184 */ 185 public boolean hasFlag(int flag) { 186 return (mFlags & flag) != 0; 187 } 188 189 /** 190 * Check if this session has system priorty and should receive media buttons 191 * before any other sessions. 192 * 193 * @return True if this is a system priority session, false otherwise 194 */ 195 public boolean isSystemPriority() { 196 return (mFlags & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0; 197 } 198 199 /** 200 * Set the selected route. This does not connect to the route, just notifies 201 * the app that a new route has been selected. 202 * 203 * @param route The route that was selected. 204 */ 205 public void selectRoute(RouteInfo route) { 206 synchronized (mLock) { 207 if (route != mRoute) { 208 if (mConnection != null) { 209 mConnection.disconnect(); 210 mConnection = null; 211 } 212 } 213 mRoute = route; 214 } 215 mSessionCb.sendRouteChange(route); 216 } 217 218 /** 219 * Update the state of the route this session is using and notify the 220 * session. 221 * 222 * @param state The new state of the route. 223 */ 224 public void setRouteState(int state) { 225 mSessionCb.sendRouteStateChange(state); 226 } 227 228 /** 229 * Send an event to this session from the route it is using. 230 * 231 * @param event The event to send. 232 */ 233 public void sendRouteEvent(RouteEvent event) { 234 mSessionCb.sendRouteEvent(event); 235 } 236 237 /** 238 * Set the connection to use for the selected route and notify the app it is 239 * now connected. 240 * 241 * @param route The route the connection is to. 242 * @param request The request that was used to connect. 243 * @param connection The connection to the route. 244 * @return True if this connection is still valid, false if it is stale. 245 */ 246 public boolean setRouteConnected(RouteInfo route, RouteOptions request, 247 RouteConnectionRecord connection) { 248 synchronized (mLock) { 249 if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) { 250 Log.w(TAG, "setRouteConnected: connected route is stale"); 251 // TODO figure out disconnection path 252 return false; 253 } 254 if (request != mRequest) { 255 Log.w(TAG, "setRouteConnected: connection request is stale"); 256 // TODO figure out disconnection path 257 return false; 258 } 259 mConnection = connection; 260 mConnection.setListener(mConnectionListener); 261 mSessionCb.sendRouteConnected(); 262 } 263 return true; 264 } 265 266 /** 267 * Check if this session has been set to active by the app. 268 * 269 * @return True if the session is active, false otherwise. 270 */ 271 public boolean isActive() { 272 return mIsActive; 273 } 274 275 /** 276 * Check if the session is currently performing playback. This will also 277 * return true if the session was recently paused. 278 * 279 * @return True if the session is performing playback, false otherwise. 280 */ 281 public boolean isPlaybackActive() { 282 int state = mPlaybackState == null ? 0 : mPlaybackState.getState(); 283 if (isActiveState(state)) { 284 return true; 285 } 286 if (state == mPlaybackState.PLAYSTATE_PAUSED) { 287 long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime; 288 if (inactiveTime < ACTIVE_BUFFER) { 289 return true; 290 } 291 } 292 return false; 293 } 294 295 public boolean isTransportControlEnabled() { 296 return hasFlag(Session.FLAG_HANDLES_TRANSPORT_CONTROLS); 297 } 298 299 @Override 300 public void binderDied() { 301 mService.sessionDied(this); 302 } 303 304 public void dump(PrintWriter pw, String prefix) { 305 pw.println(prefix + mTag + " " + this); 306 307 final String indent = prefix + " "; 308 pw.println(indent + "pid=" + mPid); 309 pw.println(indent + "info=" + mSessionInfo.toString()); 310 pw.println(indent + "published=" + mIsActive); 311 pw.println(indent + "flags=" + mFlags); 312 pw.println(indent + "rating type=" + mRatingType); 313 pw.println(indent + "controllers: " + mControllerCallbacks.size()); 314 pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString())); 315 pw.println(indent + "metadata:" + getShortMetadataString()); 316 pw.println(indent + "route requests {"); 317 int size = mRequests.size(); 318 for (int i = 0; i < size; i++) { 319 pw.println(indent + " " + mRequests.get(i).toString()); 320 } 321 pw.println(indent + "}"); 322 pw.println(indent + "route=" + (mRoute == null ? null : mRoute.toString())); 323 pw.println(indent + "connection=" + (mConnection == null ? null : mConnection.toString())); 324 pw.println(indent + "params=" + (mRequest == null ? null : mRequest.toString())); 325 } 326 327 private boolean isActiveState(int state) { 328 for (int i = 0; i < ACTIVE_STATES.length; i++) { 329 if (ACTIVE_STATES[i] == state) { 330 return true; 331 } 332 } 333 return false; 334 } 335 336 private String getShortMetadataString() { 337 int fields = mMetadata == null ? 0 : mMetadata.size(); 338 String title = mMetadata == null ? null : mMetadata 339 .getString(MediaMetadata.METADATA_KEY_TITLE); 340 return "size=" + fields + ", title=" + title; 341 } 342 343 private void onDestroy() { 344 mService.destroySession(this); 345 } 346 347 private void pushPlaybackStateUpdate() { 348 synchronized (mLock) { 349 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 350 ISessionControllerCallback cb = mControllerCallbacks.get(i); 351 try { 352 cb.onPlaybackStateChanged(mPlaybackState); 353 } catch (RemoteException e) { 354 Log.w(TAG, "Removing dead callback in pushPlaybackStateUpdate.", e); 355 mControllerCallbacks.remove(i); 356 } 357 } 358 } 359 } 360 361 private void pushMetadataUpdate() { 362 synchronized (mLock) { 363 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 364 ISessionControllerCallback cb = mControllerCallbacks.get(i); 365 try { 366 cb.onMetadataChanged(mMetadata); 367 } catch (RemoteException e) { 368 Log.w(TAG, "Removing dead callback in pushMetadataUpdate.", e); 369 mControllerCallbacks.remove(i); 370 } 371 } 372 } 373 } 374 375 private void pushRouteUpdate() { 376 synchronized (mLock) { 377 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 378 ISessionControllerCallback cb = mControllerCallbacks.get(i); 379 try { 380 cb.onRouteChanged(mRoute); 381 } catch (RemoteException e) { 382 Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e); 383 mControllerCallbacks.remove(i); 384 } 385 } 386 } 387 } 388 389 private void pushEvent(String event, Bundle data) { 390 synchronized (mLock) { 391 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 392 ISessionControllerCallback cb = mControllerCallbacks.get(i); 393 try { 394 cb.onEvent(event, data); 395 } catch (RemoteException e) { 396 Log.w(TAG, "Error with callback in pushEvent.", e); 397 } 398 } 399 } 400 } 401 402 private void pushRouteCommand(RouteCommand command, ResultReceiver cb) { 403 synchronized (mLock) { 404 if (mRoute == null || !TextUtils.equals(command.getRouteInfo(), mRoute.getId())) { 405 if (cb != null) { 406 cb.send(RouteInterface.RESULT_ROUTE_IS_STALE, null); 407 return; 408 } 409 } 410 if (mConnection != null) { 411 mConnection.sendCommand(command, cb); 412 } else if (cb != null) { 413 cb.send(RouteInterface.RESULT_NOT_CONNECTED, null); 414 } 415 } 416 } 417 418 private PlaybackState getStateWithUpdatedPosition() { 419 PlaybackState state = mPlaybackState; 420 long duration = -1; 421 if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { 422 duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); 423 } 424 PlaybackState result = null; 425 if (state != null) { 426 if (state.getState() == PlaybackState.PLAYSTATE_PLAYING 427 || state.getState() == PlaybackState.PLAYSTATE_FAST_FORWARDING 428 || state.getState() == PlaybackState.PLAYSTATE_REWINDING) { 429 long updateTime = state.getLastPositionUpdateTime(); 430 if (updateTime > 0) { 431 long position = (long) (state.getRate() 432 * (SystemClock.elapsedRealtime() - updateTime)) + state.getPosition(); 433 if (duration >= 0 && position > duration) { 434 position = duration; 435 } else if (position < 0) { 436 position = 0; 437 } 438 result = new PlaybackState(state); 439 result.setState(state.getState(), position, state.getRate()); 440 } 441 } 442 } 443 return result == null ? state : result; 444 } 445 446 private final RouteConnectionRecord.Listener mConnectionListener 447 = new RouteConnectionRecord.Listener() { 448 @Override 449 public void onEvent(RouteEvent event) { 450 RouteEvent eventForSession = new RouteEvent(null, event.getIface(), 451 event.getEvent(), event.getExtras()); 452 mSessionCb.sendRouteEvent(eventForSession); 453 } 454 455 @Override 456 public void disconnect() { 457 // TODO 458 } 459 }; 460 461 private final class SessionStub extends ISession.Stub { 462 @Override 463 public void destroy() { 464 onDestroy(); 465 } 466 467 @Override 468 public void sendEvent(String event, Bundle data) { 469 mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data); 470 } 471 472 @Override 473 public ISessionController getController() { 474 return mController; 475 } 476 477 @Override 478 public void setActive(boolean active) { 479 mIsActive = active; 480 mService.updateSession(MediaSessionRecord.this); 481 mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); 482 } 483 484 @Override 485 public void setFlags(int flags) { 486 if ((flags & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { 487 int pid = getCallingPid(); 488 int uid = getCallingUid(); 489 mService.enforcePhoneStatePermission(pid, uid); 490 } 491 mFlags = flags; 492 mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); 493 } 494 495 @Override 496 public void setMetadata(MediaMetadata metadata) { 497 mMetadata = metadata; 498 mHandler.post(MessageHandler.MSG_UPDATE_METADATA); 499 } 500 501 @Override 502 public void setPlaybackState(PlaybackState state) { 503 int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState(); 504 int newState = state == null ? 0 : state.getState(); 505 if (isActiveState(oldState) && newState == PlaybackState.PLAYSTATE_PAUSED) { 506 mLastActiveTime = SystemClock.elapsedRealtime(); 507 } 508 mPlaybackState = state; 509 mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState); 510 mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE); 511 } 512 513 @Override 514 public void setRatingType(int type) { 515 mRatingType = type; 516 } 517 518 @Override 519 public void sendRouteCommand(RouteCommand command, ResultReceiver cb) { 520 mHandler.post(MessageHandler.MSG_SEND_COMMAND, 521 new Pair<RouteCommand, ResultReceiver>(command, cb)); 522 } 523 524 @Override 525 public boolean setRoute(RouteInfo route) throws RemoteException { 526 // TODO decide if allowed to set route and if the route exists 527 return false; 528 } 529 530 @Override 531 public void connectToRoute(RouteInfo route, RouteOptions request) 532 throws RemoteException { 533 if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) { 534 throw new RemoteException("RouteInfo does not match current route"); 535 } 536 mService.connectToRoute(MediaSessionRecord.this, route, request); 537 mRequest = request; 538 } 539 540 @Override 541 public void setRouteOptions(List<RouteOptions> options) throws RemoteException { 542 mRequests.clear(); 543 for (int i = options.size() - 1; i >= 0; i--) { 544 RouteRequest request = new RouteRequest(mSessionInfo, options.get(i), 545 false); 546 mRequests.add(request); 547 } 548 } 549 } 550 551 class SessionCb { 552 private final ISessionCallback mCb; 553 554 public SessionCb(ISessionCallback cb) { 555 mCb = cb; 556 } 557 558 public void sendMediaButton(KeyEvent keyEvent) { 559 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 560 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 561 try { 562 mCb.onMediaButton(mediaButtonIntent); 563 } catch (RemoteException e) { 564 Slog.e(TAG, "Remote failure in sendMediaRequest.", e); 565 } 566 } 567 568 public void sendCommand(String command, Bundle extras, ResultReceiver cb) { 569 try { 570 mCb.onCommand(command, extras, cb); 571 } catch (RemoteException e) { 572 Slog.e(TAG, "Remote failure in sendCommand.", e); 573 } 574 } 575 576 public void sendRouteChange(RouteInfo route) { 577 try { 578 mCb.onRequestRouteChange(route); 579 } catch (RemoteException e) { 580 Slog.e(TAG, "Remote failure in sendRouteChange.", e); 581 } 582 } 583 584 public void sendRouteStateChange(int state) { 585 try { 586 mCb.onRouteStateChange(state); 587 } catch (RemoteException e) { 588 Slog.e(TAG, "Remote failure in sendRouteStateChange.", e); 589 } 590 } 591 592 public void sendRouteEvent(RouteEvent event) { 593 try { 594 mCb.onRouteEvent(event); 595 } catch (RemoteException e) { 596 Slog.e(TAG, "Remote failure in sendRouteEvent.", e); 597 } 598 } 599 600 public void sendRouteConnected() { 601 try { 602 mCb.onRouteConnected(mRoute, mRequest); 603 } catch (RemoteException e) { 604 Slog.e(TAG, "Remote failure in sendRouteStateChange.", e); 605 } 606 } 607 608 public void play() { 609 try { 610 mCb.onPlay(); 611 } catch (RemoteException e) { 612 Slog.e(TAG, "Remote failure in play.", e); 613 } 614 } 615 616 public void pause() { 617 try { 618 mCb.onPause(); 619 } catch (RemoteException e) { 620 Slog.e(TAG, "Remote failure in pause.", e); 621 } 622 } 623 624 public void stop() { 625 try { 626 mCb.onStop(); 627 } catch (RemoteException e) { 628 Slog.e(TAG, "Remote failure in stop.", e); 629 } 630 } 631 632 public void next() { 633 try { 634 mCb.onNext(); 635 } catch (RemoteException e) { 636 Slog.e(TAG, "Remote failure in next.", e); 637 } 638 } 639 640 public void previous() { 641 try { 642 mCb.onPrevious(); 643 } catch (RemoteException e) { 644 Slog.e(TAG, "Remote failure in previous.", e); 645 } 646 } 647 648 public void fastForward() { 649 try { 650 mCb.onFastForward(); 651 } catch (RemoteException e) { 652 Slog.e(TAG, "Remote failure in fastForward.", e); 653 } 654 } 655 656 public void rewind() { 657 try { 658 mCb.onRewind(); 659 } catch (RemoteException e) { 660 Slog.e(TAG, "Remote failure in rewind.", e); 661 } 662 } 663 664 public void seekTo(long pos) { 665 try { 666 mCb.onSeekTo(pos); 667 } catch (RemoteException e) { 668 Slog.e(TAG, "Remote failure in seekTo.", e); 669 } 670 } 671 672 public void rate(Rating rating) { 673 try { 674 mCb.onRate(rating); 675 } catch (RemoteException e) { 676 Slog.e(TAG, "Remote failure in rate.", e); 677 } 678 } 679 } 680 681 class ControllerStub extends ISessionController.Stub { 682 @Override 683 public void sendCommand(String command, Bundle extras, ResultReceiver cb) 684 throws RemoteException { 685 mSessionCb.sendCommand(command, extras, cb); 686 } 687 688 @Override 689 public void sendMediaButton(KeyEvent mediaButtonIntent) { 690 mSessionCb.sendMediaButton(mediaButtonIntent); 691 } 692 693 @Override 694 public void registerCallbackListener(ISessionControllerCallback cb) { 695 synchronized (mLock) { 696 if (!mControllerCallbacks.contains(cb)) { 697 mControllerCallbacks.add(cb); 698 } 699 } 700 } 701 702 @Override 703 public void unregisterCallbackListener(ISessionControllerCallback cb) 704 throws RemoteException { 705 synchronized (mLock) { 706 mControllerCallbacks.remove(cb); 707 } 708 } 709 710 @Override 711 public void play() throws RemoteException { 712 mSessionCb.play(); 713 } 714 715 @Override 716 public void pause() throws RemoteException { 717 mSessionCb.pause(); 718 } 719 720 @Override 721 public void stop() throws RemoteException { 722 mSessionCb.stop(); 723 } 724 725 @Override 726 public void next() throws RemoteException { 727 mSessionCb.next(); 728 } 729 730 @Override 731 public void previous() throws RemoteException { 732 mSessionCb.previous(); 733 } 734 735 @Override 736 public void fastForward() throws RemoteException { 737 mSessionCb.fastForward(); 738 } 739 740 @Override 741 public void rewind() throws RemoteException { 742 mSessionCb.rewind(); 743 } 744 745 @Override 746 public void seekTo(long pos) throws RemoteException { 747 mSessionCb.seekTo(pos); 748 } 749 750 @Override 751 public void rate(Rating rating) throws RemoteException { 752 mSessionCb.rate(rating); 753 } 754 755 756 @Override 757 public MediaMetadata getMetadata() { 758 return mMetadata; 759 } 760 761 @Override 762 public PlaybackState getPlaybackState() { 763 return getStateWithUpdatedPosition(); 764 } 765 766 @Override 767 public int getRatingType() { 768 return mRatingType; 769 } 770 771 @Override 772 public boolean isTransportControlEnabled() { 773 return MediaSessionRecord.this.isTransportControlEnabled(); 774 } 775 776 @Override 777 public void showRoutePicker() { 778 mService.showRoutePickerForSession(MediaSessionRecord.this); 779 } 780 } 781 782 private class MessageHandler extends Handler { 783 private static final int MSG_UPDATE_METADATA = 1; 784 private static final int MSG_UPDATE_PLAYBACK_STATE = 2; 785 private static final int MSG_UPDATE_ROUTE = 3; 786 private static final int MSG_SEND_EVENT = 4; 787 private static final int MSG_UPDATE_ROUTE_FILTERS = 5; 788 private static final int MSG_SEND_COMMAND = 6; 789 private static final int MSG_UPDATE_SESSION_STATE = 7; 790 791 public MessageHandler(Looper looper) { 792 super(looper); 793 } 794 @Override 795 public void handleMessage(Message msg) { 796 switch (msg.what) { 797 case MSG_UPDATE_METADATA: 798 pushMetadataUpdate(); 799 break; 800 case MSG_UPDATE_PLAYBACK_STATE: 801 pushPlaybackStateUpdate(); 802 break; 803 case MSG_UPDATE_ROUTE: 804 pushRouteUpdate(); 805 break; 806 case MSG_SEND_EVENT: 807 pushEvent((String) msg.obj, msg.getData()); 808 break; 809 case MSG_SEND_COMMAND: 810 Pair<RouteCommand, ResultReceiver> cmd = 811 (Pair<RouteCommand, ResultReceiver>) msg.obj; 812 pushRouteCommand(cmd.first, cmd.second); 813 break; 814 case MSG_UPDATE_SESSION_STATE: 815 // TODO add session state 816 break; 817 } 818 } 819 820 public void post(int what) { 821 post(what, null); 822 } 823 824 public void post(int what, Object obj) { 825 obtainMessage(what, obj).sendToTarget(); 826 } 827 828 public void post(int what, Object obj, Bundle data) { 829 Message msg = obtainMessage(what, obj); 830 msg.setData(data); 831 msg.sendToTarget(); 832 } 833 } 834 835} 836