• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.bluetooth.avrcp;
18 
19 import android.content.Context;
20 import android.content.pm.ResolveInfo;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.os.Message;
24 import android.util.Log;
25 
26 import java.util.ArrayList;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Set;
30 
31 /**
32  * This class provides a way to connect to multiple browsable players at a time.
33  * It will attempt to simultaneously connect to a list of services that support
34  * the MediaBrowserService. After a timeout, the list of connected players will
35  * be returned via callback.
36  *
37  * The main use of this class is to check whether a player can be browsed despite
38  * using the MediaBrowserService. This way we do not have to do the same checks
39  * when constructing BrowsedPlayerWrappers by hand.
40  */
41 public class BrowsablePlayerConnector {
42     private static final String TAG = "AvrcpBrowsablePlayerConnector";
43     private static final boolean DEBUG = true;
44     private static final long CONNECT_TIMEOUT_MS = 10000; // Time in ms to wait for a connection
45 
46     private static final int MSG_GET_FOLDER_ITEMS_CB = 0;
47     private static final int MSG_CONNECT_CB = 1;
48     private static final int MSG_TIMEOUT = 2;
49 
50     private Handler mHandler;
51     private Context mContext;
52     private PlayerListCallback mCallback;
53 
54     private List<BrowsedPlayerWrapper> mResults = new ArrayList<BrowsedPlayerWrapper>();
55     private Set<BrowsedPlayerWrapper> mPendingPlayers = new HashSet<BrowsedPlayerWrapper>();
56 
57     interface PlayerListCallback {
run(List<BrowsedPlayerWrapper> result)58         void run(List<BrowsedPlayerWrapper> result);
59     }
60 
connectToPlayers( Context context, Looper looper, List<ResolveInfo> players, PlayerListCallback cb)61     static BrowsablePlayerConnector connectToPlayers(
62             Context context,
63             Looper looper,
64             List<ResolveInfo> players,
65             PlayerListCallback cb) {
66         if (cb == null) {
67             Log.wtfStack(TAG, "Null callback passed");
68             return null;
69         }
70 
71         BrowsablePlayerConnector newWrapper = new BrowsablePlayerConnector(context, looper, cb);
72 
73         // Try to start connecting all the browsed player wrappers
74         for (ResolveInfo info : players) {
75             BrowsedPlayerWrapper player = BrowsedPlayerWrapper.wrap(
76                             context,
77                             info.serviceInfo.packageName,
78                             info.serviceInfo.name);
79             newWrapper.mPendingPlayers.add(player);
80             player.connect((int status, BrowsedPlayerWrapper wrapper) -> {
81                 // Use the handler to avoid concurrency issues
82                 if (DEBUG) {
83                     Log.d(TAG, "Browse player callback called: package="
84                             + info.serviceInfo.packageName
85                             + " : status=" + status);
86                 }
87                 Message msg = newWrapper.mHandler.obtainMessage(MSG_CONNECT_CB);
88                 msg.arg1 = status;
89                 msg.obj = wrapper;
90                 newWrapper.mHandler.sendMessage(msg);
91             });
92         }
93 
94         Message msg = newWrapper.mHandler.obtainMessage(MSG_TIMEOUT);
95         newWrapper.mHandler.sendMessageDelayed(msg, CONNECT_TIMEOUT_MS);
96         return newWrapper;
97     }
98 
BrowsablePlayerConnector(Context context, Looper looper, PlayerListCallback cb)99     private BrowsablePlayerConnector(Context context, Looper looper, PlayerListCallback cb) {
100         mContext = context;
101         mCallback = cb;
102         mHandler = new Handler(looper) {
103             public void handleMessage(Message msg) {
104                 if (DEBUG) Log.d(TAG, "Received a message: msg.what=" + msg.what);
105                 switch(msg.what) {
106                     case MSG_GET_FOLDER_ITEMS_CB: {
107                         BrowsedPlayerWrapper wrapper = (BrowsedPlayerWrapper) msg.obj;
108                         // If we failed to remove the wrapper from the pending set, that
109                         // means a timeout occurred and the callback was triggered afterwards
110                         if (!mPendingPlayers.remove(wrapper)) {
111                             return;
112                         }
113 
114                         Log.i(TAG, "Successfully added package to results: "
115                                 + wrapper.getPackageName());
116                         mResults.add(wrapper);
117                     } break;
118 
119                     case MSG_CONNECT_CB: {
120                         BrowsedPlayerWrapper wrapper = (BrowsedPlayerWrapper) msg.obj;
121 
122                         if (msg.arg1 != BrowsedPlayerWrapper.STATUS_SUCCESS) {
123                             Log.i(TAG, wrapper.getPackageName() + " is not browsable");
124                             mPendingPlayers.remove(wrapper);
125                             return;
126                         }
127 
128                         // Check to see if the root folder has any items
129                         if (DEBUG) {
130                             Log.i(TAG, "Checking root contents for " + wrapper.getPackageName());
131                         }
132                         wrapper.getFolderItems(wrapper.getRootId(),
133                                 (int status, String mediaId, List<ListItem> results) -> {
134                                     if (status != BrowsedPlayerWrapper.STATUS_SUCCESS) {
135                                         mPendingPlayers.remove(wrapper);
136                                         return;
137                                     }
138 
139                                     if (results.size() == 0) {
140                                         mPendingPlayers.remove(wrapper);
141                                         return;
142                                     }
143 
144                                     // Send the response as a message so that it is properly
145                                     // synchronized
146                                     Message success =
147                                             mHandler.obtainMessage(MSG_GET_FOLDER_ITEMS_CB);
148                                     success.obj = wrapper;
149                                     mHandler.sendMessage(success);
150                                 });
151                     } break;
152 
153                     case MSG_TIMEOUT: {
154                         Log.v(TAG, "Timed out waiting for players");
155                         removePendingPlayers();
156                     } break;
157                 }
158 
159                 if (mPendingPlayers.size() == 0) {
160                     Log.i(TAG, "Successfully connected to "
161                             + mResults.size() + " browsable players.");
162                     removeMessages(MSG_TIMEOUT);
163                     mCallback.run(mResults);
164                 }
165             }
166         };
167     }
168 
removePendingPlayers()169     private void removePendingPlayers() {
170         for (BrowsedPlayerWrapper wrapper : mPendingPlayers) {
171             if (DEBUG) Log.d(TAG, "Disconnecting " + wrapper.getPackageName());
172             wrapper.disconnect();
173         }
174         mPendingPlayers.clear();
175     }
176 
cleanup()177     void cleanup() {
178         if (mPendingPlayers.size() != 0) {
179             Log.i(TAG, "Bluetooth turn off with " + mPendingPlayers.size() + " pending player(s)");
180             mHandler.removeMessages(MSG_TIMEOUT);
181             removePendingPlayers();
182             mHandler = null;
183         }
184     }
185 }
186