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