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