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