Home | History | Annotate | Download | only in newavrcp
      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 = "NewAvrcpBrowsablePlayerConnector";
     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 {
     58         void run(List<BrowsedPlayerWrapper> result);
     59     }
     60 
     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 
     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                         for (BrowsedPlayerWrapper wrapper : mPendingPlayers) {
    156                             if (DEBUG) Log.d(TAG, "Disconnecting " + wrapper.getPackageName());
    157                             wrapper.disconnect();
    158                         }
    159                         mPendingPlayers.clear();
    160                     } break;
    161                 }
    162 
    163                 if (mPendingPlayers.size() == 0) {
    164                     Log.i(TAG, "Successfully connected to "
    165                             + mResults.size() + " browsable players.");
    166                     removeMessages(MSG_TIMEOUT);
    167                     mCallback.run(mResults);
    168                 }
    169             }
    170         };
    171     }
    172 }
    173