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