| /* |
| * 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; |
| } |
| } |