MediaBrowser.java revision 88b84178d6a8551ea1b534d8750def8dceebdc2a
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 android.media.browse; 18 19import android.annotation.IntDef; 20import android.annotation.NonNull; 21import android.annotation.Nullable; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.content.ServiceConnection; 26import android.content.pm.ParceledListSlice; 27import android.media.MediaDescription; 28import android.media.session.MediaController; 29import android.media.session.MediaSession; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.IBinder; 33import android.os.Parcel; 34import android.os.Parcelable; 35import android.os.RemoteException; 36import android.service.media.MediaBrowserService; 37import android.service.media.IMediaBrowserService; 38import android.service.media.IMediaBrowserServiceCallbacks; 39import android.text.TextUtils; 40import android.util.ArrayMap; 41import android.util.Log; 42 43import java.lang.annotation.Retention; 44import java.lang.annotation.RetentionPolicy; 45import java.lang.ref.WeakReference; 46import java.util.Collections; 47import java.util.List; 48 49/** 50 * Browses media content offered by a link MediaBrowserService. 51 * <p> 52 * This object is not thread-safe. All calls should happen on the thread on which the browser 53 * was constructed. 54 * </p> 55 */ 56public final class MediaBrowser { 57 private static final String TAG = "MediaBrowser"; 58 private static final boolean DBG = false; 59 60 private static final int CONNECT_STATE_DISCONNECTED = 0; 61 private static final int CONNECT_STATE_CONNECTING = 1; 62 private static final int CONNECT_STATE_CONNECTED = 2; 63 private static final int CONNECT_STATE_SUSPENDED = 3; 64 65 private final Context mContext; 66 private final ComponentName mServiceComponent; 67 private final ConnectionCallback mCallback; 68 private final Bundle mRootHints; 69 private final Handler mHandler = new Handler(); 70 private final ArrayMap<String,Subscription> mSubscriptions = 71 new ArrayMap<String, MediaBrowser.Subscription>(); 72 73 private int mState = CONNECT_STATE_DISCONNECTED; 74 private MediaServiceConnection mServiceConnection; 75 private IMediaBrowserService mServiceBinder; 76 private IMediaBrowserServiceCallbacks mServiceCallbacks; 77 private String mRootId; 78 private MediaSession.Token mMediaSessionToken; 79 private Bundle mExtras; 80 81 /** 82 * Creates a media browser for the specified media browse service. 83 * 84 * @param context The context. 85 * @param serviceComponent The component name of the media browse service. 86 * @param callback The connection callback. 87 * @param rootHints An optional bundle of service-specific arguments to send 88 * to the media browse service when connecting and retrieving the root id 89 * for browsing, or null if none. The contents of this bundle may affect 90 * the information returned when browsing. 91 */ 92 public MediaBrowser(Context context, ComponentName serviceComponent, 93 ConnectionCallback callback, Bundle rootHints) { 94 if (context == null) { 95 throw new IllegalArgumentException("context must not be null"); 96 } 97 if (serviceComponent == null) { 98 throw new IllegalArgumentException("service component must not be null"); 99 } 100 if (callback == null) { 101 throw new IllegalArgumentException("connection callback must not be null"); 102 } 103 mContext = context; 104 mServiceComponent = serviceComponent; 105 mCallback = callback; 106 mRootHints = rootHints; 107 } 108 109 /** 110 * Connects to the media browse service. 111 * <p> 112 * The connection callback specified in the constructor will be invoked 113 * when the connection completes or fails. 114 * </p> 115 */ 116 public void connect() { 117 if (mState != CONNECT_STATE_DISCONNECTED) { 118 throw new IllegalStateException("connect() called while not disconnected (state=" 119 + getStateLabel(mState) + ")"); 120 } 121 // TODO: remove this extra check. 122 if (DBG) { 123 if (mServiceConnection != null) { 124 throw new RuntimeException("mServiceConnection should be null. Instead it is " 125 + mServiceConnection); 126 } 127 } 128 if (mServiceBinder != null) { 129 throw new RuntimeException("mServiceBinder should be null. Instead it is " 130 + mServiceBinder); 131 } 132 if (mServiceCallbacks != null) { 133 throw new RuntimeException("mServiceCallbacks should be null. Instead it is " 134 + mServiceCallbacks); 135 } 136 137 mState = CONNECT_STATE_CONNECTING; 138 139 final Intent intent = new Intent(MediaBrowserService.SERVICE_INTERFACE); 140 intent.setComponent(mServiceComponent); 141 142 final ServiceConnection thisConnection = mServiceConnection = new MediaServiceConnection(); 143 144 try { 145 mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); 146 } catch (Exception ex) { 147 Log.e(TAG, "Failed binding to service " + mServiceComponent); 148 149 // Tell them that it didn't work. We are already on the main thread, 150 // but we don't want to do callbacks inside of connect(). So post it, 151 // and then check that we are on the same ServiceConnection. We know 152 // we won't also get an onServiceConnected or onServiceDisconnected, 153 // so we won't be doing double callbacks. 154 mHandler.post(new Runnable() { 155 @Override 156 public void run() { 157 // Ensure that nobody else came in or tried to connect again. 158 if (thisConnection == mServiceConnection) { 159 forceCloseConnection(); 160 mCallback.onConnectionFailed(); 161 } 162 } 163 }); 164 } 165 166 if (DBG) { 167 Log.d(TAG, "connect..."); 168 dump(); 169 } 170 } 171 172 /** 173 * Disconnects from the media browse service. 174 * After this, no more callbacks will be received. 175 */ 176 public void disconnect() { 177 // It's ok to call this any state, because allowing this lets apps not have 178 // to check isConnected() unnecessarily. They won't appreciate the extra 179 // assertions for this. We do everything we can here to go back to a sane state. 180 if (mServiceCallbacks != null) { 181 try { 182 mServiceBinder.disconnect(mServiceCallbacks); 183 } catch (RemoteException ex) { 184 // We are disconnecting anyway. Log, just for posterity but it's not 185 // a big problem. 186 Log.w(TAG, "RemoteException during connect for " + mServiceComponent); 187 } 188 } 189 forceCloseConnection(); 190 191 if (DBG) { 192 Log.d(TAG, "disconnect..."); 193 dump(); 194 } 195 } 196 197 /** 198 * Null out the variables and unbind from the service. This doesn't include 199 * calling disconnect on the service, because we only try to do that in the 200 * clean shutdown cases. 201 * <p> 202 * Everywhere that calls this EXCEPT for disconnect() should follow it with 203 * a call to mCallback.onConnectionFailed(). Disconnect doesn't do that callback 204 * for a clean shutdown, but everywhere else is a dirty shutdown and should 205 * notify the app. 206 */ 207 private void forceCloseConnection() { 208 if (mServiceConnection != null) { 209 mContext.unbindService(mServiceConnection); 210 } 211 mState = CONNECT_STATE_DISCONNECTED; 212 mServiceConnection = null; 213 mServiceBinder = null; 214 mServiceCallbacks = null; 215 mRootId = null; 216 mMediaSessionToken = null; 217 } 218 219 /** 220 * Returns whether the browser is connected to the service. 221 */ 222 public boolean isConnected() { 223 return mState == CONNECT_STATE_CONNECTED; 224 } 225 226 /** 227 * Gets the service component that the media browser is connected to. 228 */ 229 public @NonNull ComponentName getServiceComponent() { 230 if (!isConnected()) { 231 throw new IllegalStateException("getServiceComponent() called while not connected" + 232 " (state=" + mState + ")"); 233 } 234 return mServiceComponent; 235 } 236 237 /** 238 * Gets the root id. 239 * <p> 240 * Note that the root id may become invalid or change when when the 241 * browser is disconnected. 242 * </p> 243 * 244 * @throws IllegalStateException if not connected. 245 */ 246 public @NonNull String getRoot() { 247 if (!isConnected()) { 248 throw new IllegalStateException("getSessionToken() called while not connected (state=" 249 + getStateLabel(mState) + ")"); 250 } 251 return mRootId; 252 } 253 254 /** 255 * Gets any extras for the media service. 256 * 257 * @throws IllegalStateException if not connected. 258 */ 259 public @Nullable Bundle getExtras() { 260 if (!isConnected()) { 261 throw new IllegalStateException("getExtras() called while not connected (state=" 262 + getStateLabel(mState) + ")"); 263 } 264 return mExtras; 265 } 266 267 /** 268 * Gets the media session token associated with the media browser. 269 * <p> 270 * Note that the session token may become invalid or change when when the 271 * browser is disconnected. 272 * </p> 273 * 274 * @return The session token for the browser, never null. 275 * 276 * @throws IllegalStateException if not connected. 277 */ 278 public @NonNull MediaSession.Token getSessionToken() { 279 if (!isConnected()) { 280 throw new IllegalStateException("getSessionToken() called while not connected (state=" 281 + mState + ")"); 282 } 283 return mMediaSessionToken; 284 } 285 286 /** 287 * Queries for information about the media items that are contained within 288 * the specified id and subscribes to receive updates when they change. 289 * <p> 290 * The list of subscriptions is maintained even when not connected and is 291 * restored after reconnection. It is ok to subscribe while not connected 292 * but the results will not be returned until the connection completes. 293 * </p><p> 294 * If the id is already subscribed with a different callback then the new 295 * callback will replace the previous one. 296 * </p> 297 * 298 * @param parentId The id of the parent media item whose list of children 299 * will be subscribed. 300 * @param callback The callback to receive the list of children. 301 */ 302 public void subscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) { 303 // Check arguments. 304 if (parentId == null) { 305 throw new IllegalArgumentException("parentId is null"); 306 } 307 if (callback == null) { 308 throw new IllegalArgumentException("callback is null"); 309 } 310 311 // Update or create the subscription. 312 Subscription sub = mSubscriptions.get(parentId); 313 boolean newSubscription = sub == null; 314 if (newSubscription) { 315 sub = new Subscription(parentId); 316 mSubscriptions.put(parentId, sub); 317 } 318 sub.callback = callback; 319 320 // If we are connected, tell the service that we are watching. If we aren't 321 // connected, the service will be told when we connect. 322 if (mState == CONNECT_STATE_CONNECTED && newSubscription) { 323 try { 324 mServiceBinder.addSubscription(parentId, mServiceCallbacks); 325 } catch (RemoteException ex) { 326 // Process is crashing. We will disconnect, and upon reconnect we will 327 // automatically reregister. So nothing to do here. 328 Log.d(TAG, "addSubscription failed with RemoteException parentId=" + parentId); 329 } 330 } 331 } 332 333 /** 334 * Unsubscribes for changes to the children of the specified media id. 335 * <p> 336 * The query callback will no longer be invoked for results associated with 337 * this id once this method returns. 338 * </p> 339 * 340 * @param parentId The id of the parent media item whose list of children 341 * will be unsubscribed. 342 */ 343 public void unsubscribe(@NonNull String parentId) { 344 // Check arguments. 345 if (parentId == null) { 346 throw new IllegalArgumentException("parentId is null"); 347 } 348 349 // Remove from our list. 350 final Subscription sub = mSubscriptions.remove(parentId); 351 352 // Tell the service if necessary. 353 if (mState == CONNECT_STATE_CONNECTED && sub != null) { 354 try { 355 mServiceBinder.removeSubscription(parentId, mServiceCallbacks); 356 } catch (RemoteException ex) { 357 // Process is crashing. We will disconnect, and upon reconnect we will 358 // automatically reregister. So nothing to do here. 359 Log.d(TAG, "removeSubscription failed with RemoteException parentId=" + parentId); 360 } 361 } 362 } 363 364 /** 365 * For debugging. 366 */ 367 private static String getStateLabel(int state) { 368 switch (state) { 369 case CONNECT_STATE_DISCONNECTED: 370 return "CONNECT_STATE_DISCONNECTED"; 371 case CONNECT_STATE_CONNECTING: 372 return "CONNECT_STATE_CONNECTING"; 373 case CONNECT_STATE_CONNECTED: 374 return "CONNECT_STATE_CONNECTED"; 375 case CONNECT_STATE_SUSPENDED: 376 return "CONNECT_STATE_SUSPENDED"; 377 default: 378 return "UNKNOWN/" + state; 379 } 380 } 381 382 private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback, 383 final String root, final MediaSession.Token session, final Bundle extra) { 384 mHandler.post(new Runnable() { 385 @Override 386 public void run() { 387 // Check to make sure there hasn't been a disconnect or a different 388 // ServiceConnection. 389 if (!isCurrent(callback, "onConnect")) { 390 return; 391 } 392 // Don't allow them to call us twice. 393 if (mState != CONNECT_STATE_CONNECTING) { 394 Log.w(TAG, "onConnect from service while mState=" 395 + getStateLabel(mState) + "... ignoring"); 396 return; 397 } 398 mRootId = root; 399 mMediaSessionToken = session; 400 mExtras = extra; 401 mState = CONNECT_STATE_CONNECTED; 402 403 if (DBG) { 404 Log.d(TAG, "ServiceCallbacks.onConnect..."); 405 dump(); 406 } 407 mCallback.onConnected(); 408 409 // we may receive some subscriptions before we are connected, so re-subscribe 410 // everything now 411 for (String id : mSubscriptions.keySet()) { 412 try { 413 mServiceBinder.addSubscription(id, mServiceCallbacks); 414 } catch (RemoteException ex) { 415 // Process is crashing. We will disconnect, and upon reconnect we will 416 // automatically reregister. So nothing to do here. 417 Log.d(TAG, "addSubscription failed with RemoteException parentId=" + id); 418 } 419 } 420 } 421 }); 422 } 423 424 private final void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) { 425 mHandler.post(new Runnable() { 426 @Override 427 public void run() { 428 Log.e(TAG, "onConnectFailed for " + mServiceComponent); 429 430 // Check to make sure there hasn't been a disconnect or a different 431 // ServiceConnection. 432 if (!isCurrent(callback, "onConnectFailed")) { 433 return; 434 } 435 // Don't allow them to call us twice. 436 if (mState != CONNECT_STATE_CONNECTING) { 437 Log.w(TAG, "onConnect from service while mState=" 438 + getStateLabel(mState) + "... ignoring"); 439 return; 440 } 441 442 // Clean up 443 forceCloseConnection(); 444 445 // Tell the app. 446 mCallback.onConnectionFailed(); 447 } 448 }); 449 } 450 451 private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback, 452 final String parentId, final ParceledListSlice list) { 453 mHandler.post(new Runnable() { 454 @Override 455 public void run() { 456 // Check that there hasn't been a disconnect or a different 457 // ServiceConnection. 458 if (!isCurrent(callback, "onLoadChildren")) { 459 return; 460 } 461 462 List<MediaItem> data = list.getList(); 463 if (DBG) { 464 Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId); 465 } 466 if (data == null) { 467 data = Collections.emptyList(); 468 } 469 470 // Check that the subscription is still subscribed. 471 final Subscription subscription = mSubscriptions.get(parentId); 472 if (subscription == null) { 473 if (DBG) { 474 Log.d(TAG, "onLoadChildren for id that isn't subscribed id=" 475 + parentId); 476 } 477 return; 478 } 479 480 // Tell the app. 481 subscription.callback.onChildrenLoaded(parentId, data); 482 } 483 }); 484 } 485 486 /** 487 * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not. 488 */ 489 private boolean isCurrent(IMediaBrowserServiceCallbacks callback, String funcName) { 490 if (mServiceCallbacks != callback) { 491 if (mState != CONNECT_STATE_DISCONNECTED) { 492 Log.i(TAG, funcName + " for " + mServiceComponent + " with mServiceConnection=" 493 + mServiceCallbacks + " this=" + this); 494 } 495 return false; 496 } 497 return true; 498 } 499 500 private ServiceCallbacks getNewServiceCallbacks() { 501 return new ServiceCallbacks(this); 502 } 503 504 /** 505 * Log internal state. 506 * @hide 507 */ 508 void dump() { 509 Log.d(TAG, "MediaBrowser..."); 510 Log.d(TAG, " mServiceComponent=" + mServiceComponent); 511 Log.d(TAG, " mCallback=" + mCallback); 512 Log.d(TAG, " mRootHints=" + mRootHints); 513 Log.d(TAG, " mState=" + getStateLabel(mState)); 514 Log.d(TAG, " mServiceConnection=" + mServiceConnection); 515 Log.d(TAG, " mServiceBinder=" + mServiceBinder); 516 Log.d(TAG, " mServiceCallbacks=" + mServiceCallbacks); 517 Log.d(TAG, " mRootId=" + mRootId); 518 Log.d(TAG, " mMediaSessionToken=" + mMediaSessionToken); 519 } 520 521 public static class MediaItem implements Parcelable { 522 private final int mFlags; 523 private final MediaDescription mDescription; 524 525 /** @hide */ 526 @Retention(RetentionPolicy.SOURCE) 527 @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE }) 528 public @interface Flags { } 529 530 /** 531 * Flag: Indicates that the item has children of its own. 532 */ 533 public static final int FLAG_BROWSABLE = 1 << 0; 534 535 /** 536 * Flag: Indicates that the item is playable. 537 * <p> 538 * The id of this item may be passed to 539 * {@link MediaController.TransportControls#playFromMediaId(String, Bundle)} 540 * to start playing it. 541 * </p> 542 */ 543 public static final int FLAG_PLAYABLE = 1 << 1; 544 545 /** 546 * Create a new MediaItem for use in browsing media. 547 * @param description The description of the media, which must include a 548 * media id. 549 * @param flags The flags for this item. 550 */ 551 public MediaItem(@NonNull MediaDescription description, @Flags int flags) { 552 if (description == null) { 553 throw new IllegalArgumentException("description cannot be null"); 554 } 555 if (TextUtils.isEmpty(description.getMediaId())) { 556 throw new IllegalArgumentException("description must have a non-empty media id"); 557 } 558 mFlags = flags; 559 mDescription = description; 560 } 561 562 /** 563 * Private constructor. 564 */ 565 private MediaItem(Parcel in) { 566 mFlags = in.readInt(); 567 mDescription = MediaDescription.CREATOR.createFromParcel(in); 568 } 569 570 @Override 571 public int describeContents() { 572 return 0; 573 } 574 575 @Override 576 public void writeToParcel(Parcel out, int flags) { 577 out.writeInt(mFlags); 578 mDescription.writeToParcel(out, flags); 579 } 580 581 @Override 582 public String toString() { 583 final StringBuilder sb = new StringBuilder("MediaItem{"); 584 sb.append("mFlags=").append(mFlags); 585 sb.append(", mDescription=").append(mDescription); 586 sb.append('}'); 587 return sb.toString(); 588 } 589 590 public static final Parcelable.Creator<MediaItem> CREATOR = 591 new Parcelable.Creator<MediaItem>() { 592 @Override 593 public MediaItem createFromParcel(Parcel in) { 594 return new MediaItem(in); 595 } 596 597 @Override 598 public MediaItem[] newArray(int size) { 599 return new MediaItem[size]; 600 } 601 }; 602 603 /** 604 * Gets the flags of the item. 605 */ 606 public @Flags int getFlags() { 607 return mFlags; 608 } 609 610 /** 611 * Returns whether this item is browsable. 612 * @see #FLAG_BROWSABLE 613 */ 614 public boolean isBrowsable() { 615 return (mFlags & FLAG_BROWSABLE) != 0; 616 } 617 618 /** 619 * Returns whether this item is playable. 620 * @see #FLAG_PLAYABLE 621 */ 622 public boolean isPlayable() { 623 return (mFlags & FLAG_PLAYABLE) != 0; 624 } 625 626 /** 627 * Returns the description of the media. 628 */ 629 public @NonNull MediaDescription getDescription() { 630 return mDescription; 631 } 632 633 /** 634 * Returns the media id for this item. 635 */ 636 public @NonNull String getMediaId() { 637 return mDescription.getMediaId(); 638 } 639 } 640 641 642 /** 643 * Callbacks for connection related events. 644 */ 645 public static class ConnectionCallback { 646 /** 647 * Invoked after {@link MediaBrowser#connect()} when the request has successfully completed. 648 */ 649 public void onConnected() { 650 } 651 652 /** 653 * Invoked when the client is disconnected from the media browser. 654 */ 655 public void onConnectionSuspended() { 656 } 657 658 /** 659 * Invoked when the connection to the media browser failed. 660 */ 661 public void onConnectionFailed() { 662 } 663 } 664 665 /** 666 * Callbacks for subscription related events. 667 */ 668 public static abstract class SubscriptionCallback { 669 /** 670 * Called when the list of children is loaded or updated. 671 */ 672 public void onChildrenLoaded(@NonNull String parentId, 673 @NonNull List<MediaItem> children) { 674 } 675 676 /** 677 * Called when the id doesn't exist or other errors in subscribing. 678 * <p> 679 * If this is called, the subscription remains until {@link MediaBrowser#unsubscribe} 680 * called, because some errors may heal themselves. 681 * </p> 682 */ 683 public void onError(@NonNull String id) { 684 } 685 } 686 687 /** 688 * ServiceConnection to the other app. 689 */ 690 private class MediaServiceConnection implements ServiceConnection { 691 @Override 692 public void onServiceConnected(ComponentName name, IBinder binder) { 693 if (DBG) { 694 Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name 695 + " binder=" + binder); 696 dump(); 697 } 698 699 // Make sure we are still the current connection, and that they haven't called 700 // disconnect(). 701 if (!isCurrent("onServiceConnected")) { 702 return; 703 } 704 705 // Save their binder 706 mServiceBinder = IMediaBrowserService.Stub.asInterface(binder); 707 708 // We make a new mServiceCallbacks each time we connect so that we can drop 709 // responses from previous connections. 710 mServiceCallbacks = getNewServiceCallbacks(); 711 mState = CONNECT_STATE_CONNECTING; 712 713 // Call connect, which is async. When we get a response from that we will 714 // say that we're connected. 715 try { 716 if (DBG) { 717 Log.d(TAG, "ServiceCallbacks.onConnect..."); 718 dump(); 719 } 720 mServiceBinder.connect(mContext.getPackageName(), mRootHints, mServiceCallbacks); 721 } catch (RemoteException ex) { 722 // Connect failed, which isn't good. But the auto-reconnect on the service 723 // will take over and we will come back. We will also get the 724 // onServiceDisconnected, which has all the cleanup code. So let that do it. 725 Log.w(TAG, "RemoteException during connect for " + mServiceComponent); 726 if (DBG) { 727 Log.d(TAG, "ServiceCallbacks.onConnect..."); 728 dump(); 729 } 730 } 731 } 732 733 @Override 734 public void onServiceDisconnected(ComponentName name) { 735 if (DBG) { 736 Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name 737 + " this=" + this + " mServiceConnection=" + mServiceConnection); 738 dump(); 739 } 740 741 // Make sure we are still the current connection, and that they haven't called 742 // disconnect(). 743 if (!isCurrent("onServiceDisconnected")) { 744 return; 745 } 746 747 // Clear out what we set in onServiceConnected 748 mServiceBinder = null; 749 mServiceCallbacks = null; 750 751 // And tell the app that it's suspended. 752 mState = CONNECT_STATE_SUSPENDED; 753 mCallback.onConnectionSuspended(); 754 } 755 756 /** 757 * Return true if this is the current ServiceConnection. Also logs if it's not. 758 */ 759 private boolean isCurrent(String funcName) { 760 if (mServiceConnection != this) { 761 if (mState != CONNECT_STATE_DISCONNECTED) { 762 // Check mState, because otherwise this log is noisy. 763 Log.i(TAG, funcName + " for " + mServiceComponent + " with mServiceConnection=" 764 + mServiceConnection + " this=" + this); 765 } 766 return false; 767 } 768 return true; 769 } 770 } 771 772 /** 773 * Callbacks from the service. 774 */ 775 private static class ServiceCallbacks extends IMediaBrowserServiceCallbacks.Stub { 776 private WeakReference<MediaBrowser> mMediaBrowser; 777 778 public ServiceCallbacks(MediaBrowser mediaBrowser) { 779 mMediaBrowser = new WeakReference<MediaBrowser>(mediaBrowser); 780 } 781 782 /** 783 * The other side has acknowledged our connection. The parameters to this function 784 * are the initial data as requested. 785 */ 786 @Override 787 public void onConnect(final String root, final MediaSession.Token session, 788 final Bundle extras) { 789 MediaBrowser mediaBrowser = mMediaBrowser.get(); 790 if (mediaBrowser != null) { 791 mediaBrowser.onServiceConnected(this, root, session, extras); 792 } 793 } 794 795 /** 796 * The other side does not like us. Tell the app via onConnectionFailed. 797 */ 798 @Override 799 public void onConnectFailed() { 800 MediaBrowser mediaBrowser = mMediaBrowser.get(); 801 if (mediaBrowser != null) { 802 mediaBrowser.onConnectionFailed(this); 803 } 804 } 805 806 @Override 807 public void onLoadChildren(final String parentId, final ParceledListSlice list) { 808 MediaBrowser mediaBrowser = mMediaBrowser.get(); 809 if (mediaBrowser != null) { 810 mediaBrowser.onLoadChildren(this, parentId, list); 811 } 812 } 813 } 814 815 private static class Subscription { 816 final String id; 817 SubscriptionCallback callback; 818 819 Subscription(String id) { 820 this.id = id; 821 } 822 } 823} 824