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