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.bluetooth.BluetoothProfile; 18 import android.bluetooth.BluetoothProtoEnums; 19 import android.content.BroadcastReceiver; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.database.ContentObserver; 27 import android.net.Uri; 28 import android.util.Log; 29 30 import com.android.bluetooth.BluetoothStatsLog; 31 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils; 32 import com.android.bluetooth.mapapi.BluetoothMapContract; 33 34 import java.util.ArrayList; 35 import java.util.LinkedHashMap; 36 import java.util.List; 37 import java.util.Objects; 38 39 /** Class to construct content observers for email applications on the system. */ 40 // Next tag value for ContentProfileErrorReportUtils.report(): 6 41 public class BluetoothMapAppObserver { 42 43 private static final String TAG = "BluetoothMapAppObserver"; 44 45 /* */ 46 private LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mFullList; 47 private LinkedHashMap<String, ContentObserver> mObserverMap = 48 new LinkedHashMap<String, ContentObserver>(); 49 private ContentResolver mResolver; 50 private Context mContext; 51 private BroadcastReceiver mReceiver; 52 private PackageManager mPackageManager = null; 53 BluetoothMapAccountLoader mLoader; 54 BluetoothMapService mMapService = null; 55 private boolean mRegisteredReceiver = false; 56 BluetoothMapAppObserver(final Context context, BluetoothMapService mapService)57 public BluetoothMapAppObserver(final Context context, BluetoothMapService mapService) { 58 mContext = context; 59 mMapService = mapService; 60 mResolver = context.getContentResolver(); 61 mLoader = new BluetoothMapAccountLoader(mContext); 62 mFullList = mLoader.parsePackages(false); /* Get the current list of apps */ 63 createReceiver(); 64 initObservers(); 65 } 66 getApp(String authoritiesName)67 private BluetoothMapAccountItem getApp(String authoritiesName) { 68 Log.v(TAG, "getApp(): Looking for " + authoritiesName); 69 for (BluetoothMapAccountItem app : mFullList.keySet()) { 70 Log.v(TAG, " Comparing: " + app.getProviderAuthority()); 71 if (app.getProviderAuthority().equals(authoritiesName)) { 72 Log.v(TAG, " found " + app.mBase_uri_no_account); 73 return app; 74 } 75 } 76 Log.v(TAG, " NOT FOUND!"); 77 return null; 78 } 79 handleAccountChanges(String packageNameWithProvider)80 private void handleAccountChanges(String packageNameWithProvider) { 81 82 Log.d( 83 TAG, 84 "handleAccountChanges (packageNameWithProvider: " + packageNameWithProvider + "\n"); 85 // String packageName = packageNameWithProvider.replaceFirst("\\.[^\\.]+$", ""); 86 BluetoothMapAccountItem app = getApp(packageNameWithProvider); 87 if (app != null) { 88 ArrayList<BluetoothMapAccountItem> newAccountList = mLoader.parseAccounts(app); 89 ArrayList<BluetoothMapAccountItem> oldAccountList = mFullList.get(app); 90 ArrayList<BluetoothMapAccountItem> addedAccountList = 91 (ArrayList<BluetoothMapAccountItem>) newAccountList.clone(); 92 // Same as oldAccountList.clone 93 ArrayList<BluetoothMapAccountItem> removedAccountList = mFullList.get(app); 94 if (oldAccountList == null) { 95 oldAccountList = new ArrayList<BluetoothMapAccountItem>(); 96 } 97 if (removedAccountList == null) { 98 removedAccountList = new ArrayList<BluetoothMapAccountItem>(); 99 } 100 101 mFullList.put(app, newAccountList); 102 for (BluetoothMapAccountItem newAcc : newAccountList) { 103 for (BluetoothMapAccountItem oldAcc : oldAccountList) { 104 if (Objects.equals(newAcc.getId(), oldAcc.getId())) { 105 // For each match remove from both removed and added lists 106 removedAccountList.remove(oldAcc); 107 addedAccountList.remove(newAcc); 108 if (!newAcc.getName().equals(oldAcc.getName()) && newAcc.mIsChecked) { 109 // Name Changed and the acc is visible - Change Name in SDP record 110 mMapService.updateMasInstances( 111 BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED); 112 Log.v(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED"); 113 } 114 if (newAcc.mIsChecked != oldAcc.mIsChecked) { 115 // Visibility changed 116 if (newAcc.mIsChecked) { 117 // account added - create SDP record 118 mMapService.updateMasInstances( 119 BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED); 120 Log.v( 121 TAG, 122 "UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " 123 + "isChecked changed"); 124 } else { 125 // account removed - remove SDP record 126 mMapService.updateMasInstances( 127 BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED); 128 Log.v( 129 TAG, 130 " UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " 131 + "isChecked changed"); 132 } 133 } 134 break; 135 } 136 } 137 } 138 // Notify on any removed accounts 139 for (BluetoothMapAccountItem removedAcc : removedAccountList) { 140 mMapService.updateMasInstances( 141 BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED); 142 Log.v(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " + removedAcc); 143 } 144 // Notify on any new accounts 145 for (BluetoothMapAccountItem addedAcc : addedAccountList) { 146 mMapService.updateMasInstances( 147 BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED); 148 Log.v(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " + addedAcc); 149 } 150 151 } else { 152 Log.e(TAG, "Received change notification on package not registered for notifications!"); 153 ContentProfileErrorReportUtils.report( 154 BluetoothProfile.MAP, 155 BluetoothProtoEnums.BLUETOOTH_MAP_APP_OBSERVER, 156 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 157 0); 158 } 159 } 160 161 /** 162 * Adds a new content observer to the list of content observers. The key for the observer is the 163 * uri as string 164 * 165 * @param app app item for the package that supports MAP email 166 */ registerObserver(BluetoothMapAccountItem app)167 public void registerObserver(BluetoothMapAccountItem app) { 168 Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority()); 169 Log.v(TAG, "registerObserver for URI " + uri.toString() + "\n"); 170 ContentObserver observer = 171 new ContentObserver(null) { 172 @Override 173 public void onChange(boolean selfChange) { 174 onChange(selfChange, null); 175 } 176 177 @Override 178 public void onChange(boolean selfChange, Uri uri) { 179 Log.v( 180 TAG, 181 "onChange on thread: " 182 + Thread.currentThread().getId() 183 + " Uri: " 184 + uri 185 + " selfchange: " 186 + selfChange); 187 if (uri != null) { 188 handleAccountChanges(uri.getHost()); 189 } else { 190 Log.e(TAG, "Unable to handle change as the URI is NULL!"); 191 ContentProfileErrorReportUtils.report( 192 BluetoothProfile.MAP, 193 BluetoothProtoEnums.BLUETOOTH_MAP_APP_OBSERVER, 194 BluetoothStatsLog 195 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 196 1); 197 } 198 } 199 }; 200 mObserverMap.put(uri.toString(), observer); 201 // False "notifyForDescendents" : Get notified whenever a change occurs to the exact URI. 202 mResolver.registerContentObserver(uri, false, observer); 203 } 204 unregisterObserver(BluetoothMapAccountItem app)205 public void unregisterObserver(BluetoothMapAccountItem app) { 206 Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority()); 207 Log.v(TAG, "unregisterObserver(" + uri.toString() + ")\n"); 208 mResolver.unregisterContentObserver(mObserverMap.get(uri.toString())); 209 mObserverMap.remove(uri.toString()); 210 } 211 initObservers()212 private void initObservers() { 213 Log.d(TAG, "initObservers()"); 214 for (BluetoothMapAccountItem app : mFullList.keySet()) { 215 registerObserver(app); 216 } 217 } 218 deinitObservers()219 private void deinitObservers() { 220 Log.d(TAG, "deinitObservers()"); 221 for (BluetoothMapAccountItem app : mFullList.keySet()) { 222 unregisterObserver(app); 223 } 224 } 225 createReceiver()226 private void createReceiver() { 227 Log.d(TAG, "createReceiver()\n"); 228 IntentFilter intentFilter = new IntentFilter(); 229 intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 230 intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 231 intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 232 intentFilter.addDataScheme("package"); 233 mReceiver = 234 new BroadcastReceiver() { 235 @Override 236 public void onReceive(Context context, Intent intent) { 237 Log.d(TAG, "onReceive\n"); 238 String action = intent.getAction(); 239 240 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { 241 Uri data = intent.getData(); 242 String packageName = data.getEncodedSchemeSpecificPart(); 243 Log.d(TAG, "The installed package is: " + packageName); 244 245 BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.TYPE.NONE; 246 ResolveInfo resolveInfo = null; 247 Intent[] searchIntents = new Intent[2]; 248 // Array <Intent> searchIntents = new Array <Intent>(); 249 searchIntents[0] = 250 new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL); 251 searchIntents[1] = 252 new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM); 253 // Find all installed packages and filter out those that support 254 // Bluetooth Map. 255 256 mPackageManager = mContext.getPackageManager(); 257 258 for (Intent searchIntent : searchIntents) { 259 List<ResolveInfo> resInfos = 260 mPackageManager.queryIntentContentProviders( 261 searchIntent, 0); 262 if (resInfos != null) { 263 Log.d( 264 TAG, 265 "Found " 266 + resInfos.size() 267 + " application(s) with intent " 268 + searchIntent.getAction()); 269 for (ResolveInfo rInfo : resInfos) { 270 if (rInfo != null) { 271 // Find out if package contain Bluetooth MAP support 272 if (packageName.equals( 273 rInfo.providerInfo.packageName)) { 274 resolveInfo = rInfo; 275 if (Objects.equals( 276 searchIntent.getAction(), 277 BluetoothMapContract 278 .PROVIDER_INTERFACE_EMAIL)) { 279 msgType = BluetoothMapUtils.TYPE.EMAIL; 280 } else if (Objects.equals( 281 searchIntent.getAction(), 282 BluetoothMapContract 283 .PROVIDER_INTERFACE_IM)) { 284 msgType = BluetoothMapUtils.TYPE.IM; 285 } 286 break; 287 } 288 } 289 } 290 } 291 } 292 // if application found with Bluetooth MAP support add to list 293 if (resolveInfo != null) { 294 Log.d( 295 TAG, 296 "Found " 297 + resolveInfo.providerInfo.packageName 298 + " application of type " 299 + msgType); 300 BluetoothMapAccountItem app = 301 mLoader.createAppItem(resolveInfo, false, msgType); 302 if (app != null) { 303 registerObserver(app); 304 // Add all accounts to mFullList 305 ArrayList<BluetoothMapAccountItem> newAccountList = 306 mLoader.parseAccounts(app); 307 mFullList.put(app, newAccountList); 308 } 309 } 310 311 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { 312 Uri data = intent.getData(); 313 String packageName = data.getEncodedSchemeSpecificPart(); 314 Log.d(TAG, "The removed package is: " + packageName); 315 BluetoothMapAccountItem app = getApp(packageName); 316 /* Find the object and remove from fullList */ 317 if (app != null) { 318 unregisterObserver(app); 319 mFullList.remove(app); 320 } 321 } 322 } 323 }; 324 if (!mRegisteredReceiver) { 325 try { 326 mContext.registerReceiver(mReceiver, intentFilter); 327 mRegisteredReceiver = true; 328 } catch (Exception e) { 329 ContentProfileErrorReportUtils.report( 330 BluetoothProfile.MAP, 331 BluetoothProtoEnums.BLUETOOTH_MAP_APP_OBSERVER, 332 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 333 2); 334 Log.e(TAG, "Unable to register MapAppObserver receiver", e); 335 } 336 } 337 } 338 removeReceiver()339 private void removeReceiver() { 340 Log.d(TAG, "removeReceiver()\n"); 341 if (mRegisteredReceiver) { 342 try { 343 mRegisteredReceiver = false; 344 mContext.unregisterReceiver(mReceiver); 345 } catch (Exception e) { 346 ContentProfileErrorReportUtils.report( 347 BluetoothProfile.MAP, 348 BluetoothProtoEnums.BLUETOOTH_MAP_APP_OBSERVER, 349 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 350 3); 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 through MAP. 358 * 359 * @return Arraylist<BluetoothMapAccountItem> containing all enabled accounts 360 */ getEnabledAccountItems()361 public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems() { 362 Log.d(TAG, "getEnabledAccountItems()\n"); 363 ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>(); 364 for (BluetoothMapAccountItem app : mFullList.keySet()) { 365 if (app != null) { 366 ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app); 367 if (accountList != null) { 368 for (BluetoothMapAccountItem acc : accountList) { 369 if (acc.mIsChecked) { 370 list.add(acc); 371 } 372 } 373 } else { 374 Log.w(TAG, "getEnabledAccountItems() - No AccountList enabled\n"); 375 ContentProfileErrorReportUtils.report( 376 BluetoothProfile.MAP, 377 BluetoothProtoEnums.BLUETOOTH_MAP_APP_OBSERVER, 378 BluetoothStatsLog 379 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_WARN, 380 4); 381 } 382 } else { 383 Log.w(TAG, "getEnabledAccountItems() - No Account in App enabled\n"); 384 ContentProfileErrorReportUtils.report( 385 BluetoothProfile.MAP, 386 BluetoothProtoEnums.BLUETOOTH_MAP_APP_OBSERVER, 387 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_WARN, 388 5); 389 } 390 } 391 return list; 392 } 393 394 /** 395 * Method to get a list of the accounts (across all apps). 396 * 397 * @return Arraylist<BluetoothMapAccountItem> containing all accounts 398 */ getAllAccountItems()399 public ArrayList<BluetoothMapAccountItem> getAllAccountItems() { 400 Log.d(TAG, "getAllAccountItems()\n"); 401 ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>(); 402 for (BluetoothMapAccountItem app : mFullList.keySet()) { 403 ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app); 404 list.addAll(accountList); 405 } 406 return list; 407 } 408 409 /** Cleanup all resources - must be called to avoid leaks. */ shutdown()410 public void shutdown() { 411 deinitObservers(); 412 removeReceiver(); 413 } 414 } 415