MediaSessionRecord.java revision e7880d8eb1903d42e4e2a90c99b58e2240e01e82
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.Intent; 20import android.content.pm.PackageManager; 21import android.media.routeprovider.RouteRequest; 22import android.media.session.ISessionController; 23import android.media.session.ISessionControllerCallback; 24import android.media.session.ISession; 25import android.media.session.ISessionCallback; 26import android.media.session.SessionController; 27import android.media.session.MediaMetadata; 28import android.media.session.RouteCommand; 29import android.media.session.RouteInfo; 30import android.media.session.RouteOptions; 31import android.media.session.RouteEvent; 32import android.media.session.Session; 33import android.media.session.SessionInfo; 34import android.media.session.RouteInterface; 35import android.media.session.PlaybackState; 36import android.media.Rating; 37import android.os.Bundle; 38import android.os.Handler; 39import android.os.IBinder; 40import android.os.Looper; 41import android.os.Message; 42import android.os.RemoteException; 43import android.os.ResultReceiver; 44import android.os.SystemClock; 45import android.text.TextUtils; 46import android.util.Log; 47import android.util.Pair; 48import android.util.Slog; 49import android.view.KeyEvent; 50 51import java.io.PrintWriter; 52import java.util.ArrayList; 53import java.util.List; 54import java.util.UUID; 55 56/** 57 * This is the system implementation of a Session. Apps will interact with the 58 * MediaSession wrapper class instead. 59 */ 60public class MediaSessionRecord implements IBinder.DeathRecipient { 61 private static final String TAG = "MediaSessionRecord"; 62 63 private final MessageHandler mHandler; 64 65 private final int mPid; 66 private final SessionInfo mSessionInfo; 67 private final String mTag; 68 private final ControllerStub mController; 69 private final SessionStub mSession; 70 private final SessionCb mSessionCb; 71 private final MediaSessionService mService; 72 73 private final Object mLock = new Object(); 74 private final ArrayList<ISessionControllerCallback> mControllerCallbacks = 75 new ArrayList<ISessionControllerCallback>(); 76 private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>(); 77 78 private boolean mTransportPerformerEnabled = false; 79 private RouteInfo mRoute; 80 private RouteOptions mRequest; 81 private RouteConnectionRecord mConnection; 82 // TODO define a RouteState class with relevant info 83 private int mRouteState; 84 private long mFlags; 85 86 // TransportPerformer fields 87 88 private MediaMetadata mMetadata; 89 private PlaybackState mPlaybackState; 90 private int mRatingType; 91 // End TransportPerformer fields 92 93 private boolean mIsPublished = false; 94 95 public MediaSessionRecord(int pid, String packageName, ISessionCallback cb, String tag, 96 MediaSessionService service, Handler handler) { 97 mPid = pid; 98 mSessionInfo = new SessionInfo(UUID.randomUUID().toString(), packageName); 99 mTag = tag; 100 mController = new ControllerStub(); 101 mSession = new SessionStub(); 102 mSessionCb = new SessionCb(cb); 103 mService = service; 104 mHandler = new MessageHandler(handler.getLooper()); 105 } 106 107 /** 108 * Get the binder for the {@link Session}. 109 * 110 * @return The session binder apps talk to. 111 */ 112 public ISession getSessionBinder() { 113 return mSession; 114 } 115 116 /** 117 * Get the binder for the {@link SessionController}. 118 * 119 * @return The controller binder apps talk to. 120 */ 121 public ISessionController getControllerBinder() { 122 return mController; 123 } 124 125 /** 126 * Get the set of route requests this session is interested in. 127 * 128 * @return The list of RouteRequests 129 */ 130 public List<RouteRequest> getRouteRequests() { 131 return mRequests; 132 } 133 134 /** 135 * Get the route this session is currently on. 136 * 137 * @return The route the session is on. 138 */ 139 public RouteInfo getRoute() { 140 return mRoute; 141 } 142 143 /** 144 * Get the info for this session. 145 * 146 * @return Info that identifies this session. 147 */ 148 public SessionInfo getSessionInfo() { 149 return mSessionInfo; 150 } 151 152 /** 153 * Get this session's flags. 154 * 155 * @return The flags for this session. 156 */ 157 public long getFlags() { 158 return mFlags; 159 } 160 161 /** 162 * Check if this session has system priorty and should receive media buttons 163 * before any other sessions. 164 * 165 * @return True if this is a system priority session, false otherwise 166 */ 167 public boolean isSystemPriority() { 168 return (mFlags & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0; 169 } 170 171 /** 172 * Set the selected route. This does not connect to the route, just notifies 173 * the app that a new route has been selected. 174 * 175 * @param route The route that was selected. 176 */ 177 public void selectRoute(RouteInfo route) { 178 synchronized (mLock) { 179 if (route != mRoute) { 180 if (mConnection != null) { 181 mConnection.disconnect(); 182 mConnection = null; 183 } 184 } 185 mRoute = route; 186 } 187 mSessionCb.sendRouteChange(route); 188 } 189 190 /** 191 * Update the state of the route this session is using and notify the 192 * session. 193 * 194 * @param state The new state of the route. 195 */ 196 public void setRouteState(int state) { 197 mSessionCb.sendRouteStateChange(state); 198 } 199 200 /** 201 * Send an event to this session from the route it is using. 202 * 203 * @param event The event to send. 204 */ 205 public void sendRouteEvent(RouteEvent event) { 206 mSessionCb.sendRouteEvent(event); 207 } 208 209 /** 210 * Set the connection to use for the selected route and notify the app it is 211 * now connected. 212 * 213 * @param route The route the connection is to. 214 * @param request The request that was used to connect. 215 * @param connection The connection to the route. 216 * @return True if this connection is still valid, false if it is stale. 217 */ 218 public boolean setRouteConnected(RouteInfo route, RouteOptions request, 219 RouteConnectionRecord connection) { 220 synchronized (mLock) { 221 if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) { 222 Log.w(TAG, "setRouteConnected: connected route is stale"); 223 // TODO figure out disconnection path 224 return false; 225 } 226 if (request != mRequest) { 227 Log.w(TAG, "setRouteConnected: connection request is stale"); 228 // TODO figure out disconnection path 229 return false; 230 } 231 mConnection = connection; 232 mConnection.setListener(mConnectionListener); 233 mSessionCb.sendRouteConnected(); 234 } 235 return true; 236 } 237 238 /** 239 * Check if this session has been published by the app yet. 240 * 241 * @return True if it has been published, false otherwise. 242 */ 243 public boolean isPublished() { 244 return mIsPublished; 245 } 246 247 @Override 248 public void binderDied() { 249 mService.sessionDied(this); 250 } 251 252 public void dump(PrintWriter pw, String prefix) { 253 pw.println(prefix + mTag + " " + this); 254 255 final String indent = prefix + " "; 256 pw.println(indent + "pid=" + mPid); 257 pw.println(indent + "info=" + mSessionInfo.toString()); 258 pw.println(indent + "published=" + mIsPublished); 259 pw.println(indent + "transport controls enabled=" + mTransportPerformerEnabled); 260 pw.println(indent + "rating type=" + mRatingType); 261 pw.println(indent + "controllers: " + mControllerCallbacks.size()); 262 pw.println(indent + "state=" + mPlaybackState.toString()); 263 pw.println(indent + "metadata:" + getShortMetadataString()); 264 pw.println(indent + "route requests {"); 265 int size = mRequests.size(); 266 for (int i = 0; i < size; i++) { 267 pw.println(indent + " " + mRequests.get(i).toString()); 268 } 269 pw.println(indent + "}"); 270 pw.println(indent + "route=" + (mRoute == null ? null : mRoute.toString())); 271 pw.println(indent + "connection=" + (mConnection == null ? null : mConnection.toString())); 272 pw.println(indent + "params=" + (mRequest == null ? null : mRequest.toString())); 273 } 274 275 private String getShortMetadataString() { 276 int fields = mMetadata == null ? 0 : mMetadata.size(); 277 String title = mMetadata == null ? null : mMetadata 278 .getString(MediaMetadata.METADATA_KEY_TITLE); 279 return "size=" + fields + ", title=" + title; 280 } 281 282 private void onDestroy() { 283 mService.destroySession(this); 284 } 285 286 private void pushPlaybackStateUpdate() { 287 synchronized (mLock) { 288 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 289 ISessionControllerCallback cb = mControllerCallbacks.get(i); 290 try { 291 cb.onPlaybackStateChanged(mPlaybackState); 292 } catch (RemoteException e) { 293 Log.w(TAG, "Removing dead callback in pushPlaybackStateUpdate.", e); 294 mControllerCallbacks.remove(i); 295 } 296 } 297 } 298 } 299 300 private void pushMetadataUpdate() { 301 synchronized (mLock) { 302 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 303 ISessionControllerCallback cb = mControllerCallbacks.get(i); 304 try { 305 cb.onMetadataChanged(mMetadata); 306 } catch (RemoteException e) { 307 Log.w(TAG, "Removing dead callback in pushMetadataUpdate.", e); 308 mControllerCallbacks.remove(i); 309 } 310 } 311 } 312 } 313 314 private void pushRouteUpdate() { 315 synchronized (mLock) { 316 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 317 ISessionControllerCallback cb = mControllerCallbacks.get(i); 318 try { 319 cb.onRouteChanged(mRoute); 320 } catch (RemoteException e) { 321 Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e); 322 mControllerCallbacks.remove(i); 323 } 324 } 325 } 326 } 327 328 private void pushEvent(String event, Bundle data) { 329 synchronized (mLock) { 330 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 331 ISessionControllerCallback cb = mControllerCallbacks.get(i); 332 try { 333 cb.onEvent(event, data); 334 } catch (RemoteException e) { 335 Log.w(TAG, "Error with callback in pushEvent.", e); 336 } 337 } 338 } 339 } 340 341 private void pushRouteCommand(RouteCommand command, ResultReceiver cb) { 342 synchronized (mLock) { 343 if (mRoute == null || !TextUtils.equals(command.getRouteInfo(), mRoute.getId())) { 344 if (cb != null) { 345 cb.send(RouteInterface.RESULT_ROUTE_IS_STALE, null); 346 return; 347 } 348 } 349 if (mConnection != null) { 350 mConnection.sendCommand(command, cb); 351 } else if (cb != null) { 352 cb.send(RouteInterface.RESULT_NOT_CONNECTED, null); 353 } 354 } 355 } 356 357 private PlaybackState getStateWithUpdatedPosition() { 358 PlaybackState state = mPlaybackState; 359 long duration = -1; 360 if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { 361 duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); 362 } 363 PlaybackState result = null; 364 if (state != null) { 365 if (state.getState() == PlaybackState.PLAYSTATE_PLAYING 366 || state.getState() == PlaybackState.PLAYSTATE_FAST_FORWARDING 367 || state.getState() == PlaybackState.PLAYSTATE_REWINDING) { 368 long updateTime = state.getLastPositionUpdateTime(); 369 if (updateTime > 0) { 370 long position = (long) (state.getRate() 371 * (SystemClock.elapsedRealtime() - updateTime)) + state.getPosition(); 372 if (duration >= 0 && position > duration) { 373 position = duration; 374 } else if (position < 0) { 375 position = 0; 376 } 377 result = new PlaybackState(state); 378 result.setState(state.getState(), position, state.getRate()); 379 } 380 } 381 } 382 return result == null ? state : result; 383 } 384 385 private final RouteConnectionRecord.Listener mConnectionListener 386 = new RouteConnectionRecord.Listener() { 387 @Override 388 public void onEvent(RouteEvent event) { 389 RouteEvent eventForSession = new RouteEvent(null, event.getIface(), 390 event.getEvent(), event.getExtras()); 391 mSessionCb.sendRouteEvent(eventForSession); 392 } 393 394 @Override 395 public void disconnect() { 396 // TODO 397 } 398 }; 399 400 private final class SessionStub extends ISession.Stub { 401 @Override 402 public void destroy() { 403 onDestroy(); 404 } 405 406 @Override 407 public void sendEvent(String event, Bundle data) { 408 mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data); 409 } 410 411 @Override 412 public ISessionController getController() { 413 return mController; 414 } 415 416 @Override 417 public void publish() { 418 mIsPublished = true; 419 mService.publishSession(MediaSessionRecord.this); 420 } 421 @Override 422 public void setTransportPerformerEnabled() { 423 mTransportPerformerEnabled = true; 424 } 425 426 @Override 427 public void setFlags(long flags) { 428 if ((flags & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { 429 int pid = getCallingPid(); 430 int uid = getCallingUid(); 431 mService.enforcePhoneStatePermission(pid, uid); 432 } 433 if (mIsPublished) { 434 throw new IllegalStateException("Cannot set flags after publishing session."); 435 } 436 mFlags = flags; 437 } 438 439 @Override 440 public void setMetadata(MediaMetadata metadata) { 441 mMetadata = metadata; 442 mHandler.post(MessageHandler.MSG_UPDATE_METADATA); 443 } 444 445 @Override 446 public void setPlaybackState(PlaybackState state) { 447 mPlaybackState = state; 448 mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE); 449 } 450 451 @Override 452 public void setRatingType(int type) { 453 mRatingType = type; 454 } 455 456 @Override 457 public void sendRouteCommand(RouteCommand command, ResultReceiver cb) { 458 mHandler.post(MessageHandler.MSG_SEND_COMMAND, 459 new Pair<RouteCommand, ResultReceiver>(command, cb)); 460 } 461 462 @Override 463 public boolean setRoute(RouteInfo route) throws RemoteException { 464 // TODO decide if allowed to set route and if the route exists 465 return false; 466 } 467 468 @Override 469 public void connectToRoute(RouteInfo route, RouteOptions request) 470 throws RemoteException { 471 if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) { 472 throw new RemoteException("RouteInfo does not match current route"); 473 } 474 mService.connectToRoute(MediaSessionRecord.this, route, request); 475 mRequest = request; 476 } 477 478 @Override 479 public void setRouteOptions(List<RouteOptions> options) throws RemoteException { 480 mRequests.clear(); 481 for (int i = options.size() - 1; i >= 0; i--) { 482 RouteRequest request = new RouteRequest(mSessionInfo, options.get(i), 483 false); 484 mRequests.add(request); 485 } 486 } 487 } 488 489 class SessionCb { 490 private final ISessionCallback mCb; 491 492 public SessionCb(ISessionCallback cb) { 493 mCb = cb; 494 } 495 496 public void sendMediaButton(KeyEvent keyEvent) { 497 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 498 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 499 try { 500 mCb.onMediaButton(mediaButtonIntent); 501 } catch (RemoteException e) { 502 Slog.e(TAG, "Remote failure in sendMediaRequest.", e); 503 } 504 } 505 506 public void sendCommand(String command, Bundle extras, ResultReceiver cb) { 507 try { 508 mCb.onCommand(command, extras, cb); 509 } catch (RemoteException e) { 510 Slog.e(TAG, "Remote failure in sendCommand.", e); 511 } 512 } 513 514 public void sendRouteChange(RouteInfo route) { 515 try { 516 mCb.onRequestRouteChange(route); 517 } catch (RemoteException e) { 518 Slog.e(TAG, "Remote failure in sendRouteChange.", e); 519 } 520 } 521 522 public void sendRouteStateChange(int state) { 523 try { 524 mCb.onRouteStateChange(state); 525 } catch (RemoteException e) { 526 Slog.e(TAG, "Remote failure in sendRouteStateChange.", e); 527 } 528 } 529 530 public void sendRouteEvent(RouteEvent event) { 531 try { 532 mCb.onRouteEvent(event); 533 } catch (RemoteException e) { 534 Slog.e(TAG, "Remote failure in sendRouteEvent.", e); 535 } 536 } 537 538 public void sendRouteConnected() { 539 try { 540 mCb.onRouteConnected(mRoute, mRequest); 541 } catch (RemoteException e) { 542 Slog.e(TAG, "Remote failure in sendRouteStateChange.", e); 543 } 544 } 545 546 public void play() { 547 try { 548 mCb.onPlay(); 549 } catch (RemoteException e) { 550 Slog.e(TAG, "Remote failure in play.", e); 551 } 552 } 553 554 public void pause() { 555 try { 556 mCb.onPause(); 557 } catch (RemoteException e) { 558 Slog.e(TAG, "Remote failure in pause.", e); 559 } 560 } 561 562 public void stop() { 563 try { 564 mCb.onStop(); 565 } catch (RemoteException e) { 566 Slog.e(TAG, "Remote failure in stop.", e); 567 } 568 } 569 570 public void next() { 571 try { 572 mCb.onNext(); 573 } catch (RemoteException e) { 574 Slog.e(TAG, "Remote failure in next.", e); 575 } 576 } 577 578 public void previous() { 579 try { 580 mCb.onPrevious(); 581 } catch (RemoteException e) { 582 Slog.e(TAG, "Remote failure in previous.", e); 583 } 584 } 585 586 public void fastForward() { 587 try { 588 mCb.onFastForward(); 589 } catch (RemoteException e) { 590 Slog.e(TAG, "Remote failure in fastForward.", e); 591 } 592 } 593 594 public void rewind() { 595 try { 596 mCb.onRewind(); 597 } catch (RemoteException e) { 598 Slog.e(TAG, "Remote failure in rewind.", e); 599 } 600 } 601 602 public void seekTo(long pos) { 603 try { 604 mCb.onSeekTo(pos); 605 } catch (RemoteException e) { 606 Slog.e(TAG, "Remote failure in seekTo.", e); 607 } 608 } 609 610 public void rate(Rating rating) { 611 try { 612 mCb.onRate(rating); 613 } catch (RemoteException e) { 614 Slog.e(TAG, "Remote failure in rate.", e); 615 } 616 } 617 } 618 619 class ControllerStub extends ISessionController.Stub { 620 @Override 621 public void sendCommand(String command, Bundle extras, ResultReceiver cb) 622 throws RemoteException { 623 mSessionCb.sendCommand(command, extras, cb); 624 } 625 626 @Override 627 public void sendMediaButton(KeyEvent mediaButtonIntent) { 628 mSessionCb.sendMediaButton(mediaButtonIntent); 629 } 630 631 @Override 632 public void registerCallbackListener(ISessionControllerCallback cb) { 633 synchronized (mLock) { 634 if (!mControllerCallbacks.contains(cb)) { 635 mControllerCallbacks.add(cb); 636 } 637 } 638 } 639 640 @Override 641 public void unregisterCallbackListener(ISessionControllerCallback cb) 642 throws RemoteException { 643 synchronized (mLock) { 644 mControllerCallbacks.remove(cb); 645 } 646 } 647 648 @Override 649 public void play() throws RemoteException { 650 mSessionCb.play(); 651 } 652 653 @Override 654 public void pause() throws RemoteException { 655 mSessionCb.pause(); 656 } 657 658 @Override 659 public void stop() throws RemoteException { 660 mSessionCb.stop(); 661 } 662 663 @Override 664 public void next() throws RemoteException { 665 mSessionCb.next(); 666 } 667 668 @Override 669 public void previous() throws RemoteException { 670 mSessionCb.previous(); 671 } 672 673 @Override 674 public void fastForward() throws RemoteException { 675 mSessionCb.fastForward(); 676 } 677 678 @Override 679 public void rewind() throws RemoteException { 680 mSessionCb.rewind(); 681 } 682 683 @Override 684 public void seekTo(long pos) throws RemoteException { 685 mSessionCb.seekTo(pos); 686 } 687 688 @Override 689 public void rate(Rating rating) throws RemoteException { 690 mSessionCb.rate(rating); 691 } 692 693 694 @Override 695 public MediaMetadata getMetadata() { 696 return mMetadata; 697 } 698 699 @Override 700 public PlaybackState getPlaybackState() { 701 return getStateWithUpdatedPosition(); 702 } 703 704 @Override 705 public int getRatingType() { 706 return mRatingType; 707 } 708 709 @Override 710 public boolean isTransportControlEnabled() { 711 return mTransportPerformerEnabled; 712 } 713 714 @Override 715 public void showRoutePicker() { 716 mService.showRoutePickerForSession(MediaSessionRecord.this); 717 } 718 } 719 720 private class MessageHandler extends Handler { 721 private static final int MSG_UPDATE_METADATA = 1; 722 private static final int MSG_UPDATE_PLAYBACK_STATE = 2; 723 private static final int MSG_UPDATE_ROUTE = 3; 724 private static final int MSG_SEND_EVENT = 4; 725 private static final int MSG_UPDATE_ROUTE_FILTERS = 5; 726 private static final int MSG_SEND_COMMAND = 6; 727 728 public MessageHandler(Looper looper) { 729 super(looper); 730 } 731 @Override 732 public void handleMessage(Message msg) { 733 switch (msg.what) { 734 case MSG_UPDATE_METADATA: 735 pushMetadataUpdate(); 736 break; 737 case MSG_UPDATE_PLAYBACK_STATE: 738 pushPlaybackStateUpdate(); 739 break; 740 case MSG_UPDATE_ROUTE: 741 pushRouteUpdate(); 742 break; 743 case MSG_SEND_EVENT: 744 pushEvent((String) msg.obj, msg.getData()); 745 break; 746 case MSG_SEND_COMMAND: 747 Pair<RouteCommand, ResultReceiver> cmd = 748 (Pair<RouteCommand, ResultReceiver>) msg.obj; 749 pushRouteCommand(cmd.first, cmd.second); 750 break; 751 } 752 } 753 754 public void post(int what) { 755 post(what, null); 756 } 757 758 public void post(int what, Object obj) { 759 obtainMessage(what, obj).sendToTarget(); 760 } 761 762 public void post(int what, Object obj, Bundle data) { 763 Message msg = obtainMessage(what, obj); 764 msg.setData(data); 765 msg.sendToTarget(); 766 } 767 } 768 769} 770