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 
BluetoothMapEmailSettingsLoader(Context ctx)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,
parsePackages(boolean includeIcon)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 
createAppItem(ResolveInfo rInfo, boolean includeIcon)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      */
parseAccounts(BluetoothMapEmailSettingsItem app)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             Uri uri = Uri.parse(app.mBase_uri_no_account + "/" + BluetoothMapContract.TABLE_ACCOUNT);
155             c = mProviderClient.query(uri, BluetoothMapContract.BT_ACCOUNT_PROJECTION, null, null,
156                     BluetoothMapContract.AccountColumns._ID+" DESC");
157             while (c != null && c.moveToNext()) {
158                 BluetoothMapEmailSettingsItem child = new BluetoothMapEmailSettingsItem(
159                     String.valueOf((c.getInt(c.getColumnIndex(BluetoothMapContract.AccountColumns._ID)))),
160                     c.getString(c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_DISPLAY_NAME)) ,
161                     app.getPackageName(),
162                     app.getProviderAuthority(),
163                     null);
164 
165                 child.mIsChecked = (c.getInt(c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE))!=0);
166                 /*update the account counter so we can make sure that not to many accounts are checked. */
167                 if(child.mIsChecked)
168                 {
169                     mAccountsEnabledCount++;
170                 }
171                 children.add(child);
172             }
173         } catch (RemoteException e){
174             if(D)Log.d(TAG,"Could not establish ContentProviderClient for "+app.getPackageName()+
175                     " - returning empty account list" );
176         } finally {
177             if (c != null) c.close();
178         }
179         return children;
180     }
181     /**
182      * Gets the number of enabled accounts in total across all supported apps.
183      * NOTE that this method should not be called before the parsePackages method
184      * has been successfully called.
185      * @return number of enabled accounts
186      */
getAccountsEnabledCount()187     public int getAccountsEnabledCount() {
188         if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount);
189         return mAccountsEnabledCount;
190     }
191 }
192