BluetoothMapAppObserver.java revision 19a1a8259c6352b1afe4f86034708221a87594e5
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*/
15package com.android.bluetooth.map;
16
17import java.util.ArrayList;
18import java.util.LinkedHashMap;
19import java.util.List;
20
21import android.content.BroadcastReceiver;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.pm.PackageInfo;
27import android.content.pm.PackageManager;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.pm.ResolveInfo;
30import android.database.ContentObserver;
31import android.net.Uri;
32import android.os.Handler;
33import com.android.bluetooth.mapapi.BluetoothMapContract;
34import android.util.Log;
35
36/**
37 * Class to construct content observers for for email applications on the system.
38 *
39 *
40 */
41
42public class BluetoothMapAppObserver{
43
44    private static final String TAG = "BluetoothMapAppObserver";
45
46    private static final boolean D = BluetoothMapService.DEBUG;
47    private static final boolean V = BluetoothMapService.VERBOSE;
48    /*  */
49    private LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mFullList;
50    private LinkedHashMap<String,ContentObserver> mObserverMap =
51            new LinkedHashMap<String,ContentObserver>();
52    private ContentResolver mResolver;
53    private Context mContext;
54    private BroadcastReceiver mReceiver;
55    private PackageManager mPackageManager = null;
56    BluetoothMapAccountLoader mLoader;
57    BluetoothMapService mMapService = null;
58    private boolean mRegisteredReceiver = false;
59
60    public BluetoothMapAppObserver(final Context context, BluetoothMapService mapService) {
61        mContext    = context;
62        mMapService = mapService;
63        mResolver   = context.getContentResolver();
64        mLoader     = new BluetoothMapAccountLoader(mContext);
65        mFullList   = mLoader.parsePackages(false); /* Get the current list of apps */
66        createReceiver();
67        initObservers();
68    }
69
70
71    private BluetoothMapAccountItem getApp(String authoritiesName) {
72        if(V) Log.d(TAG, "getApp(): Looking for " + authoritiesName);
73        for(BluetoothMapAccountItem app:mFullList.keySet()){
74            if(V) Log.d(TAG, "  Comparing: " + app.getProviderAuthority());
75            if(app.getProviderAuthority().equals(authoritiesName)) {
76                if(V) Log.d(TAG, "  found " + app.mBase_uri_no_account);
77                return app;
78            }
79        }
80        if(V) Log.d(TAG, "  NOT FOUND!");
81        return null;
82    }
83
84    private void handleAccountChanges(String packageNameWithProvider) {
85
86        if(D)Log.d(TAG,"handleAccountChanges (packageNameWithProvider: "
87                        +packageNameWithProvider+"\n");
88        //String packageName = packageNameWithProvider.replaceFirst("\\.[^\\.]+$", "");
89        BluetoothMapAccountItem app = getApp(packageNameWithProvider);
90        if(app != null) {
91            ArrayList<BluetoothMapAccountItem> newAccountList = mLoader.parseAccounts(app);
92            ArrayList<BluetoothMapAccountItem> oldAccountList = mFullList.get(app);
93            ArrayList<BluetoothMapAccountItem> addedAccountList =
94                    (ArrayList<BluetoothMapAccountItem>)newAccountList.clone();
95            // Same as oldAccountList.clone
96            ArrayList<BluetoothMapAccountItem> removedAccountList = mFullList.get(app);
97            if (oldAccountList == null)
98                oldAccountList = new ArrayList <BluetoothMapAccountItem>();
99            if (removedAccountList == null)
100                removedAccountList = new ArrayList <BluetoothMapAccountItem>();
101
102            mFullList.put(app, newAccountList);
103            for(BluetoothMapAccountItem newAcc: newAccountList){
104                for(BluetoothMapAccountItem oldAcc: oldAccountList){
105                    if(newAcc.getId() == oldAcc.getId()){
106                        // For each match remove from both removed and added lists
107                        removedAccountList.remove(oldAcc);
108                        addedAccountList.remove(newAcc);
109                        if(!newAcc.getName().equals(oldAcc.getName()) && newAcc.mIsChecked){
110                            // Name Changed and the acc is visible - Change Name in SDP record
111                            mMapService.updateMasInstances(
112                                    BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED);
113                            if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED");
114                        }
115                        if(newAcc.mIsChecked != oldAcc.mIsChecked) {
116                            // Visibility changed
117                            if(newAcc.mIsChecked){
118                                // account added - create SDP record
119                                mMapService.updateMasInstances(
120                                        BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
121                                if(V)Log.d(TAG, "UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " +
122                                        "isChecked changed");
123                            } else {
124                                // account removed - remove SDP record
125                                mMapService.updateMasInstances(
126                                        BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
127                                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " +
128                                        "isChecked changed");
129                            }
130                        }
131                        break;
132                    }
133                }
134            }
135            // Notify on any removed accounts
136            for(BluetoothMapAccountItem removedAcc: removedAccountList){
137                mMapService.updateMasInstances(
138                        BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
139                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " + removedAcc);
140            }
141            // Notify on any new accounts
142            for(BluetoothMapAccountItem addedAcc: addedAccountList){
143                mMapService.updateMasInstances(
144                        BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
145                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " + addedAcc);
146            }
147
148        } else {
149            Log.e(TAG, "Received change notification on package not registered for notifications!");
150
151        }
152    }
153
154    /**
155     * Adds a new content observer to the list of content observers.
156     * The key for the observer is the uri as string
157     * @param uri uri for the package that supports MAP email
158     */
159
160    public void registerObserver(BluetoothMapAccountItem app) {
161        Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
162        if (V) Log.d(TAG, "registerObserver for URI "+uri.toString()+"\n");
163        ContentObserver observer = new ContentObserver(new Handler()) {
164            @Override
165            public void onChange(boolean selfChange) {
166                onChange(selfChange, null);
167            }
168
169            @Override
170            public void onChange(boolean selfChange, Uri uri) {
171                if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
172                        + " Uri: " + uri + " selfchange: " + selfChange);
173                if(uri != null) {
174                    handleAccountChanges(uri.getHost());
175                } else {
176                    Log.e(TAG, "Unable to handle change as the URI is NULL!");
177                }
178
179            }
180        };
181        mObserverMap.put(uri.toString(), observer);
182        mResolver.registerContentObserver(uri, true, observer);
183    }
184
185    public void unregisterObserver(BluetoothMapAccountItem app) {
186        Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
187        if (V) Log.d(TAG, "unregisterObserver("+uri.toString()+")\n");
188        mResolver.unregisterContentObserver(mObserverMap.get(uri.toString()));
189        mObserverMap.remove(uri.toString());
190    }
191
192    private void initObservers(){
193        if(D)Log.d(TAG,"initObservers()");
194        for(BluetoothMapAccountItem app: mFullList.keySet()){
195            registerObserver(app);
196        }
197    }
198
199    private void deinitObservers(){
200        if(D)Log.d(TAG,"deinitObservers()");
201        for(BluetoothMapAccountItem app: mFullList.keySet()){
202            unregisterObserver(app);
203        }
204    }
205
206    private void createReceiver(){
207        if(D)Log.d(TAG,"createReceiver()\n");
208        IntentFilter intentFilter = new IntentFilter();
209        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
210        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
211        intentFilter.addDataScheme("package");
212        mReceiver = new BroadcastReceiver() {
213            @Override
214            public void onReceive(Context context, Intent intent) {
215                if(D)Log.d(TAG,"onReceive\n");
216                String action = intent.getAction();
217
218                if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
219                    Uri data = intent.getData();
220                    String packageName = data.getEncodedSchemeSpecificPart();
221                    if(D)Log.d(TAG,"The installed package is: "+ packageName);
222
223                    BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.TYPE.NONE;
224                    ResolveInfo resolveInfo = null;
225                    Intent[] searchIntents = new Intent[2];
226                    //Array <Intent> searchIntents = new Array <Intent>();
227                    searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL);
228                    searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM);
229                    // Find all installed packages and filter out those that support Bluetooth Map.
230
231                    mPackageManager = mContext.getPackageManager();
232
233                    for (Intent searchIntent : searchIntents) {
234                        List<ResolveInfo> resInfos =
235                                mPackageManager.queryIntentContentProviders(searchIntent, 0);
236                        if (resInfos != null ) {
237                            if(D) Log.d(TAG,"Found " + resInfos.size()
238                                    + " application(s) with intent "
239                                    + searchIntent.getAction().toString());
240                            for (ResolveInfo rInfo : resInfos) {
241                                if(rInfo != null) {
242                                    // Find out if package contain Bluetooth MAP support
243                                    if (packageName.equals(rInfo.providerInfo.packageName)) {
244                                        resolveInfo = rInfo;
245                                        if(searchIntent.getAction() ==
246                                                BluetoothMapContract.PROVIDER_INTERFACE_EMAIL){
247                                            msgType = BluetoothMapUtils.TYPE.EMAIL;
248                                        } else if (searchIntent.getAction() ==
249                                                BluetoothMapContract.PROVIDER_INTERFACE_IM){
250                                            msgType = BluetoothMapUtils.TYPE.IM;
251                                        }
252                                        break;
253                                    }
254                                }
255                            }
256                        }
257                    }
258                    // if application found with Bluetooth MAP support add to list
259                    if(resolveInfo != null) {
260                        if(D) Log.d(TAG,"Found " + resolveInfo.providerInfo.packageName
261                                + " application of type " + msgType);
262                        BluetoothMapAccountItem app = mLoader.createAppItem(resolveInfo,
263                                false, msgType);
264                        if(app != null) {
265                            registerObserver(app);
266                            // Add all accounts to mFullList
267                            ArrayList<BluetoothMapAccountItem> newAccountList =
268                                    mLoader.parseAccounts(app);
269                            mFullList.put(app, newAccountList);
270                        }
271                    }
272
273                }
274                else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
275                    Uri data = intent.getData();
276                    String packageName = data.getEncodedSchemeSpecificPart();
277                    if(D)Log.d(TAG,"The removed package is: "+ packageName);
278                    BluetoothMapAccountItem app = getApp(packageName);
279                    /* Find the object and remove from fullList */
280                    if(app != null) {
281                        unregisterObserver(app);
282                        mFullList.remove(app);
283                    }
284                }
285            }
286        };
287        if (!mRegisteredReceiver) {
288            try {
289                mContext.registerReceiver(mReceiver,intentFilter);
290                mRegisteredReceiver = true;
291            } catch (Exception e) {
292                Log.e(TAG,"Unable to register MapAppObserver receiver", e);
293            }
294        }
295    }
296
297    private void removeReceiver(){
298        if(D)Log.d(TAG,"removeReceiver()\n");
299        if (mRegisteredReceiver) {
300            try {
301                mRegisteredReceiver = false;
302                mContext.unregisterReceiver(mReceiver);
303            } catch (Exception e) {
304                Log.e(TAG,"Unable to unregister mapAppObserver receiver", e);
305            }
306        }
307    }
308
309    /**
310     * Method to get a list of the accounts (across all apps) that are set to be shared
311     * through MAP.
312     * @return Arraylist<BluetoothMapAccountItem> containing all enabled accounts
313     */
314    public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems(){
315        if(D)Log.d(TAG,"getEnabledAccountItems()\n");
316        ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
317        for (BluetoothMapAccountItem app:mFullList.keySet()){
318            if (app != null) {
319                ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
320                if (accountList != null) {
321                    for (BluetoothMapAccountItem acc: accountList) {
322                        if (acc.mIsChecked) {
323                            list.add(acc);
324                        }
325                    }
326                } else {
327                    Log.w(TAG,"getEnabledAccountItems() - No AccountList enabled\n");
328                }
329            } else {
330                Log.w(TAG,"getEnabledAccountItems() - No Account in App enabled\n");
331            }
332        }
333        return list;
334    }
335
336    /**
337     * Method to get a list of the accounts (across all apps).
338     * @return Arraylist<BluetoothMapAccountItem> containing all accounts
339     */
340    public ArrayList<BluetoothMapAccountItem> getAllAccountItems(){
341        if(D)Log.d(TAG,"getAllAccountItems()\n");
342        ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
343        for(BluetoothMapAccountItem app:mFullList.keySet()){
344            ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
345            list.addAll(accountList);
346        }
347        return list;
348    }
349
350
351    /**
352     * Cleanup all resources - must be called to avoid leaks.
353     */
354    public void shutdown() {
355        deinitObservers();
356        removeReceiver();
357    }
358}
359