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