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