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