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.util; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.net.Uri; 22 import android.os.PersistableBundle; 23 import android.preference.PreferenceManager; 24 import android.provider.BlockedNumberContract; 25 import android.telephony.CarrierConfigManager; 26 import android.telephony.SubscriptionManager; 27 import android.telephony.ims.ProvisioningManager; 28 import android.text.TextUtils; 29 import android.util.Log; 30 31 import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult; 32 33 import java.time.Instant; 34 import java.util.Optional; 35 import java.util.concurrent.TimeUnit; 36 37 public class UceUtils { 38 39 public static final int LOG_SIZE = 20; 40 private static final String LOG_PREFIX = "RcsUce."; 41 private static final String LOG_TAG = LOG_PREFIX + "UceUtils"; 42 43 private static final String SHARED_PREF_DEVICE_STATE_KEY = "UceDeviceState"; 44 45 private static final int DEFAULT_RCL_MAX_NUM_ENTRIES = 100; 46 private static final long DEFAULT_RCS_PUBLISH_SOURCE_THROTTLE_MS = 60000L; 47 private static final long DEFAULT_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 48 TimeUnit.DAYS.toSeconds(30); 49 private static final long DEFAULT_REQUEST_RETRY_INTERVAL_MS = TimeUnit.MINUTES.toMillis(20); 50 private static final long DEFAULT_MINIMUM_REQUEST_RETRY_AFTER_MS = TimeUnit.SECONDS.toMillis(3); 51 52 // The default of the capabilities request timeout. 53 private static final long DEFAULT_CAP_REQUEST_TIMEOUT_AFTER_MS = TimeUnit.MINUTES.toMillis(3); 54 private static Optional<Long> OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.empty(); 55 56 // The default value of the availability cache expiration. 57 private static final long DEFAULT_AVAILABILITY_CACHE_EXPIRATION_SEC = 60L; // 60 seconds 58 59 // The task ID of the UCE request 60 private static long TASK_ID = 0L; 61 62 // The request coordinator ID 63 private static long REQUEST_COORDINATOR_ID = 0; 64 65 /** 66 * Get the log prefix of RCS UCE 67 */ getLogPrefix()68 public static String getLogPrefix() { 69 return LOG_PREFIX; 70 } 71 72 /** 73 * Generate the unique UCE request task id. 74 */ generateTaskId()75 public static synchronized long generateTaskId() { 76 return ++TASK_ID; 77 } 78 79 /** 80 * Generate the unique request coordinator id. 81 */ generateRequestCoordinatorId()82 public static synchronized long generateRequestCoordinatorId() { 83 return ++REQUEST_COORDINATOR_ID; 84 } 85 isEabProvisioned(Context context, int subId)86 public static boolean isEabProvisioned(Context context, int subId) { 87 boolean isProvisioned = false; 88 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 89 Log.w(LOG_TAG, "isEabProvisioned: invalid subscriptionId " + subId); 90 return false; 91 } 92 CarrierConfigManager configManager = (CarrierConfigManager) 93 context.getSystemService(Context.CARRIER_CONFIG_SERVICE); 94 if (configManager != null) { 95 PersistableBundle config = configManager.getConfigForSubId(subId); 96 if (config != null && !config.getBoolean( 97 CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONED_BOOL)) { 98 return true; 99 } 100 } 101 try { 102 ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId); 103 isProvisioned = manager.getProvisioningIntValue( 104 ProvisioningManager.KEY_EAB_PROVISIONING_STATUS) 105 == ProvisioningManager.PROVISIONING_VALUE_ENABLED; 106 } catch (Exception e) { 107 Log.w(LOG_TAG, "isEabProvisioned: exception=" + e.getMessage()); 108 } 109 return isProvisioned; 110 } 111 112 /** 113 * Check whether or not this carrier supports the exchange of phone numbers with the carrier's 114 * presence server. 115 */ isPresenceCapExchangeEnabled(Context context, int subId)116 public static boolean isPresenceCapExchangeEnabled(Context context, int subId) { 117 CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); 118 if (configManager == null) { 119 return false; 120 } 121 PersistableBundle config = configManager.getConfigForSubId(subId); 122 if (config == null) { 123 return false; 124 } 125 return config.getBoolean( 126 CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL); 127 } 128 129 /** 130 * Check if Presence is supported by the carrier. 131 */ isPresenceSupported(Context context, int subId)132 public static boolean isPresenceSupported(Context context, int subId) { 133 CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); 134 if (configManager == null) { 135 return false; 136 } 137 PersistableBundle config = configManager.getConfigForSubId(subId); 138 if (config == null) { 139 return false; 140 } 141 return config.getBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL); 142 } 143 144 /** 145 * Check if SIP OPTIONS is supported by the carrier. 146 */ isSipOptionsSupported(Context context, int subId)147 public static boolean isSipOptionsSupported(Context context, int subId) { 148 CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); 149 if (configManager == null) { 150 return false; 151 } 152 PersistableBundle config = configManager.getConfigForSubId(subId); 153 if (config == null) { 154 return false; 155 } 156 return config.getBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL); 157 } 158 159 /** 160 * Check whether the PRESENCE group subscribe is enabled or not. 161 * 162 * @return true when the Presence group subscribe is enabled, false otherwise. 163 */ isPresenceGroupSubscribeEnabled(Context context, int subId)164 public static boolean isPresenceGroupSubscribeEnabled(Context context, int subId) { 165 CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); 166 if (configManager == null) { 167 return false; 168 } 169 PersistableBundle config = configManager.getConfigForSubId(subId); 170 if (config == null) { 171 return false; 172 } 173 return config.getBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL); 174 } 175 176 /** 177 * Returns {@code true} if {@code phoneNumber} is blocked. 178 * 179 * @param context the context of the caller. 180 * @param phoneNumber the number to check. 181 * @return true if the number is blocked, false otherwise. 182 */ isNumberBlocked(Context context, String phoneNumber)183 public static boolean isNumberBlocked(Context context, String phoneNumber) { 184 int blockStatus; 185 try { 186 blockStatus = BlockedNumberContract.SystemContract.shouldSystemBlockNumber( 187 context, phoneNumber, null /*extras*/); 188 } catch (Exception e) { 189 return false; 190 } 191 return blockStatus != BlockedNumberContract.STATUS_NOT_BLOCKED; 192 } 193 194 /** 195 * Get the minimum time that allow two PUBLISH requests can be executed continuously. 196 * 197 * @param subId The subscribe ID 198 * @return The milliseconds that allowed two consecutive publish request. 199 */ getRcsPublishThrottle(int subId)200 public static long getRcsPublishThrottle(int subId) { 201 long throttle = DEFAULT_RCS_PUBLISH_SOURCE_THROTTLE_MS; 202 try { 203 ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId); 204 long provisioningValue = manager.getProvisioningIntValue( 205 ProvisioningManager.KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS); 206 if (provisioningValue > 0) { 207 throttle = provisioningValue; 208 } 209 } catch (Exception e) { 210 Log.w(LOG_TAG, "getRcsPublishThrottle: exception=" + e.getMessage()); 211 } 212 return throttle; 213 } 214 215 /** 216 * Retrieve the maximum number of contacts that is in one Request Contained List(RCL) 217 * 218 * @param subId The subscribe ID 219 * @return The maximum number of contacts. 220 */ getRclMaxNumberEntries(int subId)221 public static int getRclMaxNumberEntries(int subId) { 222 int maxNumEntries = DEFAULT_RCL_MAX_NUM_ENTRIES; 223 try { 224 ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId); 225 int provisioningValue = manager.getProvisioningIntValue( 226 ProvisioningManager.KEY_RCS_MAX_NUM_ENTRIES_IN_RCL); 227 if (provisioningValue > 0) { 228 maxNumEntries = provisioningValue; 229 } 230 } catch (Exception e) { 231 Log.w(LOG_TAG, "getRclMaxNumberEntries: exception=" + e.getMessage()); 232 } 233 return maxNumEntries; 234 } 235 getNonRcsCapabilitiesCacheExpiration(Context context, int subId)236 public static long getNonRcsCapabilitiesCacheExpiration(Context context, int subId) { 237 CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); 238 if (configManager == null) { 239 return DEFAULT_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC; 240 } 241 PersistableBundle config = configManager.getConfigForSubId(subId); 242 if (config == null) { 243 return DEFAULT_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC; 244 } 245 return config.getInt( 246 CarrierConfigManager.Ims.KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT); 247 } 248 isRequestForbiddenBySip489(Context context, int subId)249 public static boolean isRequestForbiddenBySip489(Context context, int subId) { 250 CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); 251 if (configManager == null) { 252 return false; 253 } 254 PersistableBundle config = configManager.getConfigForSubId(subId); 255 if (config == null) { 256 return false; 257 } 258 return config.getBoolean( 259 CarrierConfigManager.Ims.KEY_RCS_REQUEST_FORBIDDEN_BY_SIP_489_BOOL); 260 } 261 getRequestRetryInterval(Context context, int subId)262 public static long getRequestRetryInterval(Context context, int subId) { 263 CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); 264 if (configManager == null) { 265 return DEFAULT_REQUEST_RETRY_INTERVAL_MS; 266 } 267 PersistableBundle config = configManager.getConfigForSubId(subId); 268 if (config == null) { 269 return DEFAULT_REQUEST_RETRY_INTERVAL_MS; 270 } 271 return config.getLong( 272 CarrierConfigManager.Ims.KEY_RCS_REQUEST_RETRY_INTERVAL_MILLIS_LONG); 273 } 274 saveDeviceStateToPreference(Context context, int subId, DeviceStateResult deviceState)275 public static boolean saveDeviceStateToPreference(Context context, int subId, 276 DeviceStateResult deviceState) { 277 SharedPreferences sharedPreferences = 278 PreferenceManager.getDefaultSharedPreferences(context); 279 SharedPreferences.Editor editor = sharedPreferences.edit(); 280 editor.putString(getDeviceStateSharedPrefKey(subId), 281 getDeviceStateSharedPrefValue(deviceState)); 282 return editor.commit(); 283 } 284 restoreDeviceState(Context context, int subId)285 public static Optional<DeviceStateResult> restoreDeviceState(Context context, int subId) { 286 SharedPreferences sharedPreferences = 287 PreferenceManager.getDefaultSharedPreferences(context); 288 final String sharedPrefKey = getDeviceStateSharedPrefKey(subId); 289 String sharedPrefValue = sharedPreferences.getString(sharedPrefKey, ""); 290 if (TextUtils.isEmpty(sharedPrefValue)) { 291 return Optional.empty(); 292 } 293 String[] valueAry = sharedPrefValue.split(","); 294 if (valueAry == null || valueAry.length != 4) { 295 return Optional.empty(); 296 } 297 try { 298 int deviceState = Integer.valueOf(valueAry[0]); 299 Optional<Integer> errorCode = (Integer.valueOf(valueAry[1]) == -1L) ? 300 Optional.empty() : Optional.of(Integer.valueOf(valueAry[1])); 301 302 long retryTimeMillis = Long.valueOf(valueAry[2]); 303 Optional<Instant> retryTime = (retryTimeMillis == -1L) ? 304 Optional.empty() : Optional.of(Instant.ofEpochMilli(retryTimeMillis)); 305 306 long exitStateTimeMillis = Long.valueOf(valueAry[3]); 307 Optional<Instant> exitStateTime = (exitStateTimeMillis == -1L) ? 308 Optional.empty() : Optional.of(Instant.ofEpochMilli(exitStateTimeMillis)); 309 310 return Optional.of(new DeviceStateResult(deviceState, errorCode, retryTime, 311 exitStateTime)); 312 } catch (Exception e) { 313 Log.d(LOG_TAG, "restoreDeviceState: exception " + e); 314 return Optional.empty(); 315 } 316 } 317 removeDeviceStateFromPreference(Context context, int subId)318 public static boolean removeDeviceStateFromPreference(Context context, int subId) { 319 SharedPreferences sharedPreferences = 320 PreferenceManager.getDefaultSharedPreferences(context); 321 SharedPreferences.Editor editor = sharedPreferences.edit(); 322 editor.remove(getDeviceStateSharedPrefKey(subId)); 323 return editor.commit(); 324 } 325 getDeviceStateSharedPrefKey(int subId)326 private static String getDeviceStateSharedPrefKey(int subId) { 327 return SHARED_PREF_DEVICE_STATE_KEY + subId; 328 } 329 330 /** 331 * Build the device state preference value. 332 */ getDeviceStateSharedPrefValue(DeviceStateResult deviceState)333 private static String getDeviceStateSharedPrefValue(DeviceStateResult deviceState) { 334 StringBuilder builder = new StringBuilder(); 335 builder.append(deviceState.getDeviceState()) // device state 336 .append(",").append(deviceState.getErrorCode().orElse(-1)); // error code 337 338 long retryTimeMillis = -1L; 339 Optional<Instant> retryTime = deviceState.getRequestRetryTime(); 340 if (retryTime.isPresent()) { 341 retryTimeMillis = retryTime.get().toEpochMilli(); 342 } 343 builder.append(",").append(retryTimeMillis); // retryTime 344 345 long exitStateTimeMillis = -1L; 346 Optional<Instant> exitStateTime = deviceState.getExitStateTime(); 347 if (exitStateTime.isPresent()) { 348 exitStateTimeMillis = exitStateTime.get().toEpochMilli(); 349 } 350 builder.append(",").append(exitStateTimeMillis); // exit state time 351 return builder.toString(); 352 } 353 354 /** 355 * Get the minimum value of the capabilities request retry after. 356 */ getMinimumRequestRetryAfterMillis()357 public static long getMinimumRequestRetryAfterMillis() { 358 return DEFAULT_MINIMUM_REQUEST_RETRY_AFTER_MS; 359 } 360 361 /** 362 * Override the capability request timeout to the millisecond value specified. Sending a 363 * value <= 0 will reset the capabilities. 364 */ setCapRequestTimeoutAfterMillis(long timeoutAfterMs)365 public static synchronized void setCapRequestTimeoutAfterMillis(long timeoutAfterMs) { 366 if (timeoutAfterMs <= 0L) { 367 OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.empty(); 368 } else { 369 OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.of(timeoutAfterMs); 370 } 371 } 372 373 /** 374 * Get the milliseconds of the capabilities request timed out. 375 * @return the time in milliseconds before a pending capabilities request will time out. 376 */ getCapRequestTimeoutAfterMillis()377 public static synchronized long getCapRequestTimeoutAfterMillis() { 378 if(OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS.isPresent()) { 379 return OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS.get(); 380 } else { 381 return DEFAULT_CAP_REQUEST_TIMEOUT_AFTER_MS; 382 } 383 } 384 385 /** 386 * Get the contact number from the given URI. 387 * @param contactUri The contact uri of the capabilities to request for. 388 * @return The number of the contact uri. NULL if the number cannot be retrieved. 389 */ getContactNumber(Uri contactUri)390 public static String getContactNumber(Uri contactUri) { 391 if (contactUri == null) { 392 return null; 393 } 394 String number = contactUri.getSchemeSpecificPart(); 395 if (TextUtils.isEmpty(number)) { 396 return null; 397 } 398 399 String numberParts[] = number.split("[@;:]"); 400 if (numberParts.length == 0) { 401 Log.d(LOG_TAG, "getContactNumber: the length of numberPars is 0"); 402 return contactUri.toString(); 403 } 404 return numberParts[0]; 405 } 406 407 /** 408 * Get the availability expiration from provisioning manager. 409 * @param subId The subscription ID 410 * @return the number of seconds for the availability cache expiration. 411 */ getAvailabilityCacheExpiration(int subId)412 public static long getAvailabilityCacheExpiration(int subId) { 413 long value = -1; 414 try { 415 ProvisioningManager pm = ProvisioningManager.createForSubscriptionId(subId); 416 value = pm.getProvisioningIntValue( 417 ProvisioningManager.KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC); 418 } catch (Exception e) { 419 Log.w(LOG_TAG, "Exception in getAvailabilityCacheExpiration: " + e); 420 } 421 422 if (value <= 0) { 423 Log.w(LOG_TAG, "The availability expiration cannot be less than 0."); 424 value = DEFAULT_AVAILABILITY_CACHE_EXPIRATION_SEC; 425 } 426 return value; 427 } 428 } 429