MediaSessionRecord.java revision a278ea7cecb59a73586e5dd74ec05e85caa370c5
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.io.PrintWriter; 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 public void dump(PrintWriter pw, String prefix) { 231 pw.println(prefix + mTag + " " + this); 232 233 final String indent = prefix + " "; 234 pw.println(indent + "pid=" + mPid); 235 pw.println(indent + "info=" + mSessionInfo.toString()); 236 pw.println(indent + "published=" + mIsPublished); 237 pw.println(indent + "transport controls enabled=" + mTransportPerformerEnabled); 238 pw.println(indent + "rating type=" + mRatingType); 239 pw.println(indent + "controllers: " + mControllerCallbacks.size()); 240 pw.println(indent + "route requests {"); 241 int size = mRequests.size(); 242 for (int i = 0; i < size; i++) { 243 pw.println(indent + " " + mRequests.get(i).toString()); 244 } 245 pw.println(indent + "}"); 246 pw.println(indent + "route=" + (mRoute == null ? null : mRoute.toString())); 247 pw.println(indent + "connection=" + (mConnection == null ? null : mConnection.toString())); 248 pw.println(indent + "params=" + (mRequest == null ? null : mRequest.toString())); 249 } 250 251 private void onDestroy() { 252 mService.destroySession(this); 253 } 254 255 private void pushPlaybackStateUpdate() { 256 synchronized (mLock) { 257 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 258 ISessionControllerCallback cb = mControllerCallbacks.get(i); 259 try { 260 cb.onPlaybackStateChanged(mPlaybackState); 261 } catch (RemoteException e) { 262 Log.w(TAG, "Removing dead callback in pushPlaybackStateUpdate.", e); 263 mControllerCallbacks.remove(i); 264 } 265 } 266 } 267 } 268 269 private void pushMetadataUpdate() { 270 synchronized (mLock) { 271 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 272 ISessionControllerCallback cb = mControllerCallbacks.get(i); 273 try { 274 cb.onMetadataChanged(mMetadata); 275 } catch (RemoteException e) { 276 Log.w(TAG, "Removing dead callback in pushMetadataUpdate.", e); 277 mControllerCallbacks.remove(i); 278 } 279 } 280 } 281 } 282 283 private void pushRouteUpdate() { 284 synchronized (mLock) { 285 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 286 ISessionControllerCallback cb = mControllerCallbacks.get(i); 287 try { 288 cb.onRouteChanged(mRoute); 289 } catch (RemoteException e) { 290 Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e); 291 mControllerCallbacks.remove(i); 292 } 293 } 294 } 295 } 296 297 private void pushEvent(String event, Bundle data) { 298 synchronized (mLock) { 299 for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { 300 ISessionControllerCallback cb = mControllerCallbacks.get(i); 301 try { 302 cb.onEvent(event, data); 303 } catch (RemoteException e) { 304 Log.w(TAG, "Error with callback in pushEvent.", e); 305 } 306 } 307 } 308 } 309 310 private void pushRouteCommand(RouteCommand command, ResultReceiver cb) { 311 synchronized (mLock) { 312 if (mRoute == null || !TextUtils.equals(command.getRouteInfo(), mRoute.getId())) { 313 if (cb != null) { 314 cb.send(RouteInterface.RESULT_ROUTE_IS_STALE, null); 315 return; 316 } 317 } 318 if (mConnection != null) { 319 mConnection.sendCommand(command, cb); 320 } else if (cb != null) { 321 cb.send(RouteInterface.RESULT_NOT_CONNECTED, null); 322 } 323 } 324 } 325 326 private final RouteConnectionRecord.Listener mConnectionListener 327 = new RouteConnectionRecord.Listener() { 328 @Override 329 public void onEvent(RouteEvent event) { 330 RouteEvent eventForSession = new RouteEvent(null, event.getIface(), 331 event.getEvent(), event.getExtras()); 332 mSessionCb.sendRouteEvent(eventForSession); 333 } 334 335 @Override 336 public void disconnect() { 337 // TODO 338 } 339 }; 340 341 private final class SessionStub extends ISession.Stub { 342 @Override 343 public void destroy() { 344 onDestroy(); 345 } 346 347 @Override 348 public void sendEvent(String event, Bundle data) { 349 mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data); 350 } 351 352 @Override 353 public ISessionController getController() { 354 return mController; 355 } 356 357 @Override 358 public void publish() { 359 mIsPublished = true; // TODO push update to service 360 } 361 @Override 362 public void setTransportPerformerEnabled() { 363 mTransportPerformerEnabled = true; 364 } 365 366 @Override 367 public void setMetadata(MediaMetadata metadata) { 368 mMetadata = metadata; 369 mHandler.post(MessageHandler.MSG_UPDATE_METADATA); 370 } 371 372 @Override 373 public void setPlaybackState(PlaybackState state) { 374 mPlaybackState = state; 375 mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE); 376 } 377 378 @Override 379 public void setRatingType(int type) { 380 mRatingType = type; 381 } 382 383 @Override 384 public void sendRouteCommand(RouteCommand command, ResultReceiver cb) { 385 mHandler.post(MessageHandler.MSG_SEND_COMMAND, 386 new Pair<RouteCommand, ResultReceiver>(command, cb)); 387 } 388 389 @Override 390 public boolean setRoute(RouteInfo route) throws RemoteException { 391 // TODO decide if allowed to set route and if the route exists 392 return false; 393 } 394 395 @Override 396 public void connectToRoute(RouteInfo route, RouteOptions request) 397 throws RemoteException { 398 if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) { 399 throw new RemoteException("RouteInfo does not match current route"); 400 } 401 mService.connectToRoute(MediaSessionRecord.this, route, request); 402 mRequest = request; 403 } 404 405 @Override 406 public void setRouteOptions(List<RouteOptions> options) throws RemoteException { 407 mRequests.clear(); 408 for (int i = options.size() - 1; i >= 0; i--) { 409 RouteRequest request = new RouteRequest(mSessionInfo, options.get(i), 410 false); 411 mRequests.add(request); 412 } 413 } 414 } 415 416 class SessionCb { 417 private final ISessionCallback mCb; 418 419 public SessionCb(ISessionCallback cb) { 420 mCb = cb; 421 } 422 423 public void sendMediaButton(KeyEvent keyEvent) { 424 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 425 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); 426 try { 427 mCb.onMediaButton(mediaButtonIntent); 428 } catch (RemoteException e) { 429 Slog.e(TAG, "Remote failure in sendMediaRequest.", e); 430 } 431 } 432 433 public void sendCommand(String command, Bundle extras, ResultReceiver cb) { 434 try { 435 mCb.onCommand(command, extras, cb); 436 } catch (RemoteException e) { 437 Slog.e(TAG, "Remote failure in sendCommand.", e); 438 } 439 } 440 441 public void sendRouteChange(RouteInfo route) { 442 try { 443 mCb.onRequestRouteChange(route); 444 } catch (RemoteException e) { 445 Slog.e(TAG, "Remote failure in sendRouteChange.", e); 446 } 447 } 448 449 public void sendRouteStateChange(int state) { 450 try { 451 mCb.onRouteStateChange(state); 452 } catch (RemoteException e) { 453 Slog.e(TAG, "Remote failure in sendRouteStateChange.", e); 454 } 455 } 456 457 public void sendRouteEvent(RouteEvent event) { 458 try { 459 mCb.onRouteEvent(event); 460 } catch (RemoteException e) { 461 Slog.e(TAG, "Remote failure in sendRouteEvent.", e); 462 } 463 } 464 465 public void sendRouteConnected() { 466 try { 467 mCb.onRouteConnected(mRoute, mRequest); 468 } catch (RemoteException e) { 469 Slog.e(TAG, "Remote failure in sendRouteStateChange.", e); 470 } 471 } 472 473 public void play() { 474 try { 475 mCb.onPlay(); 476 } catch (RemoteException e) { 477 Slog.e(TAG, "Remote failure in play.", e); 478 } 479 } 480 481 public void pause() { 482 try { 483 mCb.onPause(); 484 } catch (RemoteException e) { 485 Slog.e(TAG, "Remote failure in pause.", e); 486 } 487 } 488 489 public void stop() { 490 try { 491 mCb.onStop(); 492 } catch (RemoteException e) { 493 Slog.e(TAG, "Remote failure in stop.", e); 494 } 495 } 496 497 public void next() { 498 try { 499 mCb.onNext(); 500 } catch (RemoteException e) { 501 Slog.e(TAG, "Remote failure in next.", e); 502 } 503 } 504 505 public void previous() { 506 try { 507 mCb.onPrevious(); 508 } catch (RemoteException e) { 509 Slog.e(TAG, "Remote failure in previous.", e); 510 } 511 } 512 513 public void fastForward() { 514 try { 515 mCb.onFastForward(); 516 } catch (RemoteException e) { 517 Slog.e(TAG, "Remote failure in fastForward.", e); 518 } 519 } 520 521 public void rewind() { 522 try { 523 mCb.onRewind(); 524 } catch (RemoteException e) { 525 Slog.e(TAG, "Remote failure in rewind.", e); 526 } 527 } 528 529 public void seekTo(long pos) { 530 try { 531 mCb.onSeekTo(pos); 532 } catch (RemoteException e) { 533 Slog.e(TAG, "Remote failure in seekTo.", e); 534 } 535 } 536 537 public void rate(Rating rating) { 538 try { 539 mCb.onRate(rating); 540 } catch (RemoteException e) { 541 Slog.e(TAG, "Remote failure in rate.", e); 542 } 543 } 544 } 545 546 class ControllerStub extends ISessionController.Stub { 547 @Override 548 public void sendCommand(String command, Bundle extras, ResultReceiver cb) 549 throws RemoteException { 550 mSessionCb.sendCommand(command, extras, cb); 551 } 552 553 @Override 554 public void sendMediaButton(KeyEvent mediaButtonIntent) { 555 mSessionCb.sendMediaButton(mediaButtonIntent); 556 } 557 558 @Override 559 public void registerCallbackListener(ISessionControllerCallback cb) { 560 synchronized (mLock) { 561 if (!mControllerCallbacks.contains(cb)) { 562 mControllerCallbacks.add(cb); 563 } 564 } 565 } 566 567 @Override 568 public void unregisterCallbackListener(ISessionControllerCallback cb) 569 throws RemoteException { 570 synchronized (mLock) { 571 mControllerCallbacks.remove(cb); 572 } 573 } 574 575 @Override 576 public void play() throws RemoteException { 577 mSessionCb.play(); 578 } 579 580 @Override 581 public void pause() throws RemoteException { 582 mSessionCb.pause(); 583 } 584 585 @Override 586 public void stop() throws RemoteException { 587 mSessionCb.stop(); 588 } 589 590 @Override 591 public void next() throws RemoteException { 592 mSessionCb.next(); 593 } 594 595 @Override 596 public void previous() throws RemoteException { 597 mSessionCb.previous(); 598 } 599 600 @Override 601 public void fastForward() throws RemoteException { 602 mSessionCb.fastForward(); 603 } 604 605 @Override 606 public void rewind() throws RemoteException { 607 mSessionCb.rewind(); 608 } 609 610 @Override 611 public void seekTo(long pos) throws RemoteException { 612 mSessionCb.seekTo(pos); 613 } 614 615 @Override 616 public void rate(Rating rating) throws RemoteException { 617 mSessionCb.rate(rating); 618 } 619 620 621 @Override 622 public MediaMetadata getMetadata() { 623 return mMetadata; 624 } 625 626 @Override 627 public PlaybackState getPlaybackState() { 628 return mPlaybackState; 629 } 630 631 @Override 632 public int getRatingType() { 633 return mRatingType; 634 } 635 636 @Override 637 public boolean isTransportControlEnabled() { 638 return mTransportPerformerEnabled; 639 } 640 641 @Override 642 public void showRoutePicker() { 643 mService.showRoutePickerForSession(MediaSessionRecord.this); 644 } 645 } 646 647 private class MessageHandler extends Handler { 648 private static final int MSG_UPDATE_METADATA = 1; 649 private static final int MSG_UPDATE_PLAYBACK_STATE = 2; 650 private static final int MSG_UPDATE_ROUTE = 3; 651 private static final int MSG_SEND_EVENT = 4; 652 private static final int MSG_UPDATE_ROUTE_FILTERS = 5; 653 private static final int MSG_SEND_COMMAND = 6; 654 655 public MessageHandler(Looper looper) { 656 super(looper); 657 } 658 @Override 659 public void handleMessage(Message msg) { 660 switch (msg.what) { 661 case MSG_UPDATE_METADATA: 662 pushMetadataUpdate(); 663 break; 664 case MSG_UPDATE_PLAYBACK_STATE: 665 pushPlaybackStateUpdate(); 666 break; 667 case MSG_UPDATE_ROUTE: 668 pushRouteUpdate(); 669 break; 670 case MSG_SEND_EVENT: 671 pushEvent((String) msg.obj, msg.getData()); 672 break; 673 case MSG_SEND_COMMAND: 674 Pair<RouteCommand, ResultReceiver> cmd = 675 (Pair<RouteCommand, ResultReceiver>) msg.obj; 676 pushRouteCommand(cmd.first, cmd.second); 677 break; 678 } 679 } 680 681 public void post(int what) { 682 post(what, null); 683 } 684 685 public void post(int what, Object obj) { 686 obtainMessage(what, obj).sendToTarget(); 687 } 688 689 public void post(int what, Object obj, Bundle data) { 690 Message msg = obtainMessage(what, obj); 691 msg.setData(data); 692 msg.sendToTarget(); 693 } 694 } 695 696} 697