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