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