1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ims.rcs.uce.eab; 18 19 import static android.telephony.ims.RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS; 20 import static android.telephony.ims.RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE; 21 22 import android.app.AlarmManager; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.database.ContentObserver; 28 import android.database.Cursor; 29 import android.net.Uri; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.PersistableBundle; 33 import android.os.RemoteException; 34 import android.provider.ContactsContract; 35 import android.provider.Telephony; 36 import android.telephony.CarrierConfigManager; 37 import android.telephony.ims.ImsManager; 38 import android.telephony.ims.ImsRcsManager; 39 import android.telephony.ims.RcsContactUceCapability; 40 import android.telephony.ims.SipDetails; 41 import android.telephony.ims.aidl.IRcsUceControllerCallback; 42 import android.util.Log; 43 44 import com.android.ims.rcs.uce.UceController; 45 46 import java.util.ArrayList; 47 import java.util.List; 48 49 public final class EabBulkCapabilityUpdater { 50 private final String TAG = this.getClass().getSimpleName(); 51 52 private static final Uri USER_EAB_SETTING = Uri.withAppendedPath(Telephony.SimInfo.CONTENT_URI, 53 Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED); 54 private static final int NUM_SECS_IN_DAY = 86400; 55 56 private final int mSubId; 57 private final Context mContext; 58 private final Handler mHandler; 59 60 private final AlarmManager.OnAlarmListener mCapabilityExpiredListener; 61 private final ContactChangedListener mContactProviderListener; 62 private final EabSettingsListener mEabSettingListener; 63 private final EabControllerImpl mEabControllerImpl; 64 private final EabContactSyncController mEabContactSyncController; 65 66 private UceController.UceControllerCallback mUceControllerCallback; 67 private List<Uri> mRefreshContactList; 68 69 private boolean mIsContactProviderListenerRegistered = false; 70 private boolean mIsEabSettingListenerRegistered = false; 71 private boolean mIsCarrierConfigListenerRegistered = false; 72 private boolean mIsCarrierConfigEnabled = false; 73 74 /** 75 * Listen capability expired intent. Only registered when 76 * {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} has enabled bulk 77 * capability exchange. 78 */ 79 private class CapabilityExpiredListener implements AlarmManager.OnAlarmListener { 80 @Override onAlarm()81 public void onAlarm() { 82 Log.d(TAG, "Capability expired."); 83 try { 84 List<Uri> expiredContactList = getExpiredContactList(); 85 if (expiredContactList.size() > 0) { 86 mUceControllerCallback.refreshCapabilities( 87 getExpiredContactList(), 88 mRcsUceControllerCallback); 89 } else { 90 Log.d(TAG, "expiredContactList is empty."); 91 } 92 } catch (RemoteException e) { 93 Log.e(TAG, "CapabilityExpiredListener RemoteException", e); 94 } 95 } 96 } 97 98 /** 99 * Listen contact provider change. Only registered when 100 * {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} has enabled bulk 101 * capability exchange. 102 */ 103 private class ContactChangedListener extends ContentObserver { ContactChangedListener(Handler handler)104 public ContactChangedListener(Handler handler) { 105 super(handler); 106 } 107 108 @Override onChange(boolean selfChange)109 public void onChange(boolean selfChange) { 110 Log.d(TAG, "Contact changed"); 111 syncContactAndRefreshCapabilities(); 112 } 113 } 114 115 /** 116 * Listen EAB settings change. Only registered when 117 * {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} has enabled bulk 118 * capability exchange. 119 */ 120 private class EabSettingsListener extends ContentObserver { EabSettingsListener(Handler handler)121 public EabSettingsListener(Handler handler) { 122 super(handler); 123 } 124 125 @Override onChange(boolean selfChange)126 public void onChange(boolean selfChange) { 127 boolean isUserEnableUce = isUserEnableUce(); 128 Log.d(TAG, "EAB user setting changed: " + isUserEnableUce); 129 if (isUserEnableUce) { 130 mHandler.post(new SyncContactRunnable()); 131 } else { 132 unRegisterContactProviderListener(); 133 cancelTimeAlert(mContext); 134 } 135 } 136 } 137 138 private IRcsUceControllerCallback mRcsUceControllerCallback = new IRcsUceControllerCallback() { 139 @Override 140 public void onCapabilitiesReceived(List<RcsContactUceCapability> contactCapabilities) { 141 Log.d(TAG, "onCapabilitiesReceived"); 142 mEabControllerImpl.saveCapabilities(contactCapabilities); 143 } 144 145 @Override 146 public void onComplete(SipDetails details) { 147 Log.d(TAG, "onComplete"); 148 } 149 150 @Override 151 public void onError(int errorCode, long retryAfterMilliseconds, SipDetails details) { 152 Log.d(TAG, "Refresh capabilities failed. Error code: " + errorCode 153 + ", retryAfterMilliseconds: " + retryAfterMilliseconds); 154 if (retryAfterMilliseconds != 0) { 155 mHandler.postDelayed(new retryRunnable(), retryAfterMilliseconds); 156 } 157 } 158 159 @Override 160 public IBinder asBinder() { 161 return null; 162 } 163 }; 164 165 private class SyncContactRunnable implements Runnable { 166 @Override run()167 public void run() { 168 Log.d(TAG, "Sync contact from contact provider"); 169 syncContactAndRefreshCapabilities(); 170 registerContactProviderListener(); 171 registerEabUserSettingsListener(); 172 } 173 } 174 175 /** 176 * Re-refresh capability if error happened. 177 */ 178 private class retryRunnable implements Runnable { 179 @Override run()180 public void run() { 181 Log.d(TAG, "Retry refreshCapabilities()"); 182 183 try { 184 mUceControllerCallback.refreshCapabilities( 185 mRefreshContactList, mRcsUceControllerCallback); 186 } catch (RemoteException e) { 187 Log.e(TAG, "refreshCapabilities RemoteException" , e); 188 } 189 } 190 } 191 EabBulkCapabilityUpdater(Context context, int subId, EabControllerImpl eabControllerImpl, EabContactSyncController eabContactSyncController, UceController.UceControllerCallback uceControllerCallback, Handler handler)192 public EabBulkCapabilityUpdater(Context context, 193 int subId, 194 EabControllerImpl eabControllerImpl, 195 EabContactSyncController eabContactSyncController, 196 UceController.UceControllerCallback uceControllerCallback, 197 Handler handler) { 198 mContext = context; 199 mSubId = subId; 200 mEabControllerImpl = eabControllerImpl; 201 mEabContactSyncController = eabContactSyncController; 202 mUceControllerCallback = uceControllerCallback; 203 204 mHandler = handler; 205 mContactProviderListener = new ContactChangedListener(mHandler); 206 mEabSettingListener = new EabSettingsListener(mHandler); 207 mCapabilityExpiredListener = new CapabilityExpiredListener(); 208 209 Log.d(TAG, "create EabBulkCapabilityUpdater() subId: " + mSubId); 210 211 enableBulkCapability(); 212 updateExpiredTimeAlert(); 213 } 214 enableBulkCapability()215 private void enableBulkCapability() { 216 boolean isUserEnableUce = isUserEnableUce(); 217 boolean isSupportBulkCapabilityExchange = getBooleanCarrierConfig( 218 CarrierConfigManager.Ims.KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, mSubId); 219 220 Log.d(TAG, "isUserEnableUce: " + isUserEnableUce 221 + ", isSupportBulkCapabilityExchange: " + isSupportBulkCapabilityExchange); 222 223 if (isUserEnableUce && isSupportBulkCapabilityExchange) { 224 mHandler.post(new SyncContactRunnable()); 225 mIsCarrierConfigEnabled = true; 226 } else if (!isUserEnableUce && isSupportBulkCapabilityExchange) { 227 registerEabUserSettingsListener(); 228 mIsCarrierConfigEnabled = false; 229 } else { 230 Log.d(TAG, "Not support bulk capability exchange."); 231 } 232 } 233 syncContactAndRefreshCapabilities()234 private void syncContactAndRefreshCapabilities() { 235 mRefreshContactList = mEabContactSyncController.syncContactToEabProvider(mContext); 236 Log.d(TAG, "refresh contacts number: " + mRefreshContactList.size()); 237 238 if (mUceControllerCallback == null) { 239 Log.d(TAG, "mUceControllerCallback is null."); 240 return; 241 } 242 243 try { 244 if (mRefreshContactList.size() > 0) { 245 mUceControllerCallback.refreshCapabilities( 246 mRefreshContactList, mRcsUceControllerCallback); 247 } 248 } catch (RemoteException e) { 249 Log.e(TAG, "mUceControllerCallback RemoteException.", e); 250 } 251 } 252 updateExpiredTimeAlert()253 protected void updateExpiredTimeAlert() { 254 boolean isUserEnableUce = isUserEnableUce(); 255 boolean isSupportBulkCapabilityExchange = getBooleanCarrierConfig( 256 CarrierConfigManager.Ims.KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, mSubId); 257 258 Log.d(TAG, " updateExpiredTimeAlert(), isUserEnableUce: " + isUserEnableUce 259 + ", isSupportBulkCapabilityExchange: " + isSupportBulkCapabilityExchange); 260 261 if (isUserEnableUce && isSupportBulkCapabilityExchange) { 262 long expiredTimestamp = getLeastExpiredTimestamp(); 263 if (expiredTimestamp == Long.MAX_VALUE) { 264 Log.d(TAG, "Can't find min timestamp in eab provider"); 265 return; 266 } 267 expiredTimestamp += mEabControllerImpl.getCapabilityCacheExpiration(mSubId); 268 Log.d(TAG, "set time alert at " + expiredTimestamp); 269 cancelTimeAlert(mContext); 270 setTimeAlert(mContext, expiredTimestamp); 271 } 272 } 273 getLeastExpiredTimestamp()274 private long getLeastExpiredTimestamp() { 275 String selection = "(" 276 // Query presence timestamp 277 + EabProvider.EabCommonColumns.MECHANISM + "=" + CAPABILITY_MECHANISM_PRESENCE 278 + " AND " 279 + EabProvider.PresenceTupleColumns.REQUEST_TIMESTAMP + " IS NOT NULL) " 280 281 // Query options timestamp 282 + " OR " + "(" + EabProvider.EabCommonColumns.MECHANISM + "=" 283 + CAPABILITY_MECHANISM_OPTIONS + " AND " 284 + EabProvider.OptionsColumns.REQUEST_TIMESTAMP + " IS NOT NULL) " 285 286 // filter by sub id 287 + " AND " + EabProvider.EabCommonColumns.SUBSCRIPTION_ID + "=" + mSubId 288 289 // filter the contact that not come from contact provider 290 + " AND " + EabProvider.ContactColumns.RAW_CONTACT_ID + " IS NOT NULL " 291 + " AND " + EabProvider.ContactColumns.DATA_ID + " IS NOT NULL "; 292 293 long minTimestamp = Long.MAX_VALUE; 294 Cursor result = mContext.getContentResolver().query(EabProvider.ALL_DATA_URI, null, 295 selection, 296 null, null); 297 298 if (result != null) { 299 while (result.moveToNext()) { 300 int mechanism = result.getInt( 301 result.getColumnIndex(EabProvider.EabCommonColumns.MECHANISM)); 302 long timestamp; 303 if (mechanism == CAPABILITY_MECHANISM_PRESENCE) { 304 timestamp = result.getLong(result.getColumnIndex( 305 EabProvider.PresenceTupleColumns.REQUEST_TIMESTAMP)); 306 } else { 307 timestamp = result.getLong(result.getColumnIndex( 308 EabProvider.OptionsColumns.REQUEST_TIMESTAMP)); 309 } 310 311 if (timestamp < minTimestamp) { 312 minTimestamp = timestamp; 313 } 314 } 315 result.close(); 316 } else { 317 Log.d(TAG, "getLeastExpiredTimestamp() cursor is null"); 318 } 319 return minTimestamp; 320 } 321 setTimeAlert(Context context, long wakeupTimeMs)322 private void setTimeAlert(Context context, long wakeupTimeMs) { 323 AlarmManager am = context.getSystemService(AlarmManager.class); 324 325 // To prevent all devices from sending requests to the server at the same time, add a jitter 326 // time (0 sec ~ 2 days) randomly. 327 int jitterTimeSec = (int) (Math.random() * (NUM_SECS_IN_DAY * 2)); 328 Log.d(TAG, " setTimeAlert: " + wakeupTimeMs + ", jitterTimeSec: " + jitterTimeSec); 329 am.set(AlarmManager.RTC_WAKEUP, 330 (wakeupTimeMs * 1000) + jitterTimeSec, 331 TAG, 332 mCapabilityExpiredListener, 333 mHandler); 334 } 335 cancelTimeAlert(Context context)336 private void cancelTimeAlert(Context context) { 337 Log.d(TAG, "cancelTimeAlert."); 338 AlarmManager am = context.getSystemService(AlarmManager.class); 339 am.cancel(mCapabilityExpiredListener); 340 } 341 getBooleanCarrierConfig(String key, int subId)342 private boolean getBooleanCarrierConfig(String key, int subId) { 343 CarrierConfigManager mConfigManager = mContext.getSystemService(CarrierConfigManager.class); 344 PersistableBundle b = null; 345 if (mConfigManager != null) { 346 b = mConfigManager.getConfigForSubId(subId); 347 } 348 if (b != null) { 349 return b.getBoolean(key); 350 } else { 351 Log.w(TAG, "getConfigForSubId(subId) is null. Return the default value of " + key); 352 return CarrierConfigManager.getDefaultConfig().getBoolean(key); 353 } 354 } 355 isUserEnableUce()356 private boolean isUserEnableUce() { 357 ImsManager manager = mContext.getSystemService(ImsManager.class); 358 if (manager == null) { 359 Log.e(TAG, "ImsManager is null"); 360 return false; 361 } 362 try { 363 ImsRcsManager rcsManager = manager.getImsRcsManager(mSubId); 364 return (rcsManager != null) && rcsManager.getUceAdapter().isUceSettingEnabled(); 365 } catch (Exception e) { 366 Log.e(TAG, "hasUserEnabledUce: exception = " + e.getMessage()); 367 } 368 return false; 369 } 370 getExpiredContactList()371 private List<Uri> getExpiredContactList() { 372 List<Uri> refreshList = new ArrayList<>(); 373 long expiredTime = (System.currentTimeMillis() / 1000) 374 + mEabControllerImpl.getCapabilityCacheExpiration(mSubId); 375 String selection = "(" 376 + EabProvider.EabCommonColumns.MECHANISM + "=" + CAPABILITY_MECHANISM_PRESENCE 377 + " AND " + EabProvider.PresenceTupleColumns.REQUEST_TIMESTAMP + "<" 378 + expiredTime + ")"; 379 selection += " OR " + "(" + EabProvider.EabCommonColumns.MECHANISM + "=" 380 + CAPABILITY_MECHANISM_OPTIONS + " AND " 381 + EabProvider.OptionsColumns.REQUEST_TIMESTAMP + "<" + expiredTime + ")"; 382 383 Cursor result = mContext.getContentResolver().query(EabProvider.ALL_DATA_URI, null, 384 selection, 385 null, null); 386 while (result.moveToNext()) { 387 String phoneNumber = result.getString( 388 result.getColumnIndex(EabProvider.ContactColumns.PHONE_NUMBER)); 389 refreshList.add(Uri.parse(phoneNumber)); 390 } 391 result.close(); 392 return refreshList; 393 } 394 onDestroy()395 protected void onDestroy() { 396 Log.d(TAG, "onDestroy"); 397 cancelTimeAlert(mContext); 398 unRegisterContactProviderListener(); 399 unRegisterEabUserSettings(); 400 mIsCarrierConfigEnabled = false; 401 } 402 registerContactProviderListener()403 private void registerContactProviderListener() { 404 Log.d(TAG, "registerContactProviderListener"); 405 mIsContactProviderListenerRegistered = true; 406 mContext.getContentResolver().registerContentObserver( 407 ContactsContract.Contacts.CONTENT_URI, 408 true, 409 mContactProviderListener); 410 } 411 registerEabUserSettingsListener()412 private void registerEabUserSettingsListener() { 413 Log.d(TAG, "registerEabUserSettingsListener"); 414 mIsEabSettingListenerRegistered = true; 415 mContext.getContentResolver().registerContentObserver( 416 USER_EAB_SETTING, 417 true, 418 mEabSettingListener); 419 } 420 unRegisterContactProviderListener()421 private void unRegisterContactProviderListener() { 422 Log.d(TAG, "unRegisterContactProviderListener"); 423 if (mIsContactProviderListenerRegistered) { 424 mIsContactProviderListenerRegistered = false; 425 mContext.getContentResolver().unregisterContentObserver(mContactProviderListener); 426 } 427 } 428 unRegisterEabUserSettings()429 private void unRegisterEabUserSettings() { 430 Log.d(TAG, "unRegisterEabUserSettings"); 431 if (mIsEabSettingListenerRegistered) { 432 mIsEabSettingListenerRegistered = false; 433 mContext.getContentResolver().unregisterContentObserver(mEabSettingListener); 434 } 435 } 436 setUceRequestCallback(UceController.UceControllerCallback uceControllerCallback)437 public void setUceRequestCallback(UceController.UceControllerCallback uceControllerCallback) { 438 mUceControllerCallback = uceControllerCallback; 439 } 440 onCarrierConfigChanged()441 public void onCarrierConfigChanged() { 442 boolean isSupportBulkCapabilityExchange = getBooleanCarrierConfig( 443 CarrierConfigManager.Ims.KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, mSubId); 444 445 Log.d(TAG, "Carrier config changed. " 446 + "isCarrierConfigEnabled: " + mIsCarrierConfigEnabled 447 + ", isSupportBulkCapabilityExchange: " + isSupportBulkCapabilityExchange); 448 if (!mIsCarrierConfigEnabled && isSupportBulkCapabilityExchange) { 449 enableBulkCapability(); 450 updateExpiredTimeAlert(); 451 mIsCarrierConfigEnabled = true; 452 } else if (mIsCarrierConfigEnabled && !isSupportBulkCapabilityExchange) { 453 onDestroy(); 454 } 455 } 456 } 457