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