Home | History | Annotate | Download | only in map
      1 /*
      2 * Copyright (C) 2014 Samsung System LSI
      3 * Licensed under the Apache License, Version 2.0 (the "License");
      4 * you may not use this file except in compliance with the License.
      5 * You may obtain a copy of the License at
      6 *
      7 *      http://www.apache.org/licenses/LICENSE-2.0
      8 *
      9 * Unless required by applicable law or agreed to in writing, software
     10 * distributed under the License is distributed on an "AS IS" BASIS,
     11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 * See the License for the specific language governing permissions and
     13 * limitations under the License.
     14 */
     15 
     16 package com.android.bluetooth.map;
     17 
     18 import java.util.ArrayList;
     19 import java.util.LinkedHashMap;
     20 import java.util.List;
     21 
     22 import android.content.ContentProviderClient;
     23 import android.content.ContentResolver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.pm.ApplicationInfo;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.ProviderInfo;
     29 import android.content.pm.ResolveInfo;
     30 import android.database.Cursor;
     31 import android.net.Uri;
     32 import android.os.RemoteException;
     33 import android.text.format.DateUtils;
     34 import android.util.Log;
     35 
     36 import com.android.bluetooth.map.BluetoothMapAccountItem;
     37 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
     38 import com.android.bluetooth.mapapi.BluetoothMapContract;
     39 
     40 public class BluetoothMapAccountLoader {
     41     private static final String TAG = "BluetoothMapAccountLoader";
     42     private static final boolean D = BluetoothMapService.DEBUG;
     43     private static final boolean V = BluetoothMapService.VERBOSE;
     44     private Context mContext = null;
     45     private PackageManager mPackageManager = null;
     46     private ContentResolver mResolver;
     47     private int mAccountsEnabledCount = 0;
     48     private ContentProviderClient mProviderClient = null;
     49     private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
     50 
     51     public BluetoothMapAccountLoader(Context ctx)
     52     {
     53         mContext = ctx;
     54     }
     55 
     56     /**
     57      * Method to look through all installed packages system-wide and find those that contain one of
     58      * the BT-MAP intents in their manifest file. For each app the list of accounts are fetched
     59      * using the method parseAccounts().
     60      * @return LinkedHashMap with the packages as keys(BluetoothMapAccountItem) and
     61      *          values as ArrayLists of BluetoothMapAccountItems.
     62      */
     63     public LinkedHashMap<BluetoothMapAccountItem,
     64                          ArrayList<BluetoothMapAccountItem>> parsePackages(boolean includeIcon) {
     65 
     66         LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> groups =
     67                 new LinkedHashMap<BluetoothMapAccountItem,
     68                                   ArrayList<BluetoothMapAccountItem>>();
     69         Intent[] searchIntents = new Intent[2];
     70         //Array <Intent> searchIntents = new Array <Intent>();
     71         searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL);
     72         searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM);
     73         // reset the counter every time this method is called.
     74         mAccountsEnabledCount=0;
     75         // find all installed packages and filter out those that do not support Bluetooth Map.
     76         // this is done by looking for a apps with content providers containing the intent-filter
     77         // in the manifest file.
     78         mPackageManager = mContext.getPackageManager();
     79 
     80         for (Intent searchIntent : searchIntents) {
     81             List<ResolveInfo> resInfos =
     82                 mPackageManager.queryIntentContentProviders(searchIntent, 0);
     83             if (resInfos != null ) {
     84                 if(D) Log.d(TAG,"Found " + resInfos.size() + " application(s) with intent "
     85                         + searchIntent.getAction().toString());
     86                 BluetoothMapUtils.TYPE msgType = (searchIntent.getAction().toString() ==
     87                         BluetoothMapContract.PROVIDER_INTERFACE_EMAIL) ?
     88                         BluetoothMapUtils.TYPE.EMAIL : BluetoothMapUtils.TYPE.IM;
     89                 for (ResolveInfo rInfo : resInfos) {
     90                     if(D) Log.d(TAG,"ResolveInfo " + rInfo.toString());
     91                     // We cannot rely on apps that have been force-stopped in the
     92                     // application settings menu.
     93                     if ((rInfo.providerInfo.applicationInfo.flags &
     94                             ApplicationInfo.FLAG_STOPPED) == 0) {
     95                         BluetoothMapAccountItem app = createAppItem(rInfo, includeIcon, msgType);
     96                         if (app != null){
     97                             ArrayList<BluetoothMapAccountItem> accounts = parseAccounts(app);
     98                             // we do not want to list apps without accounts
     99                             if(accounts.size() > 0)
    100                             {// we need to make sure that the "select all" checkbox
    101                              // is checked if all accounts in the list are checked
    102                                 app.mIsChecked = true;
    103                                 for (BluetoothMapAccountItem acc: accounts)
    104                                 {
    105                                     if(!acc.mIsChecked)
    106                                     {
    107                                         app.mIsChecked = false;
    108                                         break;
    109                                     }
    110                                 }
    111                                 groups.put(app, accounts);
    112                             }
    113                         }
    114                     } else {
    115                         if(D)Log.d(TAG,"Ignoring force-stopped authority "
    116                                 + rInfo.providerInfo.authority +"\n");
    117                     }
    118                 }
    119             }
    120             else {
    121                 if(D) Log.d(TAG,"Found no applications");
    122             }
    123         }
    124         return groups;
    125     }
    126 
    127     public BluetoothMapAccountItem createAppItem(ResolveInfo rInfo, boolean includeIcon,
    128             BluetoothMapUtils.TYPE type) {
    129         String provider = rInfo.providerInfo.authority;
    130         if(provider != null) {
    131             String name = rInfo.loadLabel(mPackageManager).toString();
    132             if(D)Log.d(TAG,rInfo.providerInfo.packageName + " - " + name +
    133                             " - meta-data(provider = " + provider+")\n");
    134             BluetoothMapAccountItem app = BluetoothMapAccountItem.create(
    135                     "0",
    136                     name,
    137                     rInfo.providerInfo.packageName,
    138                     provider,
    139                     (includeIcon == false)? null : rInfo.loadIcon(mPackageManager),
    140                     type);
    141             return app;
    142         }
    143 
    144         return null;
    145     }
    146 
    147     /**
    148      * Method for getting the accounts under a given contentprovider from a package.
    149      * @param app The parent app object
    150      * @return An ArrayList of BluetoothMapAccountItems containing all the accounts from the app
    151      */
    152     public ArrayList<BluetoothMapAccountItem> parseAccounts(BluetoothMapAccountItem app)  {
    153         Cursor c = null;
    154         if(D) Log.d(TAG,"Finding accounts for app "+app.getPackageName());
    155         ArrayList<BluetoothMapAccountItem> children = new ArrayList<BluetoothMapAccountItem>();
    156         // Get the list of accounts from the email apps content resolver (if possible)
    157         mResolver = mContext.getContentResolver();
    158         try{
    159             mProviderClient = mResolver.acquireUnstableContentProviderClient(
    160                     Uri.parse(app.mBase_uri_no_account));
    161             if (mProviderClient == null) {
    162                 throw new RemoteException("Failed to acquire provider for " + app.getPackageName());
    163             }
    164             mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
    165 
    166             Uri uri = Uri.parse(app.mBase_uri_no_account + "/"
    167                                 + BluetoothMapContract.TABLE_ACCOUNT);
    168 
    169             if(app.getType() == TYPE.IM) {
    170                 c = mProviderClient.query(uri, BluetoothMapContract.BT_IM_ACCOUNT_PROJECTION,
    171                         null, null, BluetoothMapContract.AccountColumns._ID+" DESC");
    172             } else {
    173                 c = mProviderClient.query(uri, BluetoothMapContract.BT_ACCOUNT_PROJECTION,
    174                         null, null, BluetoothMapContract.AccountColumns._ID+" DESC");
    175             }
    176         } catch (RemoteException e){
    177             if(D)Log.d(TAG,"Could not establish ContentProviderClient for "+app.getPackageName()+
    178                     " - returning empty account list" );
    179             return children;
    180         } finally {
    181             if (mProviderClient != null)
    182                 mProviderClient.release();
    183         }
    184 
    185         if (c != null) {
    186             c.moveToPosition(-1);
    187             int idIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns._ID);
    188             int dispNameIndex = c.getColumnIndex(
    189                     BluetoothMapContract.AccountColumns.ACCOUNT_DISPLAY_NAME);
    190             int exposeIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
    191             int uciIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_UCI);
    192             int uciPreIndex = c.getColumnIndex(
    193                     BluetoothMapContract.AccountColumns.ACCOUNT_UCI_PREFIX);
    194             while (c.moveToNext()) {
    195                 if(D)Log.d(TAG,"Adding account " + c.getString(dispNameIndex) +
    196                         " with ID " + String.valueOf(c.getInt(idIndex)));
    197                 String uci = null;
    198                 String uciPrefix = null;
    199                 if(app.getType() == TYPE.IM){
    200                     uci = c.getString(uciIndex);
    201                     uciPrefix = c.getString(uciPreIndex);
    202                     if(D)Log.d(TAG,"   Account UCI " + uci);
    203                 }
    204 
    205                 BluetoothMapAccountItem child = BluetoothMapAccountItem.create(
    206                         String.valueOf((c.getInt(idIndex))),
    207                         c.getString(dispNameIndex),
    208                         app.getPackageName(),
    209                         app.getProviderAuthority(),
    210                         null,
    211                         app.getType(),
    212                         uci,
    213                         uciPrefix);
    214 
    215                 child.mIsChecked = (c.getInt(exposeIndex) != 0);
    216                 child.mIsChecked = true; // TODO: Revert when this works
    217                 /* update the account counter
    218                  * so we can make sure that not to many accounts are checked. */
    219                 if(child.mIsChecked)
    220                 {
    221                     mAccountsEnabledCount++;
    222                 }
    223                 children.add(child);
    224             }
    225             c.close();
    226         } else {
    227             if(D)Log.d(TAG, "query failed");
    228         }
    229         return children;
    230     }
    231     /**
    232      * Gets the number of enabled accounts in total across all supported apps.
    233      * NOTE that this method should not be called before the parsePackages method
    234      * has been successfully called.
    235      * @return number of enabled accounts
    236      */
    237     public int getAccountsEnabledCount() {
    238         if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount);
    239         return mAccountsEnabledCount;
    240     }
    241 
    242 }
    243