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