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