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.addAction(Intent.ACTION_PACKAGE_ADDED); 245 intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 246 intentFilter.addDataScheme("package"); 247 mReceiver = new BroadcastReceiver() { 248 @Override 249 public void onReceive(Context context, Intent intent) { 250 if (D) { 251 Log.d(TAG, "onReceive\n"); 252 } 253 String action = intent.getAction(); 254 255 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { 256 Uri data = intent.getData(); 257 String packageName = data.getEncodedSchemeSpecificPart(); 258 if (D) { 259 Log.d(TAG, "The installed package is: " + packageName); 260 } 261 262 BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.TYPE.NONE; 263 ResolveInfo resolveInfo = null; 264 Intent[] searchIntents = new Intent[2]; 265 //Array <Intent> searchIntents = new Array <Intent>(); 266 searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL); 267 searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM); 268 // Find all installed packages and filter out those that support Bluetooth Map. 269 270 mPackageManager = mContext.getPackageManager(); 271 272 for (Intent searchIntent : searchIntents) { 273 List<ResolveInfo> resInfos = 274 mPackageManager.queryIntentContentProviders(searchIntent, 0); 275 if (resInfos != null) { 276 if (D) { 277 Log.d(TAG, 278 "Found " + resInfos.size() + " application(s) with intent " 279 + searchIntent.getAction()); 280 } 281 for (ResolveInfo rInfo : resInfos) { 282 if (rInfo != null) { 283 // Find out if package contain Bluetooth MAP support 284 if (packageName.equals(rInfo.providerInfo.packageName)) { 285 resolveInfo = rInfo; 286 if (Objects.equals(searchIntent.getAction(), 287 BluetoothMapContract.PROVIDER_INTERFACE_EMAIL)) { 288 msgType = BluetoothMapUtils.TYPE.EMAIL; 289 } else if (Objects.equals(searchIntent.getAction(), 290 BluetoothMapContract.PROVIDER_INTERFACE_IM)) { 291 msgType = BluetoothMapUtils.TYPE.IM; 292 } 293 break; 294 } 295 } 296 } 297 } 298 } 299 // if application found with Bluetooth MAP support add to list 300 if (resolveInfo != null) { 301 if (D) { 302 Log.d(TAG, "Found " + resolveInfo.providerInfo.packageName 303 + " application of type " + msgType); 304 } 305 BluetoothMapAccountItem app = 306 mLoader.createAppItem(resolveInfo, false, msgType); 307 if (app != null) { 308 registerObserver(app); 309 // Add all accounts to mFullList 310 ArrayList<BluetoothMapAccountItem> newAccountList = 311 mLoader.parseAccounts(app); 312 mFullList.put(app, newAccountList); 313 } 314 } 315 316 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { 317 Uri data = intent.getData(); 318 String packageName = data.getEncodedSchemeSpecificPart(); 319 if (D) { 320 Log.d(TAG, "The removed package is: " + packageName); 321 } 322 BluetoothMapAccountItem app = getApp(packageName); 323 /* Find the object and remove from fullList */ 324 if (app != null) { 325 unregisterObserver(app); 326 mFullList.remove(app); 327 } 328 } 329 } 330 }; 331 if (!mRegisteredReceiver) { 332 try { 333 mContext.registerReceiver(mReceiver, intentFilter); 334 mRegisteredReceiver = true; 335 } catch (Exception e) { 336 Log.e(TAG, "Unable to register MapAppObserver receiver", e); 337 } 338 } 339 } 340 removeReceiver()341 private void removeReceiver() { 342 if (D) { 343 Log.d(TAG, "removeReceiver()\n"); 344 } 345 if (mRegisteredReceiver) { 346 try { 347 mRegisteredReceiver = false; 348 mContext.unregisterReceiver(mReceiver); 349 } catch (Exception e) { 350 Log.e(TAG, "Unable to unregister mapAppObserver receiver", e); 351 } 352 } 353 } 354 355 /** 356 * Method to get a list of the accounts (across all apps) that are set to be shared 357 * through MAP. 358 * @return Arraylist<BluetoothMapAccountItem> containing all enabled accounts 359 */ getEnabledAccountItems()360 public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems() { 361 if (D) { 362 Log.d(TAG, "getEnabledAccountItems()\n"); 363 } 364 ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>(); 365 for (BluetoothMapAccountItem app : mFullList.keySet()) { 366 if (app != null) { 367 ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app); 368 if (accountList != null) { 369 for (BluetoothMapAccountItem acc : accountList) { 370 if (acc.mIsChecked) { 371 list.add(acc); 372 } 373 } 374 } else { 375 Log.w(TAG, "getEnabledAccountItems() - No AccountList enabled\n"); 376 } 377 } else { 378 Log.w(TAG, "getEnabledAccountItems() - No Account in App enabled\n"); 379 } 380 } 381 return list; 382 } 383 384 /** 385 * Method to get a list of the accounts (across all apps). 386 * @return Arraylist<BluetoothMapAccountItem> containing all accounts 387 */ getAllAccountItems()388 public ArrayList<BluetoothMapAccountItem> getAllAccountItems() { 389 if (D) { 390 Log.d(TAG, "getAllAccountItems()\n"); 391 } 392 ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>(); 393 for (BluetoothMapAccountItem app : mFullList.keySet()) { 394 ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app); 395 list.addAll(accountList); 396 } 397 return list; 398 } 399 400 401 /** 402 * Cleanup all resources - must be called to avoid leaks. 403 */ shutdown()404 public void shutdown() { 405 deinitObservers(); 406 removeReceiver(); 407 } 408 } 409