| /* |
| * Copyright (C) 2014 Samsung System LSI |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.bluetooth.map; |
| |
| import java.util.ArrayList; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| |
| import com.android.bluetooth.map.BluetoothMapAccountItem; |
| import com.android.bluetooth.map.BluetoothMapUtils.TYPE; |
| |
| |
| |
| import android.content.ContentProviderClient; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.ProviderInfo; |
| import android.content.pm.ResolveInfo; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.RemoteException; |
| import com.android.bluetooth.mapapi.BluetoothMapContract; |
| import android.text.format.DateUtils; |
| import android.util.Log; |
| |
| |
| public class BluetoothMapAccountLoader { |
| private static final String TAG = "BluetoothMapAccountLoader"; |
| private static final boolean D = BluetoothMapService.DEBUG; |
| private static final boolean V = BluetoothMapService.VERBOSE; |
| private Context mContext = null; |
| private PackageManager mPackageManager = null; |
| private ContentResolver mResolver; |
| private int mAccountsEnabledCount = 0; |
| private ContentProviderClient mProviderClient = null; |
| private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS; |
| |
| public BluetoothMapAccountLoader(Context ctx) |
| { |
| mContext = ctx; |
| } |
| |
| /** |
| * Method to look through all installed packages system-wide and find those that contain one of |
| * the BT-MAP intents in their manifest file. For each app the list of accounts are fetched |
| * using the method parseAccounts(). |
| * @return LinkedHashMap with the packages as keys(BluetoothMapAccountItem) and |
| * values as ArrayLists of BluetoothMapAccountItems. |
| */ |
| public LinkedHashMap<BluetoothMapAccountItem, |
| ArrayList<BluetoothMapAccountItem>> parsePackages(boolean includeIcon) { |
| |
| LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> groups = |
| new LinkedHashMap<BluetoothMapAccountItem, |
| ArrayList<BluetoothMapAccountItem>>(); |
| Intent[] searchIntents = new Intent[2]; |
| //Array <Intent> searchIntents = new Array <Intent>(); |
| searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL); |
| searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM); |
| // reset the counter every time this method is called. |
| mAccountsEnabledCount=0; |
| // find all installed packages and filter out those that do not support Bluetooth Map. |
| // this is done by looking for a apps with content providers containing the intent-filter |
| // in the manifest file. |
| mPackageManager = mContext.getPackageManager(); |
| |
| for (Intent searchIntent : searchIntents) { |
| List<ResolveInfo> resInfos = |
| mPackageManager.queryIntentContentProviders(searchIntent, 0); |
| if (resInfos != null ) { |
| if(D) Log.d(TAG,"Found " + resInfos.size() + " application(s) with intent " |
| + searchIntent.getAction().toString()); |
| BluetoothMapUtils.TYPE msgType = (searchIntent.getAction().toString() == |
| BluetoothMapContract.PROVIDER_INTERFACE_EMAIL) ? |
| BluetoothMapUtils.TYPE.EMAIL : BluetoothMapUtils.TYPE.IM; |
| for (ResolveInfo rInfo : resInfos) { |
| if(D) Log.d(TAG,"ResolveInfo " + rInfo.toString()); |
| // We cannot rely on apps that have been force-stopped in the |
| // application settings menu. |
| if ((rInfo.providerInfo.applicationInfo.flags & |
| ApplicationInfo.FLAG_STOPPED) == 0) { |
| BluetoothMapAccountItem app = createAppItem(rInfo, includeIcon, msgType); |
| if (app != null){ |
| ArrayList<BluetoothMapAccountItem> accounts = parseAccounts(app); |
| // we do not want to list apps without accounts |
| if(accounts.size() > 0) |
| {// we need to make sure that the "select all" checkbox |
| // is checked if all accounts in the list are checked |
| app.mIsChecked = true; |
| for (BluetoothMapAccountItem acc: accounts) |
| { |
| if(!acc.mIsChecked) |
| { |
| app.mIsChecked = false; |
| break; |
| } |
| } |
| groups.put(app, accounts); |
| } |
| } |
| } else { |
| if(D)Log.d(TAG,"Ignoring force-stopped authority " |
| + rInfo.providerInfo.authority +"\n"); |
| } |
| } |
| } |
| else { |
| if(D) Log.d(TAG,"Found no applications"); |
| } |
| } |
| return groups; |
| } |
| |
| public BluetoothMapAccountItem createAppItem(ResolveInfo rInfo, boolean includeIcon, |
| BluetoothMapUtils.TYPE type) { |
| String provider = rInfo.providerInfo.authority; |
| if(provider != null) { |
| String name = rInfo.loadLabel(mPackageManager).toString(); |
| if(D)Log.d(TAG,rInfo.providerInfo.packageName + " - " + name + |
| " - meta-data(provider = " + provider+")\n"); |
| BluetoothMapAccountItem app = BluetoothMapAccountItem.create( |
| "0", |
| name, |
| rInfo.providerInfo.packageName, |
| provider, |
| (includeIcon == false)? null : rInfo.loadIcon(mPackageManager), |
| type); |
| return app; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Method for getting the accounts under a given contentprovider from a package. |
| * @param app The parent app object |
| * @return An ArrayList of BluetoothMapAccountItems containing all the accounts from the app |
| */ |
| public ArrayList<BluetoothMapAccountItem> parseAccounts(BluetoothMapAccountItem app) { |
| Cursor c = null; |
| if(D) Log.d(TAG,"Finding accounts for app "+app.getPackageName()); |
| ArrayList<BluetoothMapAccountItem> children = new ArrayList<BluetoothMapAccountItem>(); |
| // Get the list of accounts from the email apps content resolver (if possible) |
| mResolver = mContext.getContentResolver(); |
| try{ |
| mProviderClient = mResolver.acquireUnstableContentProviderClient( |
| Uri.parse(app.mBase_uri_no_account)); |
| if (mProviderClient == null) { |
| throw new RemoteException("Failed to acquire provider for " + app.getPackageName()); |
| } |
| mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT); |
| |
| Uri uri = Uri.parse(app.mBase_uri_no_account + "/" |
| + BluetoothMapContract.TABLE_ACCOUNT); |
| |
| if(app.getType() == TYPE.IM) { |
| c = mProviderClient.query(uri, BluetoothMapContract.BT_IM_ACCOUNT_PROJECTION, |
| null, null, BluetoothMapContract.AccountColumns._ID+" DESC"); |
| } else { |
| c = mProviderClient.query(uri, BluetoothMapContract.BT_ACCOUNT_PROJECTION, |
| null, null, BluetoothMapContract.AccountColumns._ID+" DESC"); |
| } |
| } catch (RemoteException e){ |
| if(D)Log.d(TAG,"Could not establish ContentProviderClient for "+app.getPackageName()+ |
| " - returning empty account list" ); |
| return children; |
| } finally { |
| if (mProviderClient != null) |
| mProviderClient.release(); |
| } |
| |
| if (c != null) { |
| c.moveToPosition(-1); |
| int idIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns._ID); |
| int dispNameIndex = c.getColumnIndex( |
| BluetoothMapContract.AccountColumns.ACCOUNT_DISPLAY_NAME); |
| int exposeIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE); |
| int uciIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_UCI); |
| int uciPreIndex = c.getColumnIndex( |
| BluetoothMapContract.AccountColumns.ACCOUNT_UCI_PREFIX); |
| while (c.moveToNext()) { |
| if(D)Log.d(TAG,"Adding account " + c.getString(dispNameIndex) + |
| " with ID " + String.valueOf(c.getInt(idIndex))); |
| String uci = null; |
| String uciPrefix = null; |
| if(app.getType() == TYPE.IM){ |
| uci = c.getString(uciIndex); |
| uciPrefix = c.getString(uciPreIndex); |
| if(D)Log.d(TAG," Account UCI " + uci); |
| } |
| |
| BluetoothMapAccountItem child = BluetoothMapAccountItem.create( |
| String.valueOf((c.getInt(idIndex))), |
| c.getString(dispNameIndex), |
| app.getPackageName(), |
| app.getProviderAuthority(), |
| null, |
| app.getType(), |
| uci, |
| uciPrefix); |
| |
| child.mIsChecked = (c.getInt(exposeIndex) != 0); |
| child.mIsChecked = true; // TODO: Revert when this works |
| /* update the account counter |
| * so we can make sure that not to many accounts are checked. */ |
| if(child.mIsChecked) |
| { |
| mAccountsEnabledCount++; |
| } |
| children.add(child); |
| } |
| c.close(); |
| } else { |
| if(D)Log.d(TAG, "query failed"); |
| } |
| return children; |
| } |
| /** |
| * Gets the number of enabled accounts in total across all supported apps. |
| * NOTE that this method should not be called before the parsePackages method |
| * has been successfully called. |
| * @return number of enabled accounts |
| */ |
| public int getAccountsEnabledCount() { |
| if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount); |
| return mAccountsEnabledCount; |
| } |
| |
| } |