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*/ 15package com.android.bluetooth.map; 16 17import java.util.ArrayList; 18import java.util.LinkedHashMap; 19import java.util.List; 20 21import android.content.BroadcastReceiver; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.content.pm.PackageManager; 27import android.content.pm.ResolveInfo; 28import android.database.ContentObserver; 29import android.net.Uri; 30import android.util.Log; 31 32import com.android.bluetooth.mapapi.BluetoothMapContract; 33 34/** 35 * Class to construct content observers for for email applications on the system. 36 * 37 * 38 */ 39 40public 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 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 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 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 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 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 191 private void initObservers(){ 192 if(D)Log.d(TAG,"initObservers()"); 193 for(BluetoothMapAccountItem app: mFullList.keySet()){ 194 registerObserver(app); 195 } 196 } 197 198 private void deinitObservers(){ 199 if(D)Log.d(TAG,"deinitObservers()"); 200 for(BluetoothMapAccountItem app: mFullList.keySet()){ 201 unregisterObserver(app); 202 } 203 } 204 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 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 */ 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 */ 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 */ 353 public void shutdown() { 354 deinitObservers(); 355 removeReceiver(); 356 } 357} 358