BluetoothMapAccountLoader.java revision 5a60e47497f21f64e6d79420dc4c56c1907df22a
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
16package com.android.bluetooth.map;
17
18import java.util.ArrayList;
19import java.util.LinkedHashMap;
20import java.util.List;
21
22import com.android.bluetooth.map.BluetoothMapAccountItem;
23import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
24
25
26
27import android.content.ContentProviderClient;
28import android.content.ContentResolver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.pm.ApplicationInfo;
32import android.content.pm.PackageInfo;
33import android.content.pm.PackageManager;
34import android.content.pm.PackageManager.NameNotFoundException;
35import android.content.pm.ProviderInfo;
36import android.content.pm.ResolveInfo;
37import android.database.Cursor;
38import android.net.Uri;
39import android.os.RemoteException;
40import com.android.bluetooth.mapapi.BluetoothMapContract;
41import android.text.format.DateUtils;
42import android.util.Log;
43
44
45public 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
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,
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
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     */
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            mProviderClient.release();
187        }
188
189        if (c != null) {
190            c.moveToPosition(-1);
191            int idIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns._ID);
192            int dispNameIndex = c.getColumnIndex(
193                    BluetoothMapContract.AccountColumns.ACCOUNT_DISPLAY_NAME);
194            int exposeIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
195            int uciIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_UCI);
196            int uciPreIndex = c.getColumnIndex(
197                    BluetoothMapContract.AccountColumns.ACCOUNT_UCI_PREFIX);
198            while (c.moveToNext()) {
199                if(D)Log.d(TAG,"Adding account " + c.getString(dispNameIndex) +
200                        " with ID " + String.valueOf(c.getInt(idIndex)));
201                String uci = null;
202                String uciPrefix = null;
203                if(app.getType() == TYPE.IM){
204                    uci = c.getString(uciIndex);
205                    uciPrefix = c.getString(uciPreIndex);
206                    if(D)Log.d(TAG,"   Account UCI " + uci);
207                }
208
209                BluetoothMapAccountItem child = BluetoothMapAccountItem.create(
210                        String.valueOf((c.getInt(idIndex))),
211                        c.getString(dispNameIndex),
212                        app.getPackageName(),
213                        app.getProviderAuthority(),
214                        null,
215                        app.getType(),
216                        uci,
217                        uciPrefix);
218
219                child.mIsChecked = (c.getInt(exposeIndex) != 0);
220                child.mIsChecked = true; // TODO: Revert when this works
221                /* update the account counter
222                 * so we can make sure that not to many accounts are checked. */
223                if(child.mIsChecked)
224                {
225                    mAccountsEnabledCount++;
226                }
227                children.add(child);
228            }
229            c.close();
230        } else {
231            if(D)Log.d(TAG, "query failed");
232        }
233        return children;
234    }
235    /**
236     * Gets the number of enabled accounts in total across all supported apps.
237     * NOTE that this method should not be called before the parsePackages method
238     * has been successfully called.
239     * @return number of enabled accounts
240     */
241    public int getAccountsEnabledCount() {
242        if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount);
243        return mAccountsEnabledCount;
244    }
245
246}
247