1 /* 2 * Copyright 2018 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 17 package com.android.car.media.common.source; 18 19 import static com.android.car.apps.common.util.CarAppsDebugUtils.idHash; 20 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.os.Bundle; 24 import android.support.v4.media.MediaBrowserCompat; 25 import android.util.Log; 26 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 30 import com.android.car.media.common.MediaConstants; 31 32 /** 33 * A helper class to connect to a single {@link MediaBrowserCompat}. Connecting to a new one 34 * automatically disconnects the previous browser. Changes of the currently connected browser are 35 * sent via {@link MediaBrowserConnector.Callback}. 36 */ 37 38 public class MediaBrowserConnector { 39 40 private static final String TAG = "MediaBrowserConnector"; 41 42 /** The callback to receive the currently connected {@link MediaBrowserCompat}. */ 43 public interface Callback { 44 /** When disconnecting, the given browser will be null. */ onConnectedBrowserChanged(@ullable MediaBrowserCompat browser)45 void onConnectedBrowserChanged(@Nullable MediaBrowserCompat browser); 46 } 47 48 private final Context mContext; 49 private final Callback mCallback; 50 private final int mMaxBitmapSizePx; 51 52 @Nullable private ComponentName mBrowseService; 53 @Nullable private MediaBrowserCompat mBrowser; 54 55 /** 56 * Create a new MediaBrowserConnector. 57 * 58 * @param context The Context with which to build MediaBrowsers. 59 */ MediaBrowserConnector(@onNull Context context, @NonNull Callback callback)60 public MediaBrowserConnector(@NonNull Context context, @NonNull Callback callback) { 61 mContext = context; 62 mCallback = callback; 63 mMaxBitmapSizePx = mContext.getResources().getInteger( 64 com.android.car.media.common.R.integer.media_items_bitmap_max_size_px); 65 } 66 67 /** Counter so callbacks from obsolete connections can be ignored. */ 68 private int mBrowserConnectionCallbackCounter = 0; 69 70 private class BrowserConnectionCallback extends MediaBrowserCompat.ConnectionCallback { 71 72 private final int mSequenceNumber = ++mBrowserConnectionCallbackCounter; 73 private final String mCallbackPackage = mBrowseService.getPackageName(); 74 isValidCall(String method)75 private boolean isValidCall(String method) { 76 if (mSequenceNumber != mBrowserConnectionCallbackCounter) { 77 Log.e(TAG, "Ignoring callback " + method + " for " + mCallbackPackage + " seq: " 78 + mSequenceNumber + " current: " + mBrowserConnectionCallbackCounter 79 + " current: " + mBrowseService.getPackageName()); 80 return false; 81 } else if (Log.isLoggable(TAG, Log.DEBUG)) { 82 Log.d(TAG, method + " " + mBrowseService.getPackageName() + idHash(mBrowser)); 83 } 84 return true; 85 } 86 87 @Override onConnected()88 public void onConnected() { 89 if (isValidCall("onConnected")) { 90 mCallback.onConnectedBrowserChanged(mBrowser); 91 } 92 } 93 94 @Override onConnectionFailed()95 public void onConnectionFailed() { 96 if (isValidCall("onConnectionFailed")) { 97 mCallback.onConnectedBrowserChanged(null); 98 } 99 } 100 101 @Override onConnectionSuspended()102 public void onConnectionSuspended() { 103 if (isValidCall("onConnectionSuspended")) { 104 mCallback.onConnectedBrowserChanged(null); 105 } 106 } 107 } 108 109 /** 110 * Creates and connects a new {@link MediaBrowserCompat} if the given {@link ComponentName} 111 * isn't null. If needed, the previous browser is disconnected. 112 * @param browseService the ComponentName of the media browser service. 113 * @see MediaBrowserCompat#MediaBrowserCompat(Context, ComponentName, 114 * MediaBrowserCompat.ConnectionCallback, android.os.Bundle) 115 */ connectTo(@ullable ComponentName browseService)116 public void connectTo(@Nullable ComponentName browseService) { 117 if (mBrowser != null && mBrowser.isConnected()) { 118 if (Log.isLoggable(TAG, Log.DEBUG)) { 119 Log.d(TAG, "Disconnecting: " + mBrowseService.getPackageName() + idHash(mBrowser)); 120 } 121 mCallback.onConnectedBrowserChanged(null); 122 mBrowser.disconnect(); 123 } 124 125 mBrowseService = browseService; 126 if (mBrowseService != null) { 127 mBrowser = createMediaBrowser(mBrowseService, new BrowserConnectionCallback()); 128 if (Log.isLoggable(TAG, Log.DEBUG)) { 129 Log.d(TAG, "Connecting to: " + mBrowseService.getPackageName() + idHash(mBrowser)); 130 } 131 try { 132 mBrowser.connect(); 133 } catch (IllegalStateException ex) { 134 // Is this comment still valid ? 135 // Ignore: MediaBrowse could be in an intermediate state (not connected, but not 136 // disconnected either.). In this situation, trying to connect again can throw 137 // this exception, but there is no way to know without trying. 138 Log.e(TAG, "Connection exception: " + ex); 139 } 140 } else { 141 mBrowser = null; 142 } 143 } 144 145 // Override for testing. 146 @NonNull createMediaBrowser(@onNull ComponentName browseService, @NonNull MediaBrowserCompat.ConnectionCallback callback)147 protected MediaBrowserCompat createMediaBrowser(@NonNull ComponentName browseService, 148 @NonNull MediaBrowserCompat.ConnectionCallback callback) { 149 Bundle rootHints = new Bundle(); 150 rootHints.putInt(MediaConstants.EXTRA_MEDIA_ART_SIZE_HINT_PIXELS, mMaxBitmapSizePx); 151 return new MediaBrowserCompat(mContext, browseService, callback, rootHints); 152 } 153 } 154