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