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