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