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.Set; 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 BluetoothMapEmailAppObserver{ 43 44 private static final String TAG = "BluetoothMapEmailAppObserver"; 45 46 private static final boolean D = BluetoothMapService.DEBUG; 47 private static final boolean V = BluetoothMapService.VERBOSE; 48 /* */ 49 private LinkedHashMap<BluetoothMapEmailSettingsItem, ArrayList<BluetoothMapEmailSettingsItem>> mFullList; 50 private LinkedHashMap<String,ContentObserver> mObserverMap = new LinkedHashMap<String,ContentObserver>(); 51 private ContentResolver mResolver; 52 private Context mContext; 53 private BroadcastReceiver mReceiver; 54 private PackageManager mPackageManager = null; 55 BluetoothMapEmailSettingsLoader mLoader; 56 BluetoothMapService mMapService = null; 57 58 public BluetoothMapEmailAppObserver(final Context context, BluetoothMapService mapService) { 59 mContext = context; 60 mMapService = mapService; 61 mResolver = context.getContentResolver(); 62 mLoader = new BluetoothMapEmailSettingsLoader(mContext); 63 mFullList = mLoader.parsePackages(false); /* Get the current list of apps */ 64 createReceiver(); 65 initObservers(); 66 } 67 68 69 private BluetoothMapEmailSettingsItem getApp(String packageName) { 70 if(V) Log.d(TAG, "getApp(): Looking for " + packageName); 71 for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){ 72 if(V) Log.d(TAG, " Comparing: " + app.getPackageName()); 73 if(app.getPackageName().equals(packageName)) { 74 if(V) Log.d(TAG, " found " + app.mBase_uri_no_account); 75 return app; 76 } 77 } 78 if(V) Log.d(TAG, " NOT FOUND!"); 79 return null; 80 } 81 82 private void handleAccountChanges(String packageNameWithProvider) { 83 84 if(D)Log.d(TAG,"handleAccountChanges (packageNameWithProvider: "+packageNameWithProvider+"\n"); 85 String packageName = packageNameWithProvider.replaceFirst("\\.[^\\.]+$", ""); 86 BluetoothMapEmailSettingsItem app = getApp(packageName); 87 if(app != null) { 88 ArrayList<BluetoothMapEmailSettingsItem> newAccountList = mLoader.parseAccounts(app); 89 ArrayList<BluetoothMapEmailSettingsItem> oldAccountList = mFullList.get(app); 90 ArrayList<BluetoothMapEmailSettingsItem> addedAccountList = 91 (ArrayList<BluetoothMapEmailSettingsItem>)newAccountList.clone(); 92 ArrayList<BluetoothMapEmailSettingsItem> removedAccountList = mFullList.get(app); // Same as oldAccountList.clone 93 94 mFullList.put(app, newAccountList); 95 for(BluetoothMapEmailSettingsItem newAcc: newAccountList){ 96 for(BluetoothMapEmailSettingsItem oldAcc: oldAccountList){ 97 if(newAcc.getId() == oldAcc.getId()){ 98 // For each match remove from both removed and added lists 99 removedAccountList.remove(oldAcc); 100 addedAccountList.remove(newAcc); 101 if(!newAcc.getName().equals(oldAcc.getName()) && newAcc.mIsChecked){ 102 // Name Changed and the acc is visible - Change Name in SDP record 103 mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED); 104 if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED"); 105 } 106 if(newAcc.mIsChecked != oldAcc.mIsChecked) { 107 // Visibility changed 108 if(newAcc.mIsChecked){ 109 // account added - create SDP record 110 mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED); 111 if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_ADDED isChecked changed"); 112 } else { 113 // account removed - remove SDP record 114 mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED); 115 if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED isChecked changed"); 116 } 117 } 118 break; 119 } 120 } 121 } 122 // Notify on any removed accounts 123 for(BluetoothMapEmailSettingsItem removedAcc: removedAccountList){ 124 mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED); 125 if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " + removedAcc); 126 } 127 // Notify on any new accounts 128 for(BluetoothMapEmailSettingsItem addedAcc: addedAccountList){ 129 mMapService.updateMasInstances(BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED); 130 if(V)Log.d(TAG, " UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " + addedAcc); 131 } 132 133 } else { 134 Log.e(TAG, "Received change notification on package not registered for notifications!"); 135 136 } 137 } 138 139 /** 140 * Adds a new content observer to the list of content observers. 141 * The key for the observer is the uri as string 142 * @param uri uri for the package that supports MAP email 143 */ 144 145 public void registerObserver(BluetoothMapEmailSettingsItem app) { 146 Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority()); 147 if (V) Log.d(TAG, "registerObserver for URI "+uri.toString()+"\n"); 148 ContentObserver observer = new ContentObserver(new Handler()) { 149 @Override 150 public void onChange(boolean selfChange) { 151 onChange(selfChange, null); 152 } 153 154 @Override 155 public void onChange(boolean selfChange, Uri uri) { 156 if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId() 157 + " Uri: " + uri + " selfchange: " + selfChange); 158 if(uri != null) { 159 handleAccountChanges(uri.getHost()); 160 } else { 161 Log.e(TAG, "Unable to handle change as the URI is NULL!"); 162 } 163 164 } 165 }; 166 mObserverMap.put(uri.toString(), observer); 167 mResolver.registerContentObserver(uri, true, observer); 168 } 169 170 public void unregisterObserver(BluetoothMapEmailSettingsItem app) { 171 Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority()); 172 if (V) Log.d(TAG, "unregisterObserver("+uri.toString()+")\n"); 173 mResolver.unregisterContentObserver(mObserverMap.get(uri.toString())); 174 mObserverMap.remove(uri.toString()); 175 } 176 177 private void initObservers(){ 178 if(D)Log.d(TAG,"initObservers()"); 179 for(BluetoothMapEmailSettingsItem app: mFullList.keySet()){ 180 registerObserver(app); 181 } 182 } 183 184 private void deinitObservers(){ 185 if(D)Log.d(TAG,"deinitObservers()"); 186 for(BluetoothMapEmailSettingsItem app: mFullList.keySet()){ 187 unregisterObserver(app); 188 } 189 } 190 191 private void createReceiver(){ 192 if(D)Log.d(TAG,"createReceiver()\n"); 193 IntentFilter intentFilter = new IntentFilter(); 194 intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 195 intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 196 intentFilter.addDataScheme("package"); 197 mReceiver= new BroadcastReceiver() { 198 @Override 199 public void onReceive(Context context, Intent intent) { 200 if(D)Log.d(TAG,"onReceive\n"); 201 String action = intent.getAction(); 202 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { 203 Uri data = intent.getData(); 204 String packageName = data.getEncodedSchemeSpecificPart(); 205 if(D)Log.d(TAG,"The installed package is: "+ packageName); 206 // PackageInfo pInfo = getPackageInfo(packageName); 207 ResolveInfo rInfo = mPackageManager.resolveActivity(intent, 0); 208 BluetoothMapEmailSettingsItem app = mLoader.createAppItem(rInfo, false); 209 if(app != null) { 210 registerObserver(app); 211 // Add all accounts to mFullList 212 ArrayList<BluetoothMapEmailSettingsItem> newAccountList = mLoader.parseAccounts(app); 213 mFullList.put(app, newAccountList); 214 } 215 } 216 else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { 217 218 Uri data = intent.getData(); 219 String packageName = data.getEncodedSchemeSpecificPart(); 220 if(D)Log.d(TAG,"The removed package is: "+ packageName); 221 BluetoothMapEmailSettingsItem app = getApp(packageName); 222 /* Find the object and remove from fullList */ 223 if(app != null) { 224 unregisterObserver(app); 225 mFullList.remove(app); 226 } 227 } 228 } 229 }; 230 mContext.registerReceiver(mReceiver,new IntentFilter(Intent.ACTION_PACKAGE_ADDED)); 231 } 232 private void removeReceiver(){ 233 if(D)Log.d(TAG,"removeReceiver()\n"); 234 mContext.unregisterReceiver(mReceiver); 235 } 236 private PackageInfo getPackageInfo(String packageName){ 237 mPackageManager = mContext.getPackageManager(); 238 try { 239 return mPackageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA|PackageManager.GET_SERVICES); 240 } catch (NameNotFoundException e) { 241 Log.e(TAG,"Error getting package metadata", e); 242 } 243 return null; 244 } 245 246 /** 247 * Method to get a list of the accounts (across all apps) that are set to be shared 248 * through MAP. 249 * @return Arraylist<BluetoothMapEmailSettingsItem> containing all enabled accounts 250 */ 251 public ArrayList<BluetoothMapEmailSettingsItem> getEnabledAccountItems(){ 252 if(D)Log.d(TAG,"getEnabledAccountItems()\n"); 253 ArrayList<BluetoothMapEmailSettingsItem> list = new ArrayList<BluetoothMapEmailSettingsItem>(); 254 for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){ 255 ArrayList<BluetoothMapEmailSettingsItem> accountList = mFullList.get(app); 256 for(BluetoothMapEmailSettingsItem acc: accountList){ 257 if(acc.mIsChecked) { 258 list.add(acc); 259 } 260 } 261 } 262 return list; 263 } 264 265 /** 266 * Method to get a list of the accounts (across all apps). 267 * @return Arraylist<BluetoothMapEmailSettingsItem> containing all accounts 268 */ 269 public ArrayList<BluetoothMapEmailSettingsItem> getAllAccountItems(){ 270 if(D)Log.d(TAG,"getAllAccountItems()\n"); 271 ArrayList<BluetoothMapEmailSettingsItem> list = new ArrayList<BluetoothMapEmailSettingsItem>(); 272 for(BluetoothMapEmailSettingsItem app:mFullList.keySet()){ 273 ArrayList<BluetoothMapEmailSettingsItem> accountList = mFullList.get(app); 274 list.addAll(accountList); 275 } 276 return list; 277 } 278 279 280 /** 281 * Cleanup all resources - must be called to avoid leaks. 282 */ 283 public void shutdown() { 284 deinitObservers(); 285 removeReceiver(); 286 } 287 } 288