• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
BluetoothMapAccountLoader(Context ctx)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,
parsePackages(boolean includeIcon)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 
createAppItem(ResolveInfo rInfo, boolean includeIcon, BluetoothMapUtils.TYPE type)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      */
parseAccounts(BluetoothMapAccountItem app)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      */
getAccountsEnabledCount()237     public int getAccountsEnabledCount() {
238         if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount);
239         return mAccountsEnabledCount;
240     }
241 
242 }
243