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