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