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