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.PackageInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.pm.ResolveInfo;
30 import android.database.ContentObserver;
31 import android.net.Uri;
32 import android.os.Handler;
33 import com.android.bluetooth.mapapi.BluetoothMapContract;
34 import android.util.Log;
35 
36 /**
37  * Class to construct content observers for for email applications on the system.
38  *
39  *
40  */
41 
42 public 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 
BluetoothMapAppObserver(final Context context, BluetoothMapService mapService)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 
getApp(String authoritiesName)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 
handleAccountChanges(String packageNameWithProvider)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 
registerObserver(BluetoothMapAccountItem app)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(null) {
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         //False "notifyForDescendents" : Get notified whenever a change occurs to the exact URI.
183         mResolver.registerContentObserver(uri, false, observer);
184     }
185 
unregisterObserver(BluetoothMapAccountItem app)186     public void unregisterObserver(BluetoothMapAccountItem app) {
187         Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
188         if (V) Log.d(TAG, "unregisterObserver("+uri.toString()+")\n");
189         mResolver.unregisterContentObserver(mObserverMap.get(uri.toString()));
190         mObserverMap.remove(uri.toString());
191     }
192 
initObservers()193     private void initObservers(){
194         if(D)Log.d(TAG,"initObservers()");
195         for(BluetoothMapAccountItem app: mFullList.keySet()){
196             registerObserver(app);
197         }
198     }
199 
deinitObservers()200     private void deinitObservers(){
201         if(D)Log.d(TAG,"deinitObservers()");
202         for(BluetoothMapAccountItem app: mFullList.keySet()){
203             unregisterObserver(app);
204         }
205     }
206 
createReceiver()207     private void createReceiver(){
208         if(D)Log.d(TAG,"createReceiver()\n");
209         IntentFilter intentFilter = new IntentFilter();
210         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
211         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
212         intentFilter.addDataScheme("package");
213         mReceiver = new BroadcastReceiver() {
214             @Override
215             public void onReceive(Context context, Intent intent) {
216                 if(D)Log.d(TAG,"onReceive\n");
217                 String action = intent.getAction();
218 
219                 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
220                     Uri data = intent.getData();
221                     String packageName = data.getEncodedSchemeSpecificPart();
222                     if(D)Log.d(TAG,"The installed package is: "+ packageName);
223 
224                     BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.TYPE.NONE;
225                     ResolveInfo resolveInfo = null;
226                     Intent[] searchIntents = new Intent[2];
227                     //Array <Intent> searchIntents = new Array <Intent>();
228                     searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL);
229                     searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM);
230                     // Find all installed packages and filter out those that support Bluetooth Map.
231 
232                     mPackageManager = mContext.getPackageManager();
233 
234                     for (Intent searchIntent : searchIntents) {
235                         List<ResolveInfo> resInfos =
236                                 mPackageManager.queryIntentContentProviders(searchIntent, 0);
237                         if (resInfos != null ) {
238                             if(D) Log.d(TAG,"Found " + resInfos.size()
239                                     + " application(s) with intent "
240                                     + searchIntent.getAction().toString());
241                             for (ResolveInfo rInfo : resInfos) {
242                                 if(rInfo != null) {
243                                     // Find out if package contain Bluetooth MAP support
244                                     if (packageName.equals(rInfo.providerInfo.packageName)) {
245                                         resolveInfo = rInfo;
246                                         if(searchIntent.getAction() ==
247                                                 BluetoothMapContract.PROVIDER_INTERFACE_EMAIL){
248                                             msgType = BluetoothMapUtils.TYPE.EMAIL;
249                                         } else if (searchIntent.getAction() ==
250                                                 BluetoothMapContract.PROVIDER_INTERFACE_IM){
251                                             msgType = BluetoothMapUtils.TYPE.IM;
252                                         }
253                                         break;
254                                     }
255                                 }
256                             }
257                         }
258                     }
259                     // if application found with Bluetooth MAP support add to list
260                     if(resolveInfo != null) {
261                         if(D) Log.d(TAG,"Found " + resolveInfo.providerInfo.packageName
262                                 + " application of type " + msgType);
263                         BluetoothMapAccountItem app = mLoader.createAppItem(resolveInfo,
264                                 false, msgType);
265                         if(app != null) {
266                             registerObserver(app);
267                             // Add all accounts to mFullList
268                             ArrayList<BluetoothMapAccountItem> newAccountList =
269                                     mLoader.parseAccounts(app);
270                             mFullList.put(app, newAccountList);
271                         }
272                     }
273 
274                 }
275                 else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
276                     Uri data = intent.getData();
277                     String packageName = data.getEncodedSchemeSpecificPart();
278                     if(D)Log.d(TAG,"The removed package is: "+ packageName);
279                     BluetoothMapAccountItem app = getApp(packageName);
280                     /* Find the object and remove from fullList */
281                     if(app != null) {
282                         unregisterObserver(app);
283                         mFullList.remove(app);
284                     }
285                 }
286             }
287         };
288         if (!mRegisteredReceiver) {
289             try {
290                 mContext.registerReceiver(mReceiver,intentFilter);
291                 mRegisteredReceiver = true;
292             } catch (Exception e) {
293                 Log.e(TAG,"Unable to register MapAppObserver receiver", e);
294             }
295         }
296     }
297 
removeReceiver()298     private void removeReceiver(){
299         if(D)Log.d(TAG,"removeReceiver()\n");
300         if (mRegisteredReceiver) {
301             try {
302                 mRegisteredReceiver = false;
303                 mContext.unregisterReceiver(mReceiver);
304             } catch (Exception e) {
305                 Log.e(TAG,"Unable to unregister mapAppObserver receiver", e);
306             }
307         }
308     }
309 
310     /**
311      * Method to get a list of the accounts (across all apps) that are set to be shared
312      * through MAP.
313      * @return Arraylist<BluetoothMapAccountItem> containing all enabled accounts
314      */
getEnabledAccountItems()315     public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems(){
316         if(D)Log.d(TAG,"getEnabledAccountItems()\n");
317         ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
318         for (BluetoothMapAccountItem app:mFullList.keySet()){
319             if (app != null) {
320                 ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
321                 if (accountList != null) {
322                     for (BluetoothMapAccountItem acc: accountList) {
323                         if (acc.mIsChecked) {
324                             list.add(acc);
325                         }
326                     }
327                 } else {
328                     Log.w(TAG,"getEnabledAccountItems() - No AccountList enabled\n");
329                 }
330             } else {
331                 Log.w(TAG,"getEnabledAccountItems() - No Account in App enabled\n");
332             }
333         }
334         return list;
335     }
336 
337     /**
338      * Method to get a list of the accounts (across all apps).
339      * @return Arraylist<BluetoothMapAccountItem> containing all accounts
340      */
getAllAccountItems()341     public ArrayList<BluetoothMapAccountItem> getAllAccountItems(){
342         if(D)Log.d(TAG,"getAllAccountItems()\n");
343         ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
344         for(BluetoothMapAccountItem app:mFullList.keySet()){
345             ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
346             list.addAll(accountList);
347         }
348         return list;
349     }
350 
351 
352     /**
353      * Cleanup all resources - must be called to avoid leaks.
354      */
shutdown()355     public void shutdown() {
356         deinitObservers();
357         removeReceiver();
358     }
359 }
360