blob: cc1011fc2c6c04a4e17c0f36763ca57d938f928e [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.eab;
import static android.telephony.ims.RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS;
import static android.telephony.ims.RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE;
import static android.telephony.ims.RcsContactUceCapability.REQUEST_RESULT_NOT_FOUND;
import static android.telephony.ims.RcsContactUceCapability.SOURCE_TYPE_CACHED;
import static com.android.ims.rcs.uce.eab.EabProvider.EAB_OPTIONS_TABLE_NAME;
import static com.android.ims.rcs.uce.eab.EabProvider.EAB_PRESENCE_TUPLE_TABLE_NAME;
import android.annotation.NonNull;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.TelephonyManager;
import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.RcsContactPresenceTuple;
import android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RcsContactUceCapability.OptionsBuilder;
import android.telephony.ims.RcsContactUceCapability.PresenceBuilder;
import android.text.TextUtils;
import android.util.Log;
import com.android.i18n.phonenumbers.NumberParseException;
import com.android.i18n.phonenumbers.PhoneNumberUtil;
import com.android.i18n.phonenumbers.Phonenumber;
import com.android.ims.RcsFeatureManager;
import com.android.ims.rcs.uce.UceController.UceControllerCallback;
import com.android.internal.annotations.VisibleForTesting;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
/**
* The implementation of EabController.
*/
public class EabControllerImpl implements EabController {
private static final String TAG = "EabControllerImpl";
// 90 days
private static final int DEFAULT_CAPABILITY_CACHE_EXPIRATION_SEC = 90 * 24 * 60 * 60;
private static final int DEFAULT_AVAILABILITY_CACHE_EXPIRATION_SEC = 60;
// 1 week
private static final int CLEAN_UP_LEGACY_CAPABILITY_SEC = 7 * 24 * 60 * 60;
private static final int CLEAN_UP_LEGACY_CAPABILITY_DELAY_MILLI_SEC = 30 * 1000;
private final Context mContext;
private final int mSubId;
private final EabBulkCapabilityUpdater mEabBulkCapabilityUpdater;
private final Handler mHandler;
private UceControllerCallback mUceControllerCallback;
private volatile boolean mIsSetDestroyedFlag = false;
private ExpirationTimeFactory mExpirationTimeFactory = () -> Instant.now().getEpochSecond();
@VisibleForTesting
public final Runnable mCapabilityCleanupRunnable = () -> {
Log.d(TAG, "Cleanup Capabilities");
cleanupExpiredCapabilities();
};
@VisibleForTesting
public interface ExpirationTimeFactory {
long getExpirationTime();
}
public EabControllerImpl(Context context, int subId, UceControllerCallback c, Looper looper) {
mContext = context;
mSubId = subId;
mUceControllerCallback = c;
mHandler = new Handler(looper);
mEabBulkCapabilityUpdater = new EabBulkCapabilityUpdater(mContext, mSubId,
this,
new EabContactSyncController(),
mUceControllerCallback,
mHandler);
}
@Override
public void onRcsConnected(RcsFeatureManager manager) {
}
@Override
public void onRcsDisconnected() {
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
mIsSetDestroyedFlag = true;
mEabBulkCapabilityUpdater.onDestroy();
}
@Override
public void onCarrierConfigChanged() {
// Pick up changes to CarrierConfig and run any applicable cleanup tasks associated with
// that configuration.
mCapabilityCleanupRunnable.run();
}
/**
* Set the callback for sending the request to UceController.
*/
@Override
public void setUceRequestCallback(UceControllerCallback c) {
Objects.requireNonNull(c);
if (mIsSetDestroyedFlag) {
Log.d(TAG, "EabController destroyed.");
return;
}
mUceControllerCallback = c;
mEabBulkCapabilityUpdater.setUceRequestCallback(c);
}
/**
* Retrieve the contacts' capabilities from the EAB database.
*/
@Override
public @NonNull List<EabCapabilityResult> getCapabilities(@NonNull List<Uri> uris) {
Objects.requireNonNull(uris);
if (mIsSetDestroyedFlag) {
Log.d(TAG, "EabController destroyed.");
return generateDestroyedResult(uris);
}
Log.d(TAG, "getCapabilities uri size=" + uris.size());
List<EabCapabilityResult> capabilityResultList = new ArrayList();
for (Uri uri : uris) {
EabCapabilityResult result = generateEabResult(uri, this::isCapabilityExpired);
capabilityResultList.add(result);
}
return capabilityResultList;
}
/**
* Retrieve the contact's capabilities from the availability cache.
*/
@Override
public @NonNull EabCapabilityResult getAvailability(@NonNull Uri contactUri) {
Objects.requireNonNull(contactUri);
if (mIsSetDestroyedFlag) {
Log.d(TAG, "EabController destroyed.");
return new EabCapabilityResult(
contactUri,
EabCapabilityResult.EAB_CONTROLLER_DESTROYED_FAILURE,
null);
}
return generateEabResult(contactUri, this::isAvailabilityExpired);
}
/**
* Update the availability catch and save the capabilities to the EAB database.
*/
@Override
public void saveCapabilities(@NonNull List<RcsContactUceCapability> contactCapabilities) {
Objects.requireNonNull(contactCapabilities);
if (mIsSetDestroyedFlag) {
Log.d(TAG, "EabController destroyed.");
return;
}
Log.d(TAG, "Save capabilities: " + contactCapabilities.size());
// Update the capabilities
for (RcsContactUceCapability capability : contactCapabilities) {
String phoneNumber = getNumberFromUri(mContext, capability.getContactUri());
Cursor c = mContext.getContentResolver().query(
EabProvider.CONTACT_URI, null,
EabProvider.ContactColumns.PHONE_NUMBER + "=?",
new String[]{phoneNumber}, null);
if (c != null && c.moveToNext()) {
int contactId = getIntValue(c, EabProvider.ContactColumns._ID);
if (capability.getCapabilityMechanism() == CAPABILITY_MECHANISM_PRESENCE) {
Log.d(TAG, "Insert presence capability");
deleteOldPresenceCapability(contactId);
insertNewPresenceCapability(contactId, capability);
} else if (capability.getCapabilityMechanism() == CAPABILITY_MECHANISM_OPTIONS) {
Log.d(TAG, "Insert options capability");
deleteOldOptionCapability(contactId);
insertNewOptionCapability(contactId, capability);
}
} else {
Log.e(TAG, "The phone number can't find in contact table. ");
int contactId = insertNewContact(phoneNumber);
if (capability.getCapabilityMechanism() == CAPABILITY_MECHANISM_PRESENCE) {
insertNewPresenceCapability(contactId, capability);
} else if (capability.getCapabilityMechanism() == CAPABILITY_MECHANISM_OPTIONS) {
insertNewOptionCapability(contactId, capability);
}
}
if (c != null) {
c.close();
}
}
mEabBulkCapabilityUpdater.updateExpiredTimeAlert();
if (mHandler.hasCallbacks(mCapabilityCleanupRunnable)) {
mHandler.removeCallbacks(mCapabilityCleanupRunnable);
}
mHandler.postDelayed(mCapabilityCleanupRunnable,
CLEAN_UP_LEGACY_CAPABILITY_DELAY_MILLI_SEC);
}
private List<EabCapabilityResult> generateDestroyedResult(List<Uri> contactUri) {
List<EabCapabilityResult> destroyedResult = new ArrayList<>();
for (Uri uri : contactUri) {
destroyedResult.add(new EabCapabilityResult(
uri,
EabCapabilityResult.EAB_CONTROLLER_DESTROYED_FAILURE,
null));
}
return destroyedResult;
}
private EabCapabilityResult generateEabResult(Uri contactUri,
Predicate<Cursor> isExpiredMethod) {
RcsUceCapabilityBuilderWrapper builder = null;
EabCapabilityResult result;
// query EAB provider
Uri queryUri = Uri.withAppendedPath(
Uri.withAppendedPath(EabProvider.ALL_DATA_URI, String.valueOf(mSubId)),
getNumberFromUri(mContext, contactUri));
Cursor cursor = mContext.getContentResolver().query(
queryUri, null, null, null, null);
if (cursor != null && cursor.getCount() != 0) {
while (cursor.moveToNext()) {
if (isExpiredMethod.test(cursor)) {
continue;
}
if (builder == null) {
builder = createNewBuilder(contactUri, cursor);
} else {
updateCapability(contactUri, cursor, builder);
}
}
cursor.close();
if (builder == null) {
result = new EabCapabilityResult(contactUri,
EabCapabilityResult.EAB_CONTACT_EXPIRED_FAILURE,
null);
} else {
if (builder.getMechanism() == CAPABILITY_MECHANISM_PRESENCE) {
PresenceBuilder presenceBuilder = builder.getPresenceBuilder();
result = new EabCapabilityResult(contactUri,
EabCapabilityResult.EAB_QUERY_SUCCESSFUL,
presenceBuilder.build());
} else {
OptionsBuilder optionsBuilder = builder.getOptionsBuilder();
result = new EabCapabilityResult(contactUri,
EabCapabilityResult.EAB_QUERY_SUCCESSFUL,
optionsBuilder.build());
}
}
} else {
result = new EabCapabilityResult(contactUri,
EabCapabilityResult.EAB_CONTACT_NOT_FOUND_FAILURE, null);
}
return result;
}
private void updateCapability(Uri contactUri, Cursor cursor,
RcsUceCapabilityBuilderWrapper builderWrapper) {
if (builderWrapper.getMechanism() == CAPABILITY_MECHANISM_PRESENCE) {
PresenceBuilder builder = builderWrapper.getPresenceBuilder();
if (builder != null) {
builder.addCapabilityTuple(createPresenceTuple(contactUri, cursor));
}
} else {
OptionsBuilder builder = builderWrapper.getOptionsBuilder();
if (builder != null) {
builder.addFeatureTag(createOptionTuple(cursor));
}
}
}
private RcsUceCapabilityBuilderWrapper createNewBuilder(Uri contactUri, Cursor cursor) {
int mechanism = getIntValue(cursor, EabProvider.EabCommonColumns.MECHANISM);
int result = getIntValue(cursor, EabProvider.EabCommonColumns.REQUEST_RESULT);
RcsUceCapabilityBuilderWrapper builderWrapper =
new RcsUceCapabilityBuilderWrapper(mechanism);
if (mechanism == CAPABILITY_MECHANISM_PRESENCE) {
PresenceBuilder builder = new PresenceBuilder(
contactUri, SOURCE_TYPE_CACHED, result);
builder.addCapabilityTuple(createPresenceTuple(contactUri, cursor));
builderWrapper.setPresenceBuilder(builder);
} else {
OptionsBuilder builder = new OptionsBuilder(contactUri, SOURCE_TYPE_CACHED);
builder.setRequestResult(result);
builder.addFeatureTag(createOptionTuple(cursor));
builderWrapper.setOptionsBuilder(builder);
}
return builderWrapper;
}
private String createOptionTuple(Cursor cursor) {
return getStringValue(cursor, EabProvider.OptionsColumns.FEATURE_TAG);
}
private RcsContactPresenceTuple createPresenceTuple(Uri contactUri, Cursor cursor) {
// RcsContactPresenceTuple fields
String status = getStringValue(cursor, EabProvider.PresenceTupleColumns.BASIC_STATUS);
String serviceId = getStringValue(cursor, EabProvider.PresenceTupleColumns.SERVICE_ID);
String version = getStringValue(cursor, EabProvider.PresenceTupleColumns.SERVICE_VERSION);
String description = getStringValue(cursor, EabProvider.PresenceTupleColumns.DESCRIPTION);
String timeStamp = getStringValue(cursor,
EabProvider.PresenceTupleColumns.REQUEST_TIMESTAMP);
// ServiceCapabilities fields
boolean audioCapable = getIntValue(cursor,
EabProvider.PresenceTupleColumns.AUDIO_CAPABLE) == 1;
boolean videoCapable = getIntValue(cursor,
EabProvider.PresenceTupleColumns.VIDEO_CAPABLE) == 1;
String duplexModes = getStringValue(cursor,
EabProvider.PresenceTupleColumns.DUPLEX_MODE);
String unsupportedDuplexModes = getStringValue(cursor,
EabProvider.PresenceTupleColumns.UNSUPPORTED_DUPLEX_MODE);
String[] duplexModeList, unsupportedDuplexModeList;
if (!TextUtils.isEmpty(duplexModes)) {
duplexModeList = duplexModes.split(",");
} else {
duplexModeList = new String[0];
}
if (!TextUtils.isEmpty(unsupportedDuplexModes)) {
unsupportedDuplexModeList = unsupportedDuplexModes.split(",");
} else {
unsupportedDuplexModeList = new String[0];
}
// Create ServiceCapabilities
ServiceCapabilities serviceCapabilities;
ServiceCapabilities.Builder serviceCapabilitiesBuilder =
new ServiceCapabilities.Builder(audioCapable, videoCapable);
if (!TextUtils.isEmpty(duplexModes)
|| !TextUtils.isEmpty(unsupportedDuplexModes)) {
for (String duplexMode : duplexModeList) {
serviceCapabilitiesBuilder.addSupportedDuplexMode(duplexMode);
}
for (String unsupportedDuplex : unsupportedDuplexModeList) {
serviceCapabilitiesBuilder.addUnsupportedDuplexMode(unsupportedDuplex);
}
}
serviceCapabilities = serviceCapabilitiesBuilder.build();
// Create RcsContactPresenceTuple
RcsContactPresenceTuple.Builder rcsContactPresenceTupleBuilder =
new RcsContactPresenceTuple.Builder(status, serviceId, version);
if (description != null) {
rcsContactPresenceTupleBuilder.setServiceDescription(description);
}
if (contactUri != null) {
rcsContactPresenceTupleBuilder.setContactUri(contactUri);
}
if (serviceCapabilities != null) {
rcsContactPresenceTupleBuilder.setServiceCapabilities(serviceCapabilities);
}
if (timeStamp != null) {
try {
Instant instant = Instant.ofEpochSecond(Long.parseLong(timeStamp));
rcsContactPresenceTupleBuilder.setTime(instant);
} catch (NumberFormatException ex) {
Log.w(TAG, "Create presence tuple: NumberFormatException");
} catch (DateTimeParseException e) {
Log.w(TAG, "Create presence tuple: parse timestamp failed");
}
}
return rcsContactPresenceTupleBuilder.build();
}
private boolean isCapabilityExpired(Cursor cursor) {
boolean expired = false;
String requestTimeStamp = getRequestTimestamp(cursor);
int capabilityCacheExpiration;
if (isNonRcsCapability(cursor)) {
capabilityCacheExpiration = getNonRcsCapabilityCacheExpiration(mSubId);
} else {
capabilityCacheExpiration = getCapabilityCacheExpiration(mSubId);
}
if (requestTimeStamp != null) {
Instant expiredTimestamp = Instant
.ofEpochSecond(Long.parseLong(requestTimeStamp))
.plus(capabilityCacheExpiration, ChronoUnit.SECONDS);
expired = expiredTimestamp.isBefore(Instant.now());
Log.d(TAG, "Capability expiredTimestamp: " + expiredTimestamp.getEpochSecond() +
", isNonRcsCapability: " + isNonRcsCapability(cursor) +
", capabilityCacheExpiration: " + capabilityCacheExpiration +
", expired:" + expired);
} else {
Log.d(TAG, "Capability requestTimeStamp is null");
}
return expired;
}
private boolean isNonRcsCapability(Cursor cursor) {
int result = getIntValue(cursor, EabProvider.EabCommonColumns.REQUEST_RESULT);
return result == REQUEST_RESULT_NOT_FOUND;
}
private boolean isAvailabilityExpired(Cursor cursor) {
boolean expired = false;
String requestTimeStamp = getRequestTimestamp(cursor);
if (requestTimeStamp != null) {
Instant expiredTimestamp = Instant
.ofEpochSecond(Long.parseLong(requestTimeStamp))
.plus(getAvailabilityCacheExpiration(mSubId), ChronoUnit.SECONDS);
expired = expiredTimestamp.isBefore(Instant.now());
Log.d(TAG, "Availability insertedTimestamp: "
+ expiredTimestamp.getEpochSecond() + ", expired:" + expired);
} else {
Log.d(TAG, "Capability requestTimeStamp is null");
}
return expired;
}
private String getRequestTimestamp(Cursor cursor) {
String expiredTimestamp = null;
int mechanism = getIntValue(cursor, EabProvider.EabCommonColumns.MECHANISM);
if (mechanism == CAPABILITY_MECHANISM_PRESENCE) {
expiredTimestamp = getStringValue(cursor,
EabProvider.PresenceTupleColumns.REQUEST_TIMESTAMP);
} else if (mechanism == CAPABILITY_MECHANISM_OPTIONS) {
expiredTimestamp = getStringValue(cursor, EabProvider.OptionsColumns.REQUEST_TIMESTAMP);
}
return expiredTimestamp;
}
private int getNonRcsCapabilityCacheExpiration(int subId) {
int value;
PersistableBundle carrierConfig =
mContext.getSystemService(CarrierConfigManager.class).getConfigForSubId(subId);
if (carrierConfig != null) {
value = carrierConfig.getInt(
CarrierConfigManager.Ims.KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT);
} else {
value = DEFAULT_CAPABILITY_CACHE_EXPIRATION_SEC;
Log.e(TAG, "getNonRcsCapabilityCacheExpiration: " +
"CarrierConfig is null, returning default");
}
return value;
}
protected int getCapabilityCacheExpiration(int subId) {
int value = -1;
try {
ProvisioningManager pm = ProvisioningManager.createForSubscriptionId(subId);
value = pm.getProvisioningIntValue(
ProvisioningManager.KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC);
} catch (Exception ex) {
Log.e(TAG, "Exception in getCapabilityCacheExpiration(): " + ex);
}
if (value <= 0) {
value = DEFAULT_CAPABILITY_CACHE_EXPIRATION_SEC;
Log.e(TAG, "The capability expiration cannot be less than 0.");
}
return value;
}
protected long getAvailabilityCacheExpiration(int subId) {
long value = -1;
try {
ProvisioningManager pm = ProvisioningManager.createForSubscriptionId(subId);
value = pm.getProvisioningIntValue(
ProvisioningManager.KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC);
} catch (Exception ex) {
Log.e(TAG, "Exception in getAvailabilityCacheExpiration(): " + ex);
}
if (value <= 0) {
value = DEFAULT_AVAILABILITY_CACHE_EXPIRATION_SEC;
Log.e(TAG, "The Availability expiration cannot be less than 0.");
}
return value;
}
private int insertNewContact(String phoneNumber) {
ContentValues contentValues = new ContentValues();
contentValues.put(EabProvider.ContactColumns.PHONE_NUMBER, phoneNumber);
Uri result = mContext.getContentResolver().insert(EabProvider.CONTACT_URI, contentValues);
return Integer.parseInt(result.getLastPathSegment());
}
private void deleteOldPresenceCapability(int id) {
Cursor c = mContext.getContentResolver().query(
EabProvider.COMMON_URI,
new String[]{EabProvider.EabCommonColumns._ID},
EabProvider.EabCommonColumns.EAB_CONTACT_ID + "=?",
new String[]{String.valueOf(id)}, null);
if (c != null && c.getCount() > 0) {
while(c.moveToNext()) {
int commonId = c.getInt(c.getColumnIndex(EabProvider.EabCommonColumns._ID));
mContext.getContentResolver().delete(
EabProvider.PRESENCE_URI,
EabProvider.PresenceTupleColumns.EAB_COMMON_ID + "=?",
new String[]{String.valueOf(commonId)});
}
}
if (c != null) {
c.close();
}
}
private void insertNewPresenceCapability(int contactId, RcsContactUceCapability capability) {
ContentValues contentValues = new ContentValues();
contentValues.put(EabProvider.EabCommonColumns.EAB_CONTACT_ID, contactId);
contentValues.put(EabProvider.EabCommonColumns.MECHANISM, CAPABILITY_MECHANISM_PRESENCE);
contentValues.put(EabProvider.EabCommonColumns.SUBSCRIPTION_ID, mSubId);
contentValues.put(EabProvider.EabCommonColumns.REQUEST_RESULT,
capability.getRequestResult());
Uri result = mContext.getContentResolver().insert(EabProvider.COMMON_URI, contentValues);
int commonId = Integer.parseInt(result.getLastPathSegment());
Log.d(TAG, "Insert into common table. Id: " + commonId);
ContentValues[] presenceContent =
new ContentValues[capability.getCapabilityTuples().size()];
for (int i = 0; i < presenceContent.length; i++) {
RcsContactPresenceTuple tuple = capability.getCapabilityTuples().get(i);
// Create new ServiceCapabilities
ServiceCapabilities serviceCapabilities = tuple.getServiceCapabilities();
String duplexMode = null, unsupportedDuplexMode = null;
if (serviceCapabilities != null) {
List<String> duplexModes = serviceCapabilities.getSupportedDuplexModes();
if (duplexModes.size() != 0) {
duplexMode = TextUtils.join(",", duplexModes);
}
List<String> unsupportedDuplexModes =
serviceCapabilities.getUnsupportedDuplexModes();
if (unsupportedDuplexModes.size() != 0) {
unsupportedDuplexMode =
TextUtils.join(",", unsupportedDuplexModes);
}
}
contentValues = new ContentValues();
contentValues.put(EabProvider.PresenceTupleColumns.EAB_COMMON_ID, commonId);
contentValues.put(EabProvider.PresenceTupleColumns.BASIC_STATUS, tuple.getStatus());
contentValues.put(EabProvider.PresenceTupleColumns.SERVICE_ID, tuple.getServiceId());
contentValues.put(EabProvider.PresenceTupleColumns.SERVICE_VERSION,
tuple.getServiceVersion());
contentValues.put(EabProvider.PresenceTupleColumns.DESCRIPTION,
tuple.getServiceDescription());
// Using current timestamp instead of network timestamp since there is not use cases for
// network timestamp and the network timestamp may cause capability expire immediately.
contentValues.put(EabProvider.PresenceTupleColumns.REQUEST_TIMESTAMP,
mExpirationTimeFactory.getExpirationTime());
contentValues.put(EabProvider.PresenceTupleColumns.CONTACT_URI,
tuple.getContactUri().toString());
if (serviceCapabilities != null) {
contentValues.put(EabProvider.PresenceTupleColumns.DUPLEX_MODE, duplexMode);
contentValues.put(EabProvider.PresenceTupleColumns.UNSUPPORTED_DUPLEX_MODE,
unsupportedDuplexMode);
contentValues.put(EabProvider.PresenceTupleColumns.AUDIO_CAPABLE,
serviceCapabilities.isAudioCapable());
contentValues.put(EabProvider.PresenceTupleColumns.VIDEO_CAPABLE,
serviceCapabilities.isVideoCapable());
}
presenceContent[i] = contentValues;
}
Log.d(TAG, "Insert into presence table. count: " + presenceContent.length);
mContext.getContentResolver().bulkInsert(EabProvider.PRESENCE_URI, presenceContent);
}
private void deleteOldOptionCapability(int contactId) {
Cursor c = mContext.getContentResolver().query(
EabProvider.COMMON_URI,
new String[]{EabProvider.EabCommonColumns._ID},
EabProvider.EabCommonColumns.EAB_CONTACT_ID + "=?",
new String[]{String.valueOf(contactId)}, null);
if (c != null && c.getCount() > 0) {
while(c.moveToNext()) {
int commonId = c.getInt(c.getColumnIndex(EabProvider.EabCommonColumns._ID));
mContext.getContentResolver().delete(
EabProvider.OPTIONS_URI,
EabProvider.OptionsColumns.EAB_COMMON_ID + "=?",
new String[]{String.valueOf(commonId)});
}
}
if (c != null) {
c.close();
}
}
private void insertNewOptionCapability(int contactId, RcsContactUceCapability capability) {
ContentValues contentValues = new ContentValues();
contentValues.put(EabProvider.EabCommonColumns.EAB_CONTACT_ID, contactId);
contentValues.put(EabProvider.EabCommonColumns.MECHANISM, CAPABILITY_MECHANISM_OPTIONS);
contentValues.put(EabProvider.EabCommonColumns.SUBSCRIPTION_ID, mSubId);
contentValues.put(EabProvider.EabCommonColumns.REQUEST_RESULT,
capability.getRequestResult());
Uri result = mContext.getContentResolver().insert(EabProvider.COMMON_URI, contentValues);
int commonId = Integer.valueOf(result.getLastPathSegment());
List<ContentValues> optionContentList = new ArrayList<>();
for (String feature : capability.getFeatureTags()) {
contentValues = new ContentValues();
contentValues.put(EabProvider.OptionsColumns.EAB_COMMON_ID, commonId);
contentValues.put(EabProvider.OptionsColumns.FEATURE_TAG, feature);
contentValues.put(EabProvider.OptionsColumns.REQUEST_TIMESTAMP,
Instant.now().getEpochSecond());
optionContentList.add(contentValues);
}
ContentValues[] optionContent = new ContentValues[optionContentList.size()];
optionContent = optionContentList.toArray(optionContent);
mContext.getContentResolver().bulkInsert(EabProvider.OPTIONS_URI, optionContent);
}
private void cleanupExpiredCapabilities() {
// Cleanup the capabilities that expired more than 1 week
long rcsCapabilitiesExpiredTime = Instant.now().getEpochSecond() -
getCapabilityCacheExpiration(mSubId) -
CLEAN_UP_LEGACY_CAPABILITY_SEC;
// Cleanup the capabilities that expired more than 1 week
long nonRcsCapabilitiesExpiredTime = Instant.now().getEpochSecond() -
getNonRcsCapabilityCacheExpiration(mSubId) -
CLEAN_UP_LEGACY_CAPABILITY_SEC;
cleanupCapabilities(rcsCapabilitiesExpiredTime, getRcsCommonIdList());
cleanupCapabilities(nonRcsCapabilitiesExpiredTime, getNonRcsCommonIdList());
cleanupOrphanedRows();
}
private void cleanupCapabilities(long rcsCapabilitiesExpiredTime, List<Integer> commonIdList) {
if (commonIdList.size() > 0) {
String presenceClause =
EabProvider.PresenceTupleColumns.EAB_COMMON_ID +
" IN (" + TextUtils.join(",", commonIdList) + ") " + " AND " +
EabProvider.PresenceTupleColumns.REQUEST_TIMESTAMP + "<?";
String optionClause =
EabProvider.PresenceTupleColumns.EAB_COMMON_ID +
" IN (" + TextUtils.join(",", commonIdList) + ") " + " AND " +
EabProvider.OptionsColumns.REQUEST_TIMESTAMP + "<?";
int deletePresenceCount = mContext.getContentResolver().delete(
EabProvider.PRESENCE_URI,
presenceClause,
new String[]{String.valueOf(rcsCapabilitiesExpiredTime)});
int deleteOptionsCount = mContext.getContentResolver().delete(
EabProvider.OPTIONS_URI,
optionClause,
new String[]{String.valueOf(rcsCapabilitiesExpiredTime)});
Log.d(TAG, "Cleanup capabilities. deletePresenceCount: " + deletePresenceCount +
",deleteOptionsCount: " + deleteOptionsCount);
}
}
private List<Integer> getRcsCommonIdList() {
ArrayList<Integer> list = new ArrayList<>();
Cursor cursor = mContext.getContentResolver().query(
EabProvider.COMMON_URI,
null,
EabProvider.EabCommonColumns.REQUEST_RESULT + "<>?",
new String[]{String.valueOf(REQUEST_RESULT_NOT_FOUND)},
null);
if (cursor == null) return list;
while (cursor.moveToNext()) {
list.add(cursor.getInt(cursor.getColumnIndex(EabProvider.EabCommonColumns._ID)));
}
cursor.close();
return list;
}
private List<Integer> getNonRcsCommonIdList() {
ArrayList<Integer> list = new ArrayList<>();
Cursor cursor = mContext.getContentResolver().query(
EabProvider.COMMON_URI,
null,
EabProvider.EabCommonColumns.REQUEST_RESULT + "=?",
new String[]{String.valueOf(REQUEST_RESULT_NOT_FOUND)},
null);
if (cursor == null) return list;
while (cursor.moveToNext()) {
list.add(cursor.getInt(cursor.getColumnIndex(EabProvider.EabCommonColumns._ID)));
}
cursor.close();
return list;
}
/**
* Cleanup the entry of common table that can't map to presence or option table
*/
private void cleanupOrphanedRows() {
String presenceSelection =
" (SELECT " + EabProvider.PresenceTupleColumns.EAB_COMMON_ID +
" FROM " + EAB_PRESENCE_TUPLE_TABLE_NAME + ") ";
String optionSelection =
" (SELECT " + EabProvider.OptionsColumns.EAB_COMMON_ID +
" FROM " + EAB_OPTIONS_TABLE_NAME + ") ";
mContext.getContentResolver().delete(
EabProvider.COMMON_URI,
EabProvider.EabCommonColumns._ID + " NOT IN " + presenceSelection +
" AND " + EabProvider.EabCommonColumns._ID+ " NOT IN " + optionSelection,
null);
}
private String getStringValue(Cursor cursor, String column) {
return cursor.getString(cursor.getColumnIndex(column));
}
private int getIntValue(Cursor cursor, String column) {
return cursor.getInt(cursor.getColumnIndex(column));
}
private static String getNumberFromUri(Context context, Uri uri) {
String number = uri.getSchemeSpecificPart();
String[] numberParts = number.split("[@;:]");
if (numberParts.length == 0) {
return null;
}
return formatNumber(context, numberParts[0]);
}
static String formatNumber(Context context, String number) {
TelephonyManager manager = context.getSystemService(TelephonyManager.class);
String simCountryIso = manager.getSimCountryIso();
if (simCountryIso != null) {
simCountryIso = simCountryIso.toUpperCase();
PhoneNumberUtil util = PhoneNumberUtil.getInstance();
try {
Phonenumber.PhoneNumber phoneNumber = util.parse(number, simCountryIso);
return util.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
} catch (NumberParseException e) {
Log.w(TAG, "formatNumber: could not format " + number + ", error: " + e);
}
}
return number;
}
@VisibleForTesting
public void setExpirationTimeFactory(ExpirationTimeFactory factory) {
mExpirationTimeFactory = factory;
}
}