BluetoothMapAppObserver.java revision 19a1a8259c6352b1afe4f86034708221a87594e5
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.PackageInfo; 27import android.content.pm.PackageManager; 28import android.content.pm.PackageManager.NameNotFoundException; 29import android.content.pm.ResolveInfo; 30import android.database.ContentObserver; 31import android.net.Uri; 32import android.os.Handler; 33import com.android.bluetooth.mapapi.BluetoothMapContract; 34import android.util.Log; 35 36/** 37 * Class to construct content observers for for email applications on the system. 38 * 39 * 40 */ 41 42public 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 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 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 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 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(new Handler()) { 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 mResolver.registerContentObserver(uri, true, observer); 183 } 184 185 public void unregisterObserver(BluetoothMapAccountItem app) { 186 Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority()); 187 if (V) Log.d(TAG, "unregisterObserver("+uri.toString()+")\n"); 188 mResolver.unregisterContentObserver(mObserverMap.get(uri.toString())); 189 mObserverMap.remove(uri.toString()); 190 } 191 192 private void initObservers(){ 193 if(D)Log.d(TAG,"initObservers()"); 194 for(BluetoothMapAccountItem app: mFullList.keySet()){ 195 registerObserver(app); 196 } 197 } 198 199 private void deinitObservers(){ 200 if(D)Log.d(TAG,"deinitObservers()"); 201 for(BluetoothMapAccountItem app: mFullList.keySet()){ 202 unregisterObserver(app); 203 } 204 } 205 206 private void createReceiver(){ 207 if(D)Log.d(TAG,"createReceiver()\n"); 208 IntentFilter intentFilter = new IntentFilter(); 209 intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 210 intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 211 intentFilter.addDataScheme("package"); 212 mReceiver = new BroadcastReceiver() { 213 @Override 214 public void onReceive(Context context, Intent intent) { 215 if(D)Log.d(TAG,"onReceive\n"); 216 String action = intent.getAction(); 217 218 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { 219 Uri data = intent.getData(); 220 String packageName = data.getEncodedSchemeSpecificPart(); 221 if(D)Log.d(TAG,"The installed package is: "+ packageName); 222 223 BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.TYPE.NONE; 224 ResolveInfo resolveInfo = null; 225 Intent[] searchIntents = new Intent[2]; 226 //Array <Intent> searchIntents = new Array <Intent>(); 227 searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL); 228 searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM); 229 // Find all installed packages and filter out those that support Bluetooth Map. 230 231 mPackageManager = mContext.getPackageManager(); 232 233 for (Intent searchIntent : searchIntents) { 234 List<ResolveInfo> resInfos = 235 mPackageManager.queryIntentContentProviders(searchIntent, 0); 236 if (resInfos != null ) { 237 if(D) Log.d(TAG,"Found " + resInfos.size() 238 + " application(s) with intent " 239 + searchIntent.getAction().toString()); 240 for (ResolveInfo rInfo : resInfos) { 241 if(rInfo != null) { 242 // Find out if package contain Bluetooth MAP support 243 if (packageName.equals(rInfo.providerInfo.packageName)) { 244 resolveInfo = rInfo; 245 if(searchIntent.getAction() == 246 BluetoothMapContract.PROVIDER_INTERFACE_EMAIL){ 247 msgType = BluetoothMapUtils.TYPE.EMAIL; 248 } else if (searchIntent.getAction() == 249 BluetoothMapContract.PROVIDER_INTERFACE_IM){ 250 msgType = BluetoothMapUtils.TYPE.IM; 251 } 252 break; 253 } 254 } 255 } 256 } 257 } 258 // if application found with Bluetooth MAP support add to list 259 if(resolveInfo != null) { 260 if(D) Log.d(TAG,"Found " + resolveInfo.providerInfo.packageName 261 + " application of type " + msgType); 262 BluetoothMapAccountItem app = mLoader.createAppItem(resolveInfo, 263 false, msgType); 264 if(app != null) { 265 registerObserver(app); 266 // Add all accounts to mFullList 267 ArrayList<BluetoothMapAccountItem> newAccountList = 268 mLoader.parseAccounts(app); 269 mFullList.put(app, newAccountList); 270 } 271 } 272 273 } 274 else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { 275 Uri data = intent.getData(); 276 String packageName = data.getEncodedSchemeSpecificPart(); 277 if(D)Log.d(TAG,"The removed package is: "+ packageName); 278 BluetoothMapAccountItem app = getApp(packageName); 279 /* Find the object and remove from fullList */ 280 if(app != null) { 281 unregisterObserver(app); 282 mFullList.remove(app); 283 } 284 } 285 } 286 }; 287 if (!mRegisteredReceiver) { 288 try { 289 mContext.registerReceiver(mReceiver,intentFilter); 290 mRegisteredReceiver = true; 291 } catch (Exception e) { 292 Log.e(TAG,"Unable to register MapAppObserver receiver", e); 293 } 294 } 295 } 296 297 private void removeReceiver(){ 298 if(D)Log.d(TAG,"removeReceiver()\n"); 299 if (mRegisteredReceiver) { 300 try { 301 mRegisteredReceiver = false; 302 mContext.unregisterReceiver(mReceiver); 303 } catch (Exception e) { 304 Log.e(TAG,"Unable to unregister mapAppObserver receiver", e); 305 } 306 } 307 } 308 309 /** 310 * Method to get a list of the accounts (across all apps) that are set to be shared 311 * through MAP. 312 * @return Arraylist<BluetoothMapAccountItem> containing all enabled accounts 313 */ 314 public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems(){ 315 if(D)Log.d(TAG,"getEnabledAccountItems()\n"); 316 ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>(); 317 for (BluetoothMapAccountItem app:mFullList.keySet()){ 318 if (app != null) { 319 ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app); 320 if (accountList != null) { 321 for (BluetoothMapAccountItem acc: accountList) { 322 if (acc.mIsChecked) { 323 list.add(acc); 324 } 325 } 326 } else { 327 Log.w(TAG,"getEnabledAccountItems() - No AccountList enabled\n"); 328 } 329 } else { 330 Log.w(TAG,"getEnabledAccountItems() - No Account in App enabled\n"); 331 } 332 } 333 return list; 334 } 335 336 /** 337 * Method to get a list of the accounts (across all apps). 338 * @return Arraylist<BluetoothMapAccountItem> containing all accounts 339 */ 340 public ArrayList<BluetoothMapAccountItem> getAllAccountItems(){ 341 if(D)Log.d(TAG,"getAllAccountItems()\n"); 342 ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>(); 343 for(BluetoothMapAccountItem app:mFullList.keySet()){ 344 ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app); 345 list.addAll(accountList); 346 } 347 return list; 348 } 349 350 351 /** 352 * Cleanup all resources - must be called to avoid leaks. 353 */ 354 public void shutdown() { 355 deinitObservers(); 356 removeReceiver(); 357 } 358} 359