blob: c88de0b3083001a20baa185439f6729b41283174 [file] [log] [blame]
/*
* 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];
}
}