MediaBrowser.java revision 5f3e1f2a2dfaa4d1abdda4d0cd7871aea82ffcbd
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 root Uri. 229 * <p> 230 * Note that the root uri may become invalid or change when when the 231 * browser is disconnected. 232 * </p> 233 * 234 * @throws IllegalStateException if not connected. 235 */ 236 public @NonNull Uri getRoot() { 237 if (mState != CONNECT_STATE_CONNECTED) { 238 throw new IllegalStateException("getSessionToken() called while not connected (state=" 239 + getStateLabel(mState) + ")"); 240 } 241 return mRootUri; 242 } 243 244 /** 245 * Gets any extras for the media service. 246 * 247 * @throws IllegalStateException if not connected. 248 */ 249 public @Nullable Bundle getExtras() { 250 if (mState != CONNECT_STATE_CONNECTED) { 251 throw new IllegalStateException("getExtras() called while not connected (state=" 252 + getStateLabel(mState) + ")"); 253 } 254 return mExtras; 255 } 256 257 /** 258 * Gets the media session token associated with the media browser. 259 * <p> 260 * Note that the session token may become invalid or change when when the 261 * browser is disconnected. 262 * </p> 263 * 264 * @return The session token for the browser, never null. 265 * 266 * @throws IllegalStateException if not connected. 267 */ 268 public @NonNull MediaSession.Token getSessionToken() { 269 if (mState != CONNECT_STATE_CONNECTED) { 270 throw new IllegalStateException("getSessionToken() called while not connected (state=" 271 + mState + ")"); 272 } 273 return mMediaSessionToken; 274 } 275 276 /** 277 * Queries for information about the media items that are contained within 278 * the specified Uri and subscribes to receive updates when they change. 279 * <p> 280 * The list of subscriptions is maintained even when not connected and is 281 * restored after reconnection. It is ok to subscribe while not connected 282 * but the results will not be returned until the connection completes. 283 * </p><p> 284 * If the uri is already subscribed with a different callback then the new 285 * callback will replace the previous one. 286 * </p> 287 * 288 * @param parentUri The uri of the parent media item whose list of children 289 * will be subscribed. 290 * @param callback The callback to receive the list of children. 291 */ 292 public void subscribe(@NonNull Uri parentUri, @NonNull SubscriptionCallback callback) { 293 // Check arguments. 294 if (parentUri == null) { 295 throw new IllegalArgumentException("parentUri is null"); 296 } 297 if (callback == null) { 298 throw new IllegalArgumentException("callback is null"); 299 } 300 301 // Update or create the subscription. 302 Subscription sub = mSubscriptions.get(parentUri); 303 boolean newSubscription = sub == null; 304 if (newSubscription) { 305 sub = new Subscription(parentUri); 306 mSubscriptions.put(parentUri, sub); 307 } 308 sub.callback = callback; 309 310 // If we are connected, tell the service that we are watching. If we aren't 311 // connected, the service will be told when we connect. 312 if (mState == CONNECT_STATE_CONNECTED && newSubscription) { 313 try { 314 mServiceBinder.addSubscription(parentUri, mServiceCallbacks); 315 } catch (RemoteException ex) { 316 // Process is crashing. We will disconnect, and upon reconnect we will 317 // automatically reregister. So nothing to do here. 318 Log.d(TAG, "addSubscription failed with RemoteException parentUri=" + parentUri); 319 } 320 } 321 } 322 323 /** 324 * Unsubscribes for changes to the children of the specified Uri. 325 * <p> 326 * The query callback will no longer be invoked for results associated with 327 * this Uri once this method returns. 328 * </p> 329 * 330 * @param parentUri The uri of the parent media item whose list of children 331 * will be unsubscribed. 332 */ 333 public void unsubscribe(@NonNull Uri parentUri) { 334 // Check arguments. 335 if (parentUri == null) { 336 throw new IllegalArgumentException("parentUri is null"); 337 } 338 339 // Remove from our list. 340 final Subscription sub = mSubscriptions.remove(parentUri); 341 342 // Tell the service if necessary. 343 if (mState == CONNECT_STATE_CONNECTED && sub != null) { 344 try { 345 mServiceBinder.removeSubscription(parentUri, mServiceCallbacks); 346 } catch (RemoteException ex) { 347 // Process is crashing. We will disconnect, and upon reconnect we will 348 // automatically reregister. So nothing to do here. 349 Log.d(TAG, "removeSubscription failed with RemoteException parentUri=" + parentUri); 350 } 351 } 352 } 353 354 /** 355 * Loads the icon of a media item. 356 * 357 * @param uri The uri of the Icon. 358 * @param width The preferred width of the icon in dp. 359 * @param height The preferred width of the icon in dp. 360 * @param callback The callback to receive the icon. 361 */ 362 public void loadIcon(final @NonNull Uri uri, final int width, final int height, 363 final @NonNull IconCallback callback) { 364 if (uri == null) { 365 throw new IllegalArgumentException("Icon uri cannot be null"); 366 } 367 if (callback == null) { 368 throw new IllegalArgumentException("Icon callback cannot be null"); 369 } 370 mHandler.post(new Runnable() { 371 @Override 372 public void run() { 373 for (int i = 0; i < mIconRequests.size(); i++) { 374 IconRequest existingRequest = mIconRequests.valueAt(i); 375 if (existingRequest.isSameRequest(uri, width, height)) { 376 existingRequest.addCallback(callback); 377 return; 378 } 379 } 380 final int seq = mNextSeq++; 381 IconRequest request = new IconRequest(seq, uri, width, height); 382 request.addCallback(callback); 383 mIconRequests.put(seq, request); 384 if (mState == CONNECT_STATE_CONNECTED) { 385 try { 386 mServiceBinder.loadIcon(seq, uri, width, height, mServiceCallbacks); 387 } catch (RemoteException e) { 388 // Process is crashing. We will disconnect, and upon reconnect we will 389 // automatically reload the icons. So nothing to do here. 390 Log.d(TAG, "loadIcon failed with RemoteException uri=" + uri); 391 } 392 } 393 } 394 }); 395 } 396 397 /** 398 * For debugging. 399 */ 400 private static String getStateLabel(int state) { 401 switch (state) { 402 case CONNECT_STATE_DISCONNECTED: 403 return "CONNECT_STATE_DISCONNECTED"; 404 case CONNECT_STATE_CONNECTING: 405 return "CONNECT_STATE_CONNECTING"; 406 case CONNECT_STATE_CONNECTED: 407 return "CONNECT_STATE_CONNECTED"; 408 case CONNECT_STATE_SUSPENDED: 409 return "CONNECT_STATE_SUSPENDED"; 410 default: 411 return "UNKNOWN/" + state; 412 } 413 } 414 415 private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback, 416 final Uri root, final MediaSession.Token session, final Bundle extra) { 417 mHandler.post(new Runnable() { 418 @Override 419 public void run() { 420 // Check to make sure there hasn't been a disconnect or a different 421 // ServiceConnection. 422 if (!isCurrent(callback, "onConnect")) { 423 return; 424 } 425 // Don't allow them to call us twice. 426 if (mState != CONNECT_STATE_CONNECTING) { 427 Log.w(TAG, "onConnect from service while mState=" 428 + getStateLabel(mState) + "... ignoring"); 429 return; 430 } 431 mRootUri = root; 432 mMediaSessionToken = session; 433 mExtras = extra; 434 mState = CONNECT_STATE_CONNECTED; 435 436 if (DBG) { 437 Log.d(TAG, "ServiceCallbacks.onConnect..."); 438 dump(); 439 } 440 mCallback.onConnected(); 441 442 // we may receive some subscriptions before we are connected, so re-subscribe 443 // everything now 444 for (Uri uri : mSubscriptions.keySet()) { 445 try { 446 mServiceBinder.addSubscription(uri, mServiceCallbacks); 447 } catch (RemoteException ex) { 448 // Process is crashing. We will disconnect, and upon reconnect we will 449 // automatically reregister. So nothing to do here. 450 Log.d(TAG, "addSubscription failed with RemoteException parentUri=" + uri); 451 } 452 } 453 454 for (int i = 0; i < mIconRequests.size(); i++) { 455 IconRequest request = mIconRequests.valueAt(i); 456 try { 457 mServiceBinder.loadIcon(request.mSeq, request.mUri, 458 request.mWidth, request.mHeight, mServiceCallbacks); 459 } catch (RemoteException e) { 460 // Process is crashing. We will disconnect, and upon reconnect we will 461 // automatically reload. So nothing to do here. 462 Log.d(TAG, "loadIcon failed with RemoteException request=" + request); 463 } 464 } 465 } 466 }); 467 } 468 469 private final void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) { 470 mHandler.post(new Runnable() { 471 @Override 472 public void run() { 473 Log.e(TAG, "onConnectFailed for " + mServiceComponent); 474 475 // Check to make sure there hasn't been a disconnect or a different 476 // ServiceConnection. 477 if (!isCurrent(callback, "onConnectFailed")) { 478 return; 479 } 480 // Don't allow them to call us twice. 481 if (mState != CONNECT_STATE_CONNECTING) { 482 Log.w(TAG, "onConnect from service while mState=" 483 + getStateLabel(mState) + "... ignoring"); 484 return; 485 } 486 487 // Clean up 488 forceCloseConnection(); 489 490 // Tell the app. 491 mCallback.onConnectionFailed(); 492 } 493 }); 494 } 495 496 private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback, final Uri uri, 497 final ParceledListSlice list) { 498 mHandler.post(new Runnable() { 499 @Override 500 public void run() { 501 // Check that there hasn't been a disconnect or a different 502 // ServiceConnection. 503 if (!isCurrent(callback, "onLoadChildren")) { 504 return; 505 } 506 507 List<MediaBrowserItem> data = list.getList(); 508 if (DBG) { 509 Log.d(TAG, "onLoadChildren for " + mServiceComponent + " uri=" + uri); 510 } 511 if (data == null) { 512 data = Collections.emptyList(); 513 } 514 515 // Check that the subscription is still subscribed. 516 final Subscription subscription = mSubscriptions.get(uri); 517 if (subscription == null) { 518 if (DBG) { 519 Log.d(TAG, "onLoadChildren for uri that isn't subscribed uri=" 520 + uri); 521 } 522 return; 523 } 524 525 // Tell the app. 526 subscription.callback.onChildrenLoaded(uri, data); 527 } 528 }); 529 } 530 531 private final void onLoadIcon(final IMediaBrowserServiceCallbacks callback, 532 final int seqNum, final Bitmap bitmap) { 533 mHandler.post(new Runnable() { 534 @Override 535 public void run() { 536 // Check that there hasn't been a disconnect or a different 537 // ServiceConnection. 538 if (!isCurrent(callback, "onLoadIcon")) { 539 return; 540 } 541 542 IconRequest request = mIconRequests.get(seqNum); 543 if (request == null) { 544 Log.d(TAG, "onLoadIcon called for seqNum=" + seqNum + " request=" 545 + request + " but the request is not registered"); 546 return; 547 } 548 mIconRequests.delete(seqNum); 549 for (IconCallback IconCallback : request.getCallbacks()) { 550 IconCallback.onIconLoaded(request.mUri, bitmap); 551 } 552 } 553 }); 554 } 555 556 557 /** 558 * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not. 559 */ 560 private boolean isCurrent(IMediaBrowserServiceCallbacks callback, String funcName) { 561 if (mServiceCallbacks != callback) { 562 if (mState != CONNECT_STATE_DISCONNECTED) { 563 Log.i(TAG, funcName + " for " + mServiceComponent + " with mServiceConnection=" 564 + mServiceCallbacks + " this=" + this); 565 } 566 return false; 567 } 568 return true; 569 } 570 571 private ServiceCallbacks getNewServiceCallbacks() { 572 return new ServiceCallbacks(this); 573 } 574 575 /** 576 * Log internal state. 577 * @hide 578 */ 579 void dump() { 580 Log.d(TAG, "MediaBrowser..."); 581 Log.d(TAG, " mServiceComponent=" + mServiceComponent); 582 Log.d(TAG, " mCallback=" + mCallback); 583 Log.d(TAG, " mRootHints=" + mRootHints); 584 Log.d(TAG, " mState=" + getStateLabel(mState)); 585 Log.d(TAG, " mServiceConnection=" + mServiceConnection); 586 Log.d(TAG, " mServiceBinder=" + mServiceBinder); 587 Log.d(TAG, " mServiceCallbacks=" + mServiceCallbacks); 588 Log.d(TAG, " mRootUri=" + mRootUri); 589 Log.d(TAG, " mMediaSessionToken=" + mMediaSessionToken); 590 } 591 592 593 /** 594 * Callbacks for connection related events. 595 */ 596 public static class ConnectionCallback { 597 /** 598 * Invoked after {@link MediaBrowser#connect()} when the request has successfully completed. 599 */ 600 public void onConnected() { 601 } 602 603 /** 604 * Invoked when the client is disconnected from the media browser. 605 */ 606 public void onConnectionSuspended() { 607 } 608 609 /** 610 * Invoked when the connection to the media browser failed. 611 */ 612 public void onConnectionFailed() { 613 } 614 } 615 616 /** 617 * Callbacks for subscription related events. 618 */ 619 public static abstract class SubscriptionCallback { 620 /** 621 * Called when the list of children is loaded or updated. 622 */ 623 public void onChildrenLoaded(@NonNull Uri parentUri, 624 @NonNull List<MediaBrowserItem> children) { 625 } 626 627 /** 628 * Called when the Uri doesn't exist or other errors in subscribing. 629 * <p> 630 * If this is called, the subscription remains until {@link MediaBrowser#unsubscribe} 631 * called, because some errors may heal themselves. 632 * </p> 633 */ 634 public void onError(@NonNull Uri uri) { 635 } 636 } 637 638 /** 639 * Callbacks for icon loading. 640 */ 641 public static abstract class IconCallback { 642 /** 643 * Called when the icon is loaded. 644 */ 645 public void onIconLoaded(@NonNull Uri uri, @NonNull Bitmap bitmap) { 646 } 647 648 /** 649 * Called when the Uri doesn’t exist or the bitmap cannot be loaded. 650 */ 651 public void onError(@NonNull Uri uri) { 652 } 653 } 654 655 private static class IconRequest { 656 final int mSeq; 657 final Uri mUri; 658 final int mWidth; 659 final int mHeight; 660 final List<IconCallback> mCallbacks; 661 662 /** 663 * Constructs an icon request. 664 * @param seq The unique sequence number assigned to the request by the media browser. 665 * @param uri The Uri for the icon. 666 * @param width The width for the icon. 667 * @param height The height for the icon. 668 */ 669 IconRequest(int seq, @NonNull Uri uri, int width, int height) { 670 if (uri == null) { 671 throw new IllegalArgumentException("Icon uri cannot be null"); 672 } 673 this.mSeq = seq; 674 this.mUri = uri; 675 this.mWidth = width; 676 this.mHeight = height; 677 mCallbacks = new ArrayList<IconCallback>(); 678 } 679 680 /** 681 * Adds a callback to the icon request. 682 * If the callback already exists, it will not be added again. 683 */ 684 public void addCallback(@NonNull IconCallback callback) { 685 if (callback == null) { 686 throw new IllegalArgumentException("callback cannot be null in IconRequest"); 687 } 688 if (!mCallbacks.contains(callback)) { 689 mCallbacks.add(callback); 690 } 691 } 692 693 /** 694 * Checks if the icon request has the same uri, width, and height as the given values. 695 */ 696 public boolean isSameRequest(@Nullable Uri uri, int width, int height) { 697 return Objects.equals(mUri, uri) && mWidth == width && mHeight == height; 698 } 699 700 @Override 701 public String toString() { 702 final StringBuilder sb = new StringBuilder("IconRequest{"); 703 sb.append("uri=").append(mUri); 704 sb.append(", width=").append(mWidth); 705 sb.append(", height=").append(mHeight); 706 sb.append(", seq=").append(mSeq); 707 sb.append('}'); 708 return sb.toString(); 709 } 710 711 /** 712 * Gets an unmodifiable view of the list of callbacks associated with the request. 713 */ 714 public List<IconCallback> getCallbacks() { 715 return Collections.unmodifiableList(mCallbacks); 716 } 717 } 718 719 /** 720 * ServiceConnection to the other app. 721 */ 722 private class MediaServiceConnection implements ServiceConnection { 723 @Override 724 public void onServiceConnected(ComponentName name, IBinder binder) { 725 if (DBG) { 726 Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name 727 + " binder=" + binder); 728 dump(); 729 } 730 731 // Make sure we are still the current connection, and that they haven't called 732 // disconnect(). 733 if (!isCurrent("onServiceConnected")) { 734 return; 735 } 736 737 // Save their binder 738 mServiceBinder = IMediaBrowserService.Stub.asInterface(binder); 739 740 // We make a new mServiceCallbacks each time we connect so that we can drop 741 // responses from previous connections. 742 mServiceCallbacks = getNewServiceCallbacks(); 743 744 // Call connect, which is async. When we get a response from that we will 745 // say that we're connected. 746 try { 747 if (DBG) { 748 Log.d(TAG, "ServiceCallbacks.onConnect..."); 749 dump(); 750 } 751 mServiceBinder.connect(mContext.getPackageName(), mRootHints, mServiceCallbacks); 752 } catch (RemoteException ex) { 753 // Connect failed, which isn't good. But the auto-reconnect on the service 754 // will take over and we will come back. We will also get the 755 // onServiceDisconnected, which has all the cleanup code. So let that do it. 756 Log.w(TAG, "RemoteException during connect for " + mServiceComponent); 757 if (DBG) { 758 Log.d(TAG, "ServiceCallbacks.onConnect..."); 759 dump(); 760 } 761 } 762 } 763 764 @Override 765 public void onServiceDisconnected(ComponentName name) { 766 if (DBG) { 767 Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name 768 + " this=" + this + " mServiceConnection=" + mServiceConnection); 769 dump(); 770 } 771 772 // Make sure we are still the current connection, and that they haven't called 773 // disconnect(). 774 if (!isCurrent("onServiceDisconnected")) { 775 return; 776 } 777 778 // Clear out what we set in onServiceConnected 779 mServiceBinder = null; 780 mServiceCallbacks = null; 781 782 // And tell the app that it's suspended. 783 mState = CONNECT_STATE_SUSPENDED; 784 mCallback.onConnectionSuspended(); 785 } 786 787 /** 788 * Return true if this is the current ServiceConnection. Also logs if it's not. 789 */ 790 private boolean isCurrent(String funcName) { 791 if (mServiceConnection != this) { 792 if (mState != CONNECT_STATE_DISCONNECTED) { 793 // Check mState, because otherwise this log is noisy. 794 Log.i(TAG, funcName + " for " + mServiceComponent + " with mServiceConnection=" 795 + mServiceConnection + " this=" + this); 796 } 797 return false; 798 } 799 return true; 800 } 801 }; 802 803 /** 804 * Callbacks from the service. 805 */ 806 private static class ServiceCallbacks extends IMediaBrowserServiceCallbacks.Stub { 807 private WeakReference<MediaBrowser> mMediaBrowser; 808 809 public ServiceCallbacks(MediaBrowser mediaBrowser) { 810 mMediaBrowser = new WeakReference<MediaBrowser>(mediaBrowser); 811 } 812 813 /** 814 * The other side has acknowledged our connection. The parameters to this function 815 * are the initial data as requested. 816 */ 817 @Override 818 public void onConnect(final Uri root, final MediaSession.Token session, 819 final Bundle extras) { 820 MediaBrowser mediaBrowser = mMediaBrowser.get(); 821 if (mediaBrowser != null) { 822 mediaBrowser.onServiceConnected(this, root, session, extras); 823 } 824 } 825 826 /** 827 * The other side does not like us. Tell the app via onConnectionFailed. 828 */ 829 @Override 830 public void onConnectFailed() { 831 MediaBrowser mediaBrowser = mMediaBrowser.get(); 832 if (mediaBrowser != null) { 833 mediaBrowser.onConnectionFailed(this); 834 } 835 } 836 837 @Override 838 public void onLoadChildren(final Uri uri, final ParceledListSlice list) { 839 MediaBrowser mediaBrowser = mMediaBrowser.get(); 840 if (mediaBrowser != null) { 841 mediaBrowser.onLoadChildren(this, uri, list); 842 } 843 } 844 845 @Override 846 public void onLoadIcon(final int seqNum, final Bitmap bitmap) { 847 MediaBrowser mediaBrowser = mMediaBrowser.get(); 848 if (mediaBrowser != null) { 849 mediaBrowser.onLoadIcon(this, seqNum, bitmap); 850 } 851 } 852 } 853 854 private static class Subscription { 855 final Uri uri; 856 SubscriptionCallback callback; 857 858 Subscription(Uri u) { 859 this.uri = u; 860 } 861 } 862} 863