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