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.Set;
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 BluetoothMapEmailAppObserver{
43 
44     private static final String TAG = "BluetoothMapEmailAppObserver";
45 
46     private static final boolean D = BluetoothMapService.DEBUG;
47     private static final boolean V = BluetoothMapService.VERBOSE;
48     /*  */
49     private LinkedHashMap<BluetoothMapEmailSettingsItem, ArrayList<BluetoothMapEmailSettingsItem>> mFullList;
50     private LinkedHashMap<String,ContentObserver> mObserverMap = new LinkedHashMap<String,ContentObserver>();
51     private ContentResolver mResolver;
52     private Context mContext;
53     private BroadcastReceiver mReceiver;
54     private PackageManager mPackageManager = null;
55     BluetoothMapEmailSettingsLoader mLoader;
56     BluetoothMapService mMapService = null;
57 
BluetoothMapEmailAppObserver(final Context context, BluetoothMapService mapService)58     public BluetoothMapEmailAppObserver(final Context context, BluetoothMapService mapService) {
59         mContext  = context;
60         mMapService = mapService;
61         mResolver = context.getContentResolver();
62         mLoader    = new BluetoothMapEmailSettingsLoader(mContext);
63         mFullList = mLoader.parsePackages(false); /* Get the current list of apps */
64         createReceiver();
65         initObservers();
66     }
67 
68 
getApp(String packageName)69     private BluetoothMapEmailSettingsItem getApp(String packageName) {
70         if(V) Log.d(TAG, "getApp(): Looking for " + packageName);
71         for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){
72             if(V) Log.d(TAG, "  Comparing: " + app.getPackageName());
73             if(app.getPackageName().equals(packageName)) {
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: "+packageNameWithProvider+"\n");
85         String packageName = packageNameWithProvider.replaceFirst("\\.[^\\.]+$", "");
86         BluetoothMapEmailSettingsItem app = getApp(packageName);
87         if(app != null) {
88             ArrayList<BluetoothMapEmailSettingsItem> newAccountList = mLoader.parseAccounts(app);
89             ArrayList<BluetoothMapEmailSettingsItem> oldAccountList = mFullList.get(app);
90             ArrayList<BluetoothMapEmailSettingsItem> addedAccountList =
91                     (ArrayList<BluetoothMapEmailSettingsItem>)newAccountList.clone();
92             ArrayList<BluetoothMapEmailSettingsItem> removedAccountList = mFullList.get(app); // Same as oldAccountList.clone
93 
94             mFullList.put(app, newAccountList);
95             for(BluetoothMapEmailSettingsItem newAcc: newAccountList){
96                 for(BluetoothMapEmailSettingsItem oldAcc: oldAccountList){
97                     if(newAcc.getId() == oldAcc.getId()){
98                         // For each match remove from both removed and added lists
99                         removedAccountList.remove(oldAcc);
100                         addedAccountList.remove(newAcc);
101                         if(!newAcc.getName().equals(oldAcc.getName()) && newAcc.mIsChecked){
102                             // Name Changed and the acc is visible - Change Name in SDP record
103                             mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED);
104                             if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED");
105                         }
106                         if(newAcc.mIsChecked != oldAcc.mIsChecked) {
107                             // Visibility changed
108                             if(newAcc.mIsChecked){
109                                 // account added - create SDP record
110                                 mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
111                                 if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_ADDED isChecked changed");
112                             } else {
113                                 // account removed - remove SDP record
114                                 mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
115                                 if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED isChecked changed");
116                             }
117                         }
118                         break;
119                     }
120                 }
121             }
122             // Notify on any removed accounts
123             for(BluetoothMapEmailSettingsItem removedAcc: removedAccountList){
124                 mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
125                 if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " + removedAcc);
126             }
127             // Notify on any new accounts
128             for(BluetoothMapEmailSettingsItem addedAcc: addedAccountList){
129                 mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
130                 if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " + addedAcc);
131             }
132 
133         } else {
134             Log.e(TAG, "Received change notification on package not registered for notifications!");
135 
136         }
137     }
138 
139     /**
140      * Adds a new content observer to the list of content observers.
141      * The key for the observer is the uri as string
142      * @param uri uri for the package that supports MAP email
143      */
144 
registerObserver(BluetoothMapEmailSettingsItem app)145     public void registerObserver(BluetoothMapEmailSettingsItem app) {
146         Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
147         if (V) Log.d(TAG, "registerObserver for URI "+uri.toString()+"\n");
148         ContentObserver observer = new ContentObserver(new Handler()) {
149             @Override
150             public void onChange(boolean selfChange) {
151                 onChange(selfChange, null);
152             }
153 
154             @Override
155             public void onChange(boolean selfChange, Uri uri) {
156                 if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
157                         + " Uri: " + uri + " selfchange: " + selfChange);
158                 if(uri != null) {
159                     handleAccountChanges(uri.getHost());
160                 } else {
161                     Log.e(TAG, "Unable to handle change as the URI is NULL!");
162                 }
163 
164             }
165         };
166         mObserverMap.put(uri.toString(), observer);
167         mResolver.registerContentObserver(uri, true, observer);
168     }
169 
unregisterObserver(BluetoothMapEmailSettingsItem app)170     public void unregisterObserver(BluetoothMapEmailSettingsItem app) {
171         Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
172         if (V) Log.d(TAG, "unregisterObserver("+uri.toString()+")\n");
173         mResolver.unregisterContentObserver(mObserverMap.get(uri.toString()));
174         mObserverMap.remove(uri.toString());
175     }
176 
initObservers()177     private void initObservers(){
178         if(D)Log.d(TAG,"initObservers()");
179         for(BluetoothMapEmailSettingsItem app: mFullList.keySet()){
180             registerObserver(app);
181         }
182     }
183 
deinitObservers()184     private void deinitObservers(){
185         if(D)Log.d(TAG,"deinitObservers()");
186         for(BluetoothMapEmailSettingsItem app: mFullList.keySet()){
187             unregisterObserver(app);
188         }
189     }
190 
createReceiver()191     private void createReceiver(){
192         if(D)Log.d(TAG,"createReceiver()\n");
193         IntentFilter intentFilter = new IntentFilter();
194         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
195         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
196         intentFilter.addDataScheme("package");
197         mReceiver= new BroadcastReceiver() {
198             @Override
199             public void onReceive(Context context, Intent intent) {
200                 if(D)Log.d(TAG,"onReceive\n");
201                 String action = intent.getAction();
202                 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
203                     Uri data = intent.getData();
204                     String packageName = data.getEncodedSchemeSpecificPart();
205                     if(D)Log.d(TAG,"The installed package is: "+ packageName);
206                   //  PackageInfo pInfo = getPackageInfo(packageName);
207                     ResolveInfo rInfo = mPackageManager.resolveActivity(intent, 0);
208                     BluetoothMapEmailSettingsItem app = mLoader.createAppItem(rInfo, false);
209                     if(app != null) {
210                         registerObserver(app);
211                         // Add all accounts to mFullList
212                         ArrayList<BluetoothMapEmailSettingsItem> newAccountList = mLoader.parseAccounts(app);
213                         mFullList.put(app, newAccountList);
214                     }
215                 }
216                 else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
217 
218                     Uri data = intent.getData();
219                     String packageName = data.getEncodedSchemeSpecificPart();
220                     if(D)Log.d(TAG,"The removed package is: "+ packageName);
221                     BluetoothMapEmailSettingsItem app = getApp(packageName);
222                     /* Find the object and remove from fullList */
223                     if(app != null) {
224                         unregisterObserver(app);
225                         mFullList.remove(app);
226                     }
227                 }
228             }
229         };
230         mContext.registerReceiver(mReceiver,new IntentFilter(Intent.ACTION_PACKAGE_ADDED));
231     }
removeReceiver()232     private void removeReceiver(){
233         if(D)Log.d(TAG,"removeReceiver()\n");
234         mContext.unregisterReceiver(mReceiver);
235     }
getPackageInfo(String packageName)236     private PackageInfo getPackageInfo(String packageName){
237         mPackageManager = mContext.getPackageManager();
238         try {
239             return mPackageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA|PackageManager.GET_SERVICES);
240         } catch (NameNotFoundException e) {
241             Log.e(TAG,"Error getting package metadata", e);
242         }
243         return null;
244     }
245 
246     /**
247      * Method to get a list of the accounts (across all apps) that are set to be shared
248      * through MAP.
249      * @return Arraylist<BluetoothMapEmailSettingsItem> containing all enabled accounts
250      */
getEnabledAccountItems()251     public ArrayList<BluetoothMapEmailSettingsItem> getEnabledAccountItems(){
252         if(D)Log.d(TAG,"getEnabledAccountItems()\n");
253         ArrayList<BluetoothMapEmailSettingsItem> list = new ArrayList<BluetoothMapEmailSettingsItem>();
254         for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){
255             ArrayList<BluetoothMapEmailSettingsItem> accountList = mFullList.get(app);
256             for(BluetoothMapEmailSettingsItem acc: accountList){
257                 if(acc.mIsChecked) {
258                     list.add(acc);
259                 }
260             }
261         }
262         return list;
263     }
264 
265     /**
266      * Method to get a list of the accounts (across all apps).
267      * @return Arraylist<BluetoothMapEmailSettingsItem> containing all accounts
268      */
getAllAccountItems()269     public ArrayList<BluetoothMapEmailSettingsItem> getAllAccountItems(){
270         if(D)Log.d(TAG,"getAllAccountItems()\n");
271         ArrayList<BluetoothMapEmailSettingsItem> list = new ArrayList<BluetoothMapEmailSettingsItem>();
272         for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){
273             ArrayList<BluetoothMapEmailSettingsItem> accountList = mFullList.get(app);
274             list.addAll(accountList);
275         }
276         return list;
277     }
278 
279 
280     /**
281      * Cleanup all resources - must be called to avoid leaks.
282      */
shutdown()283     public void shutdown() {
284         deinitObservers();
285         removeReceiver();
286     }
287 }
288