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