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 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 */ 187 public int getAccountsEnabledCount() { 188 if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount); 189 return mAccountsEnabledCount; 190 } 191 } 192