• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
245         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
246         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
247         intentFilter.addDataScheme("package");
248         mReceiver = new BroadcastReceiver() {
249             @Override
250             public void onReceive(Context context, Intent intent) {
251                 if (D) {
252                     Log.d(TAG, "onReceive\n");
253                 }
254                 String action = intent.getAction();
255 
256                 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
257                     Uri data = intent.getData();
258                     String packageName = data.getEncodedSchemeSpecificPart();
259                     if (D) {
260                         Log.d(TAG, "The installed package is: " + packageName);
261                     }
262 
263                     BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.TYPE.NONE;
264                     ResolveInfo resolveInfo = null;
265                     Intent[] searchIntents = new Intent[2];
266                     //Array <Intent> searchIntents = new Array <Intent>();
267                     searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL);
268                     searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM);
269                     // Find all installed packages and filter out those that support Bluetooth Map.
270 
271                     mPackageManager = mContext.getPackageManager();
272 
273                     for (Intent searchIntent : searchIntents) {
274                         List<ResolveInfo> resInfos =
275                                 mPackageManager.queryIntentContentProviders(searchIntent, 0);
276                         if (resInfos != null) {
277                             if (D) {
278                                 Log.d(TAG,
279                                         "Found " + resInfos.size() + " application(s) with intent "
280                                                 + searchIntent.getAction());
281                             }
282                             for (ResolveInfo rInfo : resInfos) {
283                                 if (rInfo != null) {
284                                     // Find out if package contain Bluetooth MAP support
285                                     if (packageName.equals(rInfo.providerInfo.packageName)) {
286                                         resolveInfo = rInfo;
287                                         if (Objects.equals(searchIntent.getAction(),
288                                                 BluetoothMapContract.PROVIDER_INTERFACE_EMAIL)) {
289                                             msgType = BluetoothMapUtils.TYPE.EMAIL;
290                                         } else if (Objects.equals(searchIntent.getAction(),
291                                                 BluetoothMapContract.PROVIDER_INTERFACE_IM)) {
292                                             msgType = BluetoothMapUtils.TYPE.IM;
293                                         }
294                                         break;
295                                     }
296                                 }
297                             }
298                         }
299                     }
300                     // if application found with Bluetooth MAP support add to list
301                     if (resolveInfo != null) {
302                         if (D) {
303                             Log.d(TAG, "Found " + resolveInfo.providerInfo.packageName
304                                     + " application of type " + msgType);
305                         }
306                         BluetoothMapAccountItem app =
307                                 mLoader.createAppItem(resolveInfo, false, msgType);
308                         if (app != null) {
309                             registerObserver(app);
310                             // Add all accounts to mFullList
311                             ArrayList<BluetoothMapAccountItem> newAccountList =
312                                     mLoader.parseAccounts(app);
313                             mFullList.put(app, newAccountList);
314                         }
315                     }
316 
317                 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
318                     Uri data = intent.getData();
319                     String packageName = data.getEncodedSchemeSpecificPart();
320                     if (D) {
321                         Log.d(TAG, "The removed package is: " + packageName);
322                     }
323                     BluetoothMapAccountItem app = getApp(packageName);
324                     /* Find the object and remove from fullList */
325                     if (app != null) {
326                         unregisterObserver(app);
327                         mFullList.remove(app);
328                     }
329                 }
330             }
331         };
332         if (!mRegisteredReceiver) {
333             try {
334                 mContext.registerReceiver(mReceiver, intentFilter);
335                 mRegisteredReceiver = true;
336             } catch (Exception e) {
337                 Log.e(TAG, "Unable to register MapAppObserver receiver", e);
338             }
339         }
340     }
341 
removeReceiver()342     private void removeReceiver() {
343         if (D) {
344             Log.d(TAG, "removeReceiver()\n");
345         }
346         if (mRegisteredReceiver) {
347             try {
348                 mRegisteredReceiver = false;
349                 mContext.unregisterReceiver(mReceiver);
350             } catch (Exception e) {
351                 Log.e(TAG, "Unable to unregister mapAppObserver receiver", e);
352             }
353         }
354     }
355 
356     /**
357      * Method to get a list of the accounts (across all apps) that are set to be shared
358      * through MAP.
359      * @return Arraylist<BluetoothMapAccountItem> containing all enabled accounts
360      */
getEnabledAccountItems()361     public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems() {
362         if (D) {
363             Log.d(TAG, "getEnabledAccountItems()\n");
364         }
365         ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
366         for (BluetoothMapAccountItem app : mFullList.keySet()) {
367             if (app != null) {
368                 ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
369                 if (accountList != null) {
370                     for (BluetoothMapAccountItem acc : accountList) {
371                         if (acc.mIsChecked) {
372                             list.add(acc);
373                         }
374                     }
375                 } else {
376                     Log.w(TAG, "getEnabledAccountItems() - No AccountList enabled\n");
377                 }
378             } else {
379                 Log.w(TAG, "getEnabledAccountItems() - No Account in App enabled\n");
380             }
381         }
382         return list;
383     }
384 
385     /**
386      * Method to get a list of the accounts (across all apps).
387      * @return Arraylist<BluetoothMapAccountItem> containing all accounts
388      */
getAllAccountItems()389     public ArrayList<BluetoothMapAccountItem> getAllAccountItems() {
390         if (D) {
391             Log.d(TAG, "getAllAccountItems()\n");
392         }
393         ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
394         for (BluetoothMapAccountItem app : mFullList.keySet()) {
395             ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
396             list.addAll(accountList);
397         }
398         return list;
399     }
400 
401 
402     /**
403      * Cleanup all resources - must be called to avoid leaks.
404      */
shutdown()405     public void shutdown() {
406         deinitObservers();
407         removeReceiver();
408     }
409 }
410