| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ims.rcs.uce.util; |
| |
| import android.content.Context; |
| import android.content.SharedPreferences; |
| import android.net.Uri; |
| import android.os.PersistableBundle; |
| import android.preference.PreferenceManager; |
| import android.provider.BlockedNumberContract; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.ims.ProvisioningManager; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult; |
| |
| import java.time.Instant; |
| import java.util.Optional; |
| import java.util.concurrent.TimeUnit; |
| |
| public class UceUtils { |
| |
| public static final int LOG_SIZE = 20; |
| private static final String LOG_PREFIX = "RcsUce."; |
| private static final String LOG_TAG = LOG_PREFIX + "UceUtils"; |
| |
| private static final String SHARED_PREF_DEVICE_STATE_KEY = "UceDeviceState"; |
| |
| private static final int DEFAULT_RCL_MAX_NUM_ENTRIES = 100; |
| private static final long DEFAULT_RCS_PUBLISH_SOURCE_THROTTLE_MS = 60000L; |
| private static final long DEFAULT_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = |
| TimeUnit.DAYS.toSeconds(30); |
| private static final long DEFAULT_REQUEST_RETRY_INTERVAL_MS = TimeUnit.MINUTES.toMillis(20); |
| private static final long DEFAULT_MINIMUM_REQUEST_RETRY_AFTER_MS = TimeUnit.SECONDS.toMillis(3); |
| |
| // The default of the capabilities request timeout. |
| private static final long DEFAULT_CAP_REQUEST_TIMEOUT_AFTER_MS = TimeUnit.MINUTES.toMillis(3); |
| private static Optional<Long> OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.empty(); |
| |
| // The task ID of the UCE request |
| private static long TASK_ID = 0L; |
| |
| // The request coordinator ID |
| private static long REQUEST_COORDINATOR_ID = 0; |
| |
| /** |
| * Get the log prefix of RCS UCE |
| */ |
| public static String getLogPrefix() { |
| return LOG_PREFIX; |
| } |
| |
| /** |
| * Generate the unique UCE request task id. |
| */ |
| public static synchronized long generateTaskId() { |
| return ++TASK_ID; |
| } |
| |
| /** |
| * Generate the unique request coordinator id. |
| */ |
| public static synchronized long generateRequestCoordinatorId() { |
| return ++REQUEST_COORDINATOR_ID; |
| } |
| |
| public static boolean isEabProvisioned(Context context, int subId) { |
| boolean isProvisioned = false; |
| if (!SubscriptionManager.isValidSubscriptionId(subId)) { |
| Log.w(LOG_TAG, "isEabProvisioned: invalid subscriptionId " + subId); |
| return false; |
| } |
| CarrierConfigManager configManager = (CarrierConfigManager) |
| context.getSystemService(Context.CARRIER_CONFIG_SERVICE); |
| if (configManager != null) { |
| PersistableBundle config = configManager.getConfigForSubId(subId); |
| if (config != null && !config.getBoolean( |
| CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONED_BOOL)) { |
| return true; |
| } |
| } |
| try { |
| ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId); |
| isProvisioned = manager.getProvisioningIntValue( |
| ProvisioningManager.KEY_EAB_PROVISIONING_STATUS) |
| == ProvisioningManager.PROVISIONING_VALUE_ENABLED; |
| } catch (Exception e) { |
| Log.w(LOG_TAG, "isEabProvisioned: exception=" + e.getMessage()); |
| } |
| return isProvisioned; |
| } |
| |
| /** |
| * Check whether or not this carrier supports the exchange of phone numbers with the carrier's |
| * presence server. |
| */ |
| public static boolean isPresenceCapExchangeEnabled(Context context, int subId) { |
| CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); |
| if (configManager == null) { |
| return false; |
| } |
| PersistableBundle config = configManager.getConfigForSubId(subId); |
| if (config == null) { |
| return false; |
| } |
| return config.getBoolean( |
| CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL); |
| } |
| |
| /** |
| * Check if Presence is supported by the carrier. |
| */ |
| public static boolean isPresenceSupported(Context context, int subId) { |
| CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); |
| if (configManager == null) { |
| return false; |
| } |
| PersistableBundle config = configManager.getConfigForSubId(subId); |
| if (config == null) { |
| return false; |
| } |
| return config.getBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL); |
| } |
| |
| /** |
| * Check if SIP OPTIONS is supported by the carrier. |
| */ |
| public static boolean isSipOptionsSupported(Context context, int subId) { |
| CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); |
| if (configManager == null) { |
| return false; |
| } |
| PersistableBundle config = configManager.getConfigForSubId(subId); |
| if (config == null) { |
| return false; |
| } |
| return config.getBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL); |
| } |
| |
| /** |
| * Check whether the PRESENCE group subscribe is enabled or not. |
| * |
| * @return true when the Presence group subscribe is enabled, false otherwise. |
| */ |
| public static boolean isPresenceGroupSubscribeEnabled(Context context, int subId) { |
| CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); |
| if (configManager == null) { |
| return false; |
| } |
| PersistableBundle config = configManager.getConfigForSubId(subId); |
| if (config == null) { |
| return false; |
| } |
| return config.getBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL); |
| } |
| |
| /** |
| * Returns {@code true} if {@code phoneNumber} is blocked. |
| * |
| * @param context the context of the caller. |
| * @param phoneNumber the number to check. |
| * @return true if the number is blocked, false otherwise. |
| */ |
| public static boolean isNumberBlocked(Context context, String phoneNumber) { |
| int blockStatus; |
| try { |
| blockStatus = BlockedNumberContract.SystemContract.shouldSystemBlockNumber( |
| context, phoneNumber, null /*extras*/); |
| } catch (Exception e) { |
| return false; |
| } |
| return blockStatus != BlockedNumberContract.STATUS_NOT_BLOCKED; |
| } |
| |
| /** |
| * Get the minimum time that allow two PUBLISH requests can be executed continuously. |
| * |
| * @param subId The subscribe ID |
| * @return The milliseconds that allowed two consecutive publish request. |
| */ |
| public static long getRcsPublishThrottle(int subId) { |
| long throttle = DEFAULT_RCS_PUBLISH_SOURCE_THROTTLE_MS; |
| try { |
| ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId); |
| long provisioningValue = manager.getProvisioningIntValue( |
| ProvisioningManager.KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS); |
| if (provisioningValue > 0) { |
| throttle = provisioningValue; |
| } |
| } catch (Exception e) { |
| Log.w(LOG_TAG, "getRcsPublishThrottle: exception=" + e.getMessage()); |
| } |
| return throttle; |
| } |
| |
| /** |
| * Retrieve the maximum number of contacts that is in one Request Contained List(RCL) |
| * |
| * @param subId The subscribe ID |
| * @return The maximum number of contacts. |
| */ |
| public static int getRclMaxNumberEntries(int subId) { |
| int maxNumEntries = DEFAULT_RCL_MAX_NUM_ENTRIES; |
| try { |
| ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId); |
| int provisioningValue = manager.getProvisioningIntValue( |
| ProvisioningManager.KEY_RCS_MAX_NUM_ENTRIES_IN_RCL); |
| if (provisioningValue > 0) { |
| maxNumEntries = provisioningValue; |
| } |
| } catch (Exception e) { |
| Log.w(LOG_TAG, "getRclMaxNumberEntries: exception=" + e.getMessage()); |
| } |
| return maxNumEntries; |
| } |
| |
| public static long getNonRcsCapabilitiesCacheExpiration(Context context, int subId) { |
| CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); |
| if (configManager == null) { |
| return DEFAULT_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC; |
| } |
| PersistableBundle config = configManager.getConfigForSubId(subId); |
| if (config == null) { |
| return DEFAULT_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC; |
| } |
| return config.getInt( |
| CarrierConfigManager.Ims.KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT); |
| } |
| |
| public static boolean isRequestForbiddenBySip489(Context context, int subId) { |
| CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); |
| if (configManager == null) { |
| return false; |
| } |
| PersistableBundle config = configManager.getConfigForSubId(subId); |
| if (config == null) { |
| return false; |
| } |
| return config.getBoolean( |
| CarrierConfigManager.Ims.KEY_RCS_REQUEST_FORBIDDEN_BY_SIP_489_BOOL); |
| } |
| |
| public static long getRequestRetryInterval(Context context, int subId) { |
| CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class); |
| if (configManager == null) { |
| return DEFAULT_REQUEST_RETRY_INTERVAL_MS; |
| } |
| PersistableBundle config = configManager.getConfigForSubId(subId); |
| if (config == null) { |
| return DEFAULT_REQUEST_RETRY_INTERVAL_MS; |
| } |
| return config.getLong( |
| CarrierConfigManager.Ims.KEY_RCS_REQUEST_RETRY_INTERVAL_MILLIS_LONG); |
| } |
| |
| public static boolean saveDeviceStateToPreference(Context context, int subId, |
| DeviceStateResult deviceState) { |
| SharedPreferences sharedPreferences = |
| PreferenceManager.getDefaultSharedPreferences(context); |
| SharedPreferences.Editor editor = sharedPreferences.edit(); |
| editor.putString(getDeviceStateSharedPrefKey(subId), |
| getDeviceStateSharedPrefValue(deviceState)); |
| return editor.commit(); |
| } |
| |
| public static Optional<DeviceStateResult> restoreDeviceState(Context context, int subId) { |
| SharedPreferences sharedPreferences = |
| PreferenceManager.getDefaultSharedPreferences(context); |
| final String sharedPrefKey = getDeviceStateSharedPrefKey(subId); |
| String sharedPrefValue = sharedPreferences.getString(sharedPrefKey, ""); |
| if (TextUtils.isEmpty(sharedPrefValue)) { |
| return Optional.empty(); |
| } |
| String[] valueAry = sharedPrefValue.split(","); |
| if (valueAry == null || valueAry.length != 4) { |
| return Optional.empty(); |
| } |
| try { |
| int deviceState = Integer.valueOf(valueAry[0]); |
| Optional<Integer> errorCode = (Integer.valueOf(valueAry[1]) == -1L) ? |
| Optional.empty() : Optional.of(Integer.valueOf(valueAry[1])); |
| |
| long retryTimeMillis = Long.valueOf(valueAry[2]); |
| Optional<Instant> retryTime = (retryTimeMillis == -1L) ? |
| Optional.empty() : Optional.of(Instant.ofEpochMilli(retryTimeMillis)); |
| |
| long exitStateTimeMillis = Long.valueOf(valueAry[3]); |
| Optional<Instant> exitStateTime = (exitStateTimeMillis == -1L) ? |
| Optional.empty() : Optional.of(Instant.ofEpochMilli(exitStateTimeMillis)); |
| |
| return Optional.of(new DeviceStateResult(deviceState, errorCode, retryTime, |
| exitStateTime)); |
| } catch (Exception e) { |
| Log.d(LOG_TAG, "restoreDeviceState: exception " + e); |
| return Optional.empty(); |
| } |
| } |
| |
| public static boolean removeDeviceStateFromPreference(Context context, int subId) { |
| SharedPreferences sharedPreferences = |
| PreferenceManager.getDefaultSharedPreferences(context); |
| SharedPreferences.Editor editor = sharedPreferences.edit(); |
| editor.remove(getDeviceStateSharedPrefKey(subId)); |
| return editor.commit(); |
| } |
| |
| private static String getDeviceStateSharedPrefKey(int subId) { |
| return SHARED_PREF_DEVICE_STATE_KEY + subId; |
| } |
| |
| /** |
| * Build the device state preference value. |
| */ |
| private static String getDeviceStateSharedPrefValue(DeviceStateResult deviceState) { |
| StringBuilder builder = new StringBuilder(); |
| builder.append(deviceState.getDeviceState()) // device state |
| .append(",").append(deviceState.getErrorCode().orElse(-1)); // error code |
| |
| long retryTimeMillis = -1L; |
| Optional<Instant> retryTime = deviceState.getRequestRetryTime(); |
| if (retryTime.isPresent()) { |
| retryTimeMillis = retryTime.get().toEpochMilli(); |
| } |
| builder.append(",").append(retryTimeMillis); // retryTime |
| |
| long exitStateTimeMillis = -1L; |
| Optional<Instant> exitStateTime = deviceState.getExitStateTime(); |
| if (exitStateTime.isPresent()) { |
| exitStateTimeMillis = exitStateTime.get().toEpochMilli(); |
| } |
| builder.append(",").append(exitStateTimeMillis); // exit state time |
| return builder.toString(); |
| } |
| |
| /** |
| * Get the minimum value of the capabilities request retry after. |
| */ |
| public static long getMinimumRequestRetryAfterMillis() { |
| return DEFAULT_MINIMUM_REQUEST_RETRY_AFTER_MS; |
| } |
| |
| /** |
| * Override the capability request timeout to the millisecond value specified. Sending a |
| * value <= 0 will reset the capabilities. |
| */ |
| public static synchronized void setCapRequestTimeoutAfterMillis(long timeoutAfterMs) { |
| if (timeoutAfterMs <= 0L) { |
| OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.empty(); |
| } else { |
| OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.of(timeoutAfterMs); |
| } |
| } |
| |
| /** |
| * Get the milliseconds of the capabilities request timed out. |
| * @return the time in milliseconds before a pending capabilities request will time out. |
| */ |
| public static synchronized long getCapRequestTimeoutAfterMillis() { |
| if(OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS.isPresent()) { |
| return OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS.get(); |
| } else { |
| return DEFAULT_CAP_REQUEST_TIMEOUT_AFTER_MS; |
| } |
| } |
| |
| /** |
| * Get the contact number from the given URI. |
| * @param contactUri The contact uri of the capabilities to request for. |
| * @return The number of the contact uri. NULL if the number cannot be retrieved. |
| */ |
| public static String getContactNumber(Uri contactUri) { |
| if (contactUri == null) { |
| return null; |
| } |
| String number = contactUri.getSchemeSpecificPart(); |
| if (TextUtils.isEmpty(number)) { |
| return null; |
| } |
| |
| String numberParts[] = number.split("[@;:]"); |
| if (numberParts.length == 0) { |
| Log.d(LOG_TAG, "getContactNumber: the length of numberPars is 0"); |
| return contactUri.toString(); |
| } |
| return numberParts[0]; |
| } |
| } |