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 BluetoothMapAccountLoader(Context ctx)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, parsePackages(boolean includeIcon)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 createAppItem(ResolveInfo rInfo, boolean includeIcon, BluetoothMapUtils.TYPE type)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 */ parseAccounts(BluetoothMapAccountItem app)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 if (mProviderClient != null) 187 mProviderClient.release(); 188 } 189 190 if (c != null) { 191 c.moveToPosition(-1); 192 int idIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns._ID); 193 int dispNameIndex = c.getColumnIndex( 194 BluetoothMapContract.AccountColumns.ACCOUNT_DISPLAY_NAME); 195 int exposeIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE); 196 int uciIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_UCI); 197 int uciPreIndex = c.getColumnIndex( 198 BluetoothMapContract.AccountColumns.ACCOUNT_UCI_PREFIX); 199 while (c.moveToNext()) { 200 if(D)Log.d(TAG,"Adding account " + c.getString(dispNameIndex) + 201 " with ID " + String.valueOf(c.getInt(idIndex))); 202 String uci = null; 203 String uciPrefix = null; 204 if(app.getType() == TYPE.IM){ 205 uci = c.getString(uciIndex); 206 uciPrefix = c.getString(uciPreIndex); 207 if(D)Log.d(TAG," Account UCI " + uci); 208 } 209 210 BluetoothMapAccountItem child = BluetoothMapAccountItem.create( 211 String.valueOf((c.getInt(idIndex))), 212 c.getString(dispNameIndex), 213 app.getPackageName(), 214 app.getProviderAuthority(), 215 null, 216 app.getType(), 217 uci, 218 uciPrefix); 219 220 child.mIsChecked = (c.getInt(exposeIndex) != 0); 221 child.mIsChecked = true; // TODO: Revert when this works 222 /* update the account counter 223 * so we can make sure that not to many accounts are checked. */ 224 if(child.mIsChecked) 225 { 226 mAccountsEnabledCount++; 227 } 228 children.add(child); 229 } 230 c.close(); 231 } else { 232 if(D)Log.d(TAG, "query failed"); 233 } 234 return children; 235 } 236 /** 237 * Gets the number of enabled accounts in total across all supported apps. 238 * NOTE that this method should not be called before the parsePackages method 239 * has been successfully called. 240 * @return number of enabled accounts 241 */ getAccountsEnabledCount()242 public int getAccountsEnabledCount() { 243 if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount); 244 return mAccountsEnabledCount; 245 } 246 247 } 248