Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6ba4f3fa authored by James.cf Lin's avatar James.cf Lin
Browse files

Fix the UCE capability request with subscribe mechanism

1. Use the carrier config "KEY_ENABLE_PRESENCE_PUBLISH_BOOL" instead of the config "KEY_USE_RCS_PRESENCE_BOOL
2. Check the carrier config "KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL" before requesting capabilities.
3. Skip checking the cache when the capability request is triggered by the capability polling service
4. Support shell command to remove eab contacts from the EAB database.

Bug: 174656329
Test: atest -c CtsTelephonyTestCases:android.telephony.ims.cts.RcsUceAdapterTest
Change-Id: I5429b5a9b0afbfdfa3f831f9994b3980ae545f59
parent 23f045d8
Loading
Loading
Loading
Loading
+62 −36
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.ims.rcs.uce;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.Uri;
import android.os.HandlerThread;
@@ -37,6 +38,7 @@ import com.android.ims.RcsFeatureManager;
import com.android.ims.rcs.uce.eab.EabCapabilityResult;
import com.android.ims.rcs.uce.eab.EabController;
import com.android.ims.rcs.uce.eab.EabControllerImpl;
import com.android.ims.rcs.uce.eab.EabUtil;
import com.android.ims.rcs.uce.options.OptionsController;
import com.android.ims.rcs.uce.options.OptionsControllerImpl;
import com.android.ims.rcs.uce.presence.publish.PublishController;
@@ -89,15 +91,11 @@ public class UceController {
        /**
         * The network reply that the request is forbidden.
         * @param isForbidden If UCE requests are forbidden by the network.
         * @param errorCode The {@link RcsUceAdapter#ErrorCode} of the forbidden reason.
         * @param retryAfterMillis The time to wait for the retry.
         */
        void updateRequestForbidden(boolean isForbidden, long retryAfterMillis);

        /**
         * Check if UCE request is forbidden by the network.
         * @return true when the UCE is forbidden by the network
         */
        boolean isRequestForbiddenByNetwork();
        void updateRequestForbidden(boolean isForbidden, @Nullable Integer errorCode,
                long retryAfterMillis);

        /**
         * Get the milliseconds need to wait for retry.
@@ -105,6 +103,12 @@ public class UceController {
         */
        long getRetryAfterMillis();

        /**
         * Check if UCE request is forbidden by the network.
         * @return true when the UCE is forbidden by the network
         */
        boolean isRequestForbiddenByNetwork();

        /**
         * Trigger the capabilities request with OPTIONS
         */
@@ -211,7 +215,7 @@ public class UceController {
    private UceRequestManager mRequestManager;

    // The server state for UCE requests.
    private ServerState mServerState;
    private final ServerState mServerState;

    public UceController(Context context, int subId) {
        mSubId = subId;
@@ -340,18 +344,19 @@ public class UceController {
        }

        @Override
        public void updateRequestForbidden(boolean isForbidden, long retryAfterMillis) {
            onRequestForbidden(isForbidden, retryAfterMillis);
        public void updateRequestForbidden(boolean isForbidden, @Nullable Integer errorCode,
                long retryAfterMillis) {
            mServerState.updateRequestForbidden(isForbidden, errorCode, retryAfterMillis);
        }

        @Override
        public boolean isRequestForbiddenByNetwork() {
            return mServerState.isRequestForbidden();
        public long getRetryAfterMillis() {
            return mServerState.getRetryAfterMillis();
        }

        @Override
        public long getRetryAfterMillis() {
            return mServerState.getRetryAfterMillis();
        public boolean isRequestForbiddenByNetwork() {
            return (mServerState.getForbiddenErrorCode() != null) ? true : false;
        }

        @Override
@@ -365,7 +370,7 @@ public class UceController {
        public void refreshCapabilities(@NonNull List<Uri> contactNumbers,
                @NonNull IRcsUceControllerCallback callback) throws RemoteException{
            logd("refreshCapabilities: " + contactNumbers.size());
            UceController.this.requestCapabilities(contactNumbers, callback);
            UceController.this.requestCapabilitiesInternal(contactNumbers, true, callback);
        }

        @Override
@@ -416,7 +421,12 @@ public class UceController {
     */
    public void requestCapabilities(@NonNull List<Uri> uriList,
            @NonNull IRcsUceControllerCallback c) throws RemoteException {
        if (uriList == null || c == null) {
        requestCapabilitiesInternal(uriList, false, c);
    }

    private void requestCapabilitiesInternal(@NonNull List<Uri> uriList, boolean skipFromCache,
            @NonNull IRcsUceControllerCallback c) throws RemoteException {
        if (uriList == null || uriList.isEmpty() || c == null) {
            logw("requestCapabilities: parameter is empty");
            if (c != null) {
                c.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L);
@@ -432,15 +442,18 @@ public class UceController {

        // Check if UCE requests are forbidden by the network.
        if (mServerState.isRequestForbidden()) {
            Integer errorCode = mServerState.getForbiddenErrorCode();
            long retryAfter = mServerState.getRetryAfterMillis();
            logw("requestCapabilities: The request is forbidden, retryAfter=" + retryAfter);
            c.onError(RcsUceAdapter.ERROR_FORBIDDEN, retryAfter);
            logw("requestCapabilities: The request is forbidden, errorCode=" + errorCode
                    + ", retryAfter=" + retryAfter);
            errorCode = (errorCode != null) ? errorCode : RcsUceAdapter.ERROR_FORBIDDEN;
            c.onError(errorCode, retryAfter);
            return;
        }

        // Trigger the capabilities request task
        logd("requestCapabilities: " + uriList.size());
        mRequestManager.sendCapabilityRequest(uriList, c);
        mRequestManager.sendCapabilityRequest(uriList, skipFromCache, c);
    }

    /**
@@ -466,9 +479,12 @@ public class UceController {

        // Check if UCE requests are forbidden by the network.
        if (mServerState.isRequestForbidden()) {
            Integer errorCode = mServerState.getForbiddenErrorCode();
            long retryAfter = mServerState.getRetryAfterMillis();
            logw("requestAvailability: The request is forbidden, retryAfter=" + retryAfter);
            c.onError(RcsUceAdapter.ERROR_FORBIDDEN, retryAfter);
            logw("requestCapabilities: The request is forbidden, errorCode=" + errorCode
                + ", retryAfter=" + retryAfter);
            errorCode = (errorCode != null) ? errorCode : RcsUceAdapter.ERROR_FORBIDDEN;
            c.onError(errorCode, retryAfter);
            return;
        }

@@ -482,6 +498,9 @@ public class UceController {
     */
    public void onRequestPublishCapabilitiesFromService(@StackPublishTriggerType int triggerType) {
        logd("onRequestPublishCapabilitiesFromService: " + triggerType);
        // Reset the forbidden status if the service requests to publish the device's capabilities
        mServerState.updateRequestForbidden(false, null, 0L);
        // Send the publish request.
        mPublishController.requestPublishCapabilitiesFromService(triggerType);
    }

@@ -504,14 +523,6 @@ public class UceController {
        mOptionsController.retrieveCapabilitiesForRemote(contactUri, remoteCapabilities, c);
    }

    /**
     * Update the Request forbidden state.
     */
    public void onRequestForbidden(boolean isForbidden, long retryAfterMillis) {
        logi("onRequestForbidden: forbidden=" + isForbidden + ", retry=" + retryAfterMillis);
        mServerState.forbidUceRequest(isForbidden, retryAfterMillis);
    }

    /**
     * Register a {@link PublishStateCallback} to receive the published state changed.
     */
@@ -557,34 +568,40 @@ public class UceController {
     */
    @VisibleForTesting
    public static class ServerState {
        // If UCE requests are forbidden by the network.
        private boolean mIsForbidden;
        private Integer mForbiddenErrorCode;

        // The timestamp when the network allows the UCE requests. This value may be null if the
        // network doesn't specified any retryAfter info.
        private Instant mAllowedTimestamp;

        private final Object mNetworkStateLock = new Object();
        private final Object mServerStateLock = new Object();

        public ServerState() {
            mIsForbidden = false;
            mForbiddenErrorCode = null;
            mAllowedTimestamp = null;
        }

        public void forbidUceRequest(boolean isForbidden, long retryAfterMillis) {
            synchronized (mNetworkStateLock) {
        public void updateRequestForbidden(boolean isForbidden, @Nullable Integer errorCode,
                long retryAfterMillis) {
            synchronized (mServerStateLock) {
                mIsForbidden = isForbidden;
                if (!mIsForbidden) {
                    mForbiddenErrorCode = null;
                    mAllowedTimestamp = null;
                } else {
                    mForbiddenErrorCode =
                        (errorCode == null) ? RcsUceAdapter.ERROR_FORBIDDEN : errorCode;
                    mAllowedTimestamp = Instant.now().plus(retryAfterMillis, ChronoUnit.MILLIS);
                }
                Log.d(LOG_TAG, "forbidUceRequest: " + mIsForbidden + ",time=" + mAllowedTimestamp);
                Log.i(LOG_TAG, "updateRequestForbidden: isForbidden=" + mIsForbidden
                        + ", errorCode=" + mForbiddenErrorCode + ", time=" + mAllowedTimestamp);
            }
        }

        public boolean isRequestForbidden() {
            synchronized (mNetworkStateLock) {
            synchronized (mServerStateLock) {
                if (mIsForbidden && mAllowedTimestamp != null) {
                    return Instant.now().isBefore(mAllowedTimestamp);
                }
@@ -592,8 +609,17 @@ public class UceController {
            }
        }

        public @Nullable Integer getForbiddenErrorCode() {
            synchronized (mServerStateLock) {
                if (!mIsForbidden) {
                    return null;
                }
                return mForbiddenErrorCode;
            }
        }

        public long getRetryAfterMillis() {
            synchronized (mNetworkStateLock) {
            synchronized (mServerStateLock) {
                if (!mIsForbidden || mAllowedTimestamp == null) {
                    return 0L;
                }
+37 −18
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ 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.telephony.ims.RcsContactUceCapability.RcsUcsCapabilityBuilder;
import android.text.TextUtils;
import android.util.Log;

@@ -201,7 +200,7 @@ public class EabControllerImpl implements EabController {

    private EabCapabilityResult generateEabResult(Uri contactUri,
            Predicate<Cursor> isExpiredMethod) {
        RcsContactUceCapability.RcsUcsCapabilityBuilder builder = null;
        RcsUceCapabilityBuilderWrapper builder = null;
        EabCapabilityResult result;

        // query EAB provider
@@ -231,9 +230,18 @@ public class EabControllerImpl implements EabController {
                        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,
                        builder.build());
                            presenceBuilder.build());
                } else {
                    OptionsBuilder optionsBuilder = builder.getOptionsBuilder();
                    result = new EabCapabilityResult(contactUri,
                            EabCapabilityResult.EAB_QUERY_SUCCESSFUL,
                            optionsBuilder.build());
                }

            }
        } else {
            result = new EabCapabilityResult(contactUri,
@@ -242,26 +250,36 @@ public class EabControllerImpl implements EabController {
        return result;
    }

    private void updateCapability(Uri contactUri, Cursor cursor, RcsUcsCapabilityBuilder builder) {
        if (builder instanceof PresenceBuilder) {
            ((PresenceBuilder) builder).addCapabilityTuple(createPresenceTuple(contactUri, cursor));
    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).addFeatureTag(createOptionTuple(cursor));
            OptionsBuilder builder = builderWrapper.getOptionsBuilder();
            if (builder != null) {
                builder.addFeatureTag(createOptionTuple(cursor));
            }
        }
    }

    private RcsUcsCapabilityBuilder createNewBuilder(Uri contactUri, Cursor 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, CAPABILITY_MECHANISM_PRESENCE, result);
            builder.addCapabilityTuple(createPresenceTuple(contactUri, cursor));
            return builder;
            builderWrapper.setPresenceBuilder(builder);
        } else {
            return new OptionsBuilder(contactUri);
            builderWrapper.setOptionsBuilder(new OptionsBuilder(contactUri));
        }
        return builderWrapper;
    }

    private String createOptionTuple(Cursor cursor) {
@@ -323,16 +341,16 @@ public class EabControllerImpl implements EabController {
        RcsContactPresenceTuple.Builder rcsContactPresenceTupleBuilder =
                new RcsContactPresenceTuple.Builder(status, serviceId, version);
        if (description != null) {
            rcsContactPresenceTupleBuilder.addDescription(description);
            rcsContactPresenceTupleBuilder.setServiceDescription(description);
        }
        if (contactUri != null) {
            rcsContactPresenceTupleBuilder.addContactUri(contactUri);
            rcsContactPresenceTupleBuilder.setContactUri(contactUri);
        }
        if (serviceCapabilities != null) {
            rcsContactPresenceTupleBuilder.addServiceCapabilities(serviceCapabilities);
            rcsContactPresenceTupleBuilder.setServiceCapabilities(serviceCapabilities);
        }
        if (timeStamp != null) {
            rcsContactPresenceTupleBuilder.addTimeStamp(timeStamp);
            rcsContactPresenceTupleBuilder.setTimestamp(timeStamp);
        }

        return rcsContactPresenceTupleBuilder.build();
@@ -459,9 +477,10 @@ public class EabControllerImpl implements EabController {
        int commonId = Integer.valueOf(result.getLastPathSegment());
        Log.d(TAG, "Insert into common table. Id: " + commonId);

        ContentValues[] presenceContent = new ContentValues[capability.getPresenceTuples().size()];
        ContentValues[] presenceContent =
                new ContentValues[capability.getCapabilityTuples().size()];
        for (int i = 0; i < presenceContent.length; i++) {
            RcsContactPresenceTuple tuple = capability.getPresenceTuples().get(i);
            RcsContactPresenceTuple tuple = capability.getCapabilityTuples().get(i);

            // Create new ServiceCapabilities
            ServiceCapabilities serviceCapabilities = tuple.getServiceCapabilities();
+117 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;

import com.android.ims.rcs.uce.eab.EabProvider.ContactColumns;
import com.android.ims.rcs.uce.eab.EabProvider.EabCommonColumns;
import com.android.ims.rcs.uce.eab.EabProvider.PresenceTupleColumns;
import com.android.ims.rcs.uce.util.UceUtils;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * The util to modify the EAB database.
 */
public class EabUtil {

    private static final String LOG_TAG = UceUtils.getLogPrefix() + "EabUtil";

    /**
     * Remove the given EAB contacts from the EAB database.
     */
    public static int removeContactFromEab(int subId, String contacts, Context context) {
        if (TextUtils.isEmpty(contacts)) {
            return -1;
        }
        List<String> contactList = Arrays.stream(contacts.split(",")).collect(Collectors.toList());
        if (contactList == null || contactList.isEmpty()) {
            return -1;
        }
        int count = 0;
        for (String contact : contactList) {
            int contactId = getEabContactId(contact, context);
            if (contactId == -1) {
                continue;
            }
            int commonId = getEabCommonId(contactId, context);
            count += removeContactCapabilities(contactId, commonId, context);
        }
        return count;
    }

    private static int getEabContactId(String contactNumber, Context context) {
        int contactId = -1;
        Cursor cursor = null;
        try {
            cursor = context.getContentResolver().query(
                    EabProvider.CONTACT_URI,
                    new String[] { EabProvider.EabCommonColumns._ID },
                    EabProvider.ContactColumns.PHONE_NUMBER + "=?",
                    new String[] { contactNumber }, null);
            if (cursor != null && cursor.moveToFirst()) {
                contactId = cursor.getInt(cursor.getColumnIndex(EabProvider.ContactColumns._ID));
            }
        } catch (Exception e) {
            Log.w(LOG_TAG, "getEabContactId exception " + e);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return contactId;
    }

    private static int getEabCommonId(int contactId, Context context) {
        int commonId = -1;
        Cursor cursor = null;
        try {
            cursor = context.getContentResolver().query(
                    EabProvider.COMMON_URI,
                    new String[] { EabProvider.EabCommonColumns._ID },
                    EabProvider.EabCommonColumns.EAB_CONTACT_ID + "=?",
                    new String[] { String.valueOf(contactId) }, null);
            if (cursor != null && cursor.moveToFirst()) {
                commonId = cursor.getInt(cursor.getColumnIndex(EabProvider.EabCommonColumns._ID));
            }
        } catch (Exception e) {
            Log.w(LOG_TAG, "getEabCommonId exception " + e);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return commonId;
    }

    private static int removeContactCapabilities(int contactId, int commonId, Context context) {
        int count = 0;
        count = context.getContentResolver().delete(EabProvider.PRESENCE_URI,
                PresenceTupleColumns.EAB_COMMON_ID + "=?", new String[]{String.valueOf(commonId)});
        context.getContentResolver().delete(EabProvider.COMMON_URI,
                EabCommonColumns.EAB_CONTACT_ID + "=?", new String[]{String.valueOf(contactId)});
        context.getContentResolver().delete(EabProvider.CONTACT_URI,
                ContactColumns._ID + "=?", new String[]{String.valueOf(contactId)});
        return count;
    }
}
+55 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.telephony.ims.RcsContactUceCapability.OptionsBuilder;
import android.telephony.ims.RcsContactUceCapability.PresenceBuilder;

/**
 * The wrapper class of the PresenceBuilder and the OptionsBuilder.
 */
public class RcsUceCapabilityBuilderWrapper {
    private final int mMechanism;
    private PresenceBuilder mPresenceBuilder;
    private OptionsBuilder mOptionsBuilder;

    public RcsUceCapabilityBuilderWrapper(int mechanism) {
        mMechanism = mechanism;
    }

    public int getMechanism() {
        return mMechanism;
    }

    public void setPresenceBuilder(@NonNull PresenceBuilder presenceBuilder) {
        mPresenceBuilder = presenceBuilder;
    }

    public @Nullable PresenceBuilder getPresenceBuilder() {
        return mPresenceBuilder;
    }

    public void setOptionsBuilder(@NonNull OptionsBuilder optionsBuilder) {
        mOptionsBuilder = optionsBuilder;
    }

    public @Nullable OptionsBuilder getOptionsBuilder() {
        return mOptionsBuilder;
    }
}
+35 −28

File changed.

Preview size limit exceeded, changes collapsed.

Loading