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