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