All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.ims.rcs.uce.eab.EabControllerImpl Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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.Optional;
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 getCapabilities(@NonNull List uris) {
        Objects.requireNonNull(uris);
        if (mIsSetDestroyedFlag) {
            Log.d(TAG, "EabController destroyed.");
            return generateDestroyedResult(uris);
        }

        Log.d(TAG, "getCapabilities uri size=" + uris.size());
        List capabilityResultList = new ArrayList();

        for (Uri uri : uris) {
            EabCapabilityResult result = generateEabResult(uri, this::isCapabilityExpired);
            capabilityResultList.add(result);
        }
        return capabilityResultList;
    }

    /**
     * Retrieve the contacts' capabilities from the EAB database including expired capabilities.
     */
    @Override
    public @NonNull List getCapabilitiesIncludingExpired(
            @NonNull List uris) {
        Objects.requireNonNull(uris);
        if (mIsSetDestroyedFlag) {
            Log.d(TAG, "EabController destroyed.");
            return generateDestroyedResult(uris);
        }

        Log.d(TAG, "getCapabilitiesIncludingExpired uri size=" + uris.size());
        List capabilityResultList = new ArrayList();

        for (Uri uri : uris) {
            EabCapabilityResult result = generateEabResultIncludingExpired(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);
    }

    /**
     * Retrieve the contact's capabilities from the availability cache including expired
     * capabilities.
     */
    @Override
    public @NonNull EabCapabilityResult getAvailabilityIncludingExpired(@NonNull Uri contactUri) {
        Objects.requireNonNull(contactUri);
        if (mIsSetDestroyedFlag) {
            Log.d(TAG, "EabController destroyed.");
            return new EabCapabilityResult(
                contactUri,
                EabCapabilityResult.EAB_CONTROLLER_DESTROYED_FAILURE,
                null);
        }
        return generateEabResultIncludingExpired(contactUri, this::isAvailabilityExpired);
    }

    /**
     * Update the availability catch and save the capabilities to the EAB database.
     */
    @Override
    public void saveCapabilities(@NonNull List 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 generateDestroyedResult(List contactUri) {
        List 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 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 EabCapabilityResult generateEabResultIncludingExpired(Uri contactUri,
            Predicate isExpiredMethod) {
        RcsUceCapabilityBuilderWrapper builder = null;
        EabCapabilityResult result;
        Optional isExpired = Optional.empty();

        // 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()) {
                // Record whether it has expired.
                if (!isExpired.isPresent()) {
                    isExpired = Optional.of(isExpiredMethod.test(cursor));
                }
                if (builder == null) {
                    builder = createNewBuilder(contactUri, cursor);
                } else {
                    updateCapability(contactUri, cursor, builder);
                }
            }
            cursor.close();

            // Determine the query result
            int eabResult = EabCapabilityResult.EAB_QUERY_SUCCESSFUL;
            if (isExpired.orElse(false)) {
                eabResult = EabCapabilityResult.EAB_CONTACT_EXPIRED_FAILURE;
            }

            if (builder.getMechanism() == CAPABILITY_MECHANISM_PRESENCE) {
                PresenceBuilder presenceBuilder = builder.getPresenceBuilder();
                result = new EabCapabilityResult(contactUri, eabResult, presenceBuilder.build());
            } else {
                OptionsBuilder optionsBuilder = builder.getOptionsBuilder();
                result = new EabCapabilityResult(contactUri, eabResult, 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 duplexModes = serviceCapabilities.getSupportedDuplexModes();
                if (duplexModes.size() != 0) {
                    duplexMode = TextUtils.join(",", duplexModes);
                }

                List 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 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 commonIdList) {
        if (commonIdList.size() > 0) {
            String presenceClause =
                    EabProvider.PresenceTupleColumns.EAB_COMMON_ID +
                            " IN (" + TextUtils.join(",", commonIdList) + ") " + " AND " +
                            EabProvider.PresenceTupleColumns.REQUEST_TIMESTAMP + " getRcsCommonIdList() {
        ArrayList 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 getNonRcsCommonIdList() {
        ArrayList 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy