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