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