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