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