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

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

Split contact capabilities requests with multiple contacts into multiple individual requests.

Bug: 181987883
Test: atest ImsServiceTest RcsUceAdapterTest
Change-Id: I1abd90c0bbe2c6a27867611f2779e6af6863153c
parent 289f0842
Loading
Loading
Loading
Loading
+247 −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.request;

import android.net.Uri;
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.RcsContactUceCapability;
import android.util.Log;

import com.android.ims.rcs.uce.eab.EabCapabilityResult;
import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
import com.android.ims.rcs.uce.util.UceUtils;
import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * The base class of the UCE request to request the capabilities from the carrier network.
 */
public abstract class CapabilityRequest implements UceRequest {

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

    protected final int mSubId;
    protected final long mTaskId;
    protected final List<Uri> mUriList;
    protected final @UceRequestType int mRequestType;
    protected final RequestManagerCallback mRequestManagerCallback;
    protected final CapabilityRequestResponse mRequestResponse;

    protected volatile long mCoordinatorId;
    protected volatile boolean mIsFinished;
    protected volatile boolean mSkipGettingFromCache;

    public CapabilityRequest(int subId, @UceRequestType int type, RequestManagerCallback callback) {
        mSubId = subId;
        mRequestType = type;
        mUriList = new ArrayList<>();
        mRequestManagerCallback = callback;
        mRequestResponse = new CapabilityRequestResponse();
        mTaskId = UceUtils.generateTaskId();
    }

    @VisibleForTesting
    public CapabilityRequest(int subId, @UceRequestType int type, RequestManagerCallback callback,
            CapabilityRequestResponse requestResponse) {
        mSubId = subId;
        mRequestType = type;
        mUriList = new ArrayList<>();
        mRequestManagerCallback = callback;
        mRequestResponse = requestResponse;
        mTaskId = UceUtils.generateTaskId();
    }

    @Override
    public void setRequestCoordinatorId(long coordinatorId) {
        mCoordinatorId = coordinatorId;
    }

    @Override
    public long getRequestCoordinatorId() {
        return mCoordinatorId;
    }

    @Override
    public long getTaskId() {
        return mTaskId;
    }

    @Override
    public void onFinish() {
        mIsFinished = true;
    }

    @Override
    public void setContactUri(List<Uri> uris) {
        mUriList.addAll(uris);
    }

    public List<Uri> getContactUri() {
        return Collections.unmodifiableList(mUriList);
    }

    /**
     * Set to check if this request should be getting the capabilities from the cache. The flag is
     * set when the request is triggered by the capability polling service. The contacts from the
     * capability polling service are already expired, skip checking from the cache.
     */
    public void setSkipGettingFromCache(boolean skipFromCache) {
        mSkipGettingFromCache = skipFromCache;
    }

    /**
     * Return if the capabilities request should skip getting from the cache. The flag is set when
     * the request is triggered by the capability polling service and the request doesn't need to
     * check the cache again.
     */
    private boolean isSkipGettingFromCache() {
        return mSkipGettingFromCache;
    }

    /**
     * @return A copy of the RequestResponse instance associated with this request.
     */
    public CapabilityRequestResponse getRequestResponse() {
        return mRequestResponse.copy();
    }

    /**
     * Start executing this request.
     */
    @Override
    public void executeRequest() {
        // Return if this request is not allowed to be executed.
        if (!isRequestAllowed()) {
            logd("executeRequest: The request is not allowed.");
            mRequestManagerCallback.notifyRequestError(mCoordinatorId, mTaskId);
            return;
        }

        // Get the capabilities from the cache.
        final List<RcsContactUceCapability> cachedCapList
                = isSkipGettingFromCache() ? Collections.EMPTY_LIST : getCapabilitiesFromCache();
        mRequestResponse.addCachedCapabilities(cachedCapList);

        logd("executeRequest: cached capabilities size=" + cachedCapList.size());

        // Notify that the cached capabilities are updated.
        if (!cachedCapList.isEmpty()) {
            mRequestManagerCallback.notifyCachedCapabilitiesUpdated(mCoordinatorId, mTaskId);
        }

        // Get the rest contacts which need to request capabilities from the network.
        final List<Uri> requestCapUris = getRequestingFromNetworkUris(cachedCapList);

        logd("executeRequest: requestCapUris size=" + requestCapUris.size());

        // Notify that it doesn't need to request capabilities from the network when all the
        // requested capabilities can be retrieved from cache. Otherwise, it needs to request
        // capabilities from the network for those contacts which cannot retrieve capabilities from
        // the cache.
        if (requestCapUris.isEmpty()) {
            mRequestManagerCallback.notifyNoNeedRequestFromNetwork(mCoordinatorId, mTaskId);
        } else {
            requestCapabilities(requestCapUris);
        }
    }

    // Check whether this request is allowed to be executed or not.
    private boolean isRequestAllowed() {
        if (mUriList == null || mUriList.isEmpty()) {
            logw("isRequestAllowed: uri is empty");
            mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            return false;
        }

        if (mIsFinished) {
            logw("isRequestAllowed: This request is finished");
            mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            return false;
        }

        if (mRequestManagerCallback.isRequestForbidden()) {
            logw("isRequestAllowed: The request is forbidden");
            mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_FORBIDDEN);
            return false;
        }
        return true;
    }

    // Get the cached capabilities by the given request type.
    private List<RcsContactUceCapability> getCapabilitiesFromCache() {
        List<EabCapabilityResult> resultList = null;
        if (mRequestType == REQUEST_TYPE_CAPABILITY) {
            resultList = mRequestManagerCallback.getCapabilitiesFromCache(mUriList);
        } else if (mRequestType == REQUEST_TYPE_AVAILABILITY) {
            // Always get the first element if the request type is availability.
            Uri uri = mUriList.get(0);
            EabCapabilityResult eabResult = mRequestManagerCallback.getAvailabilityFromCache(uri);
            resultList = new ArrayList<>();
            resultList.add(eabResult);
        }
        if (resultList == null) {
            return Collections.emptyList();
        }
        return resultList.stream()
                .filter(Objects::nonNull)
                .filter(result -> result.getStatus() == EabCapabilityResult.EAB_QUERY_SUCCESSFUL)
                .map(EabCapabilityResult::getContactCapabilities)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    /**
     * Get the contact uris which cannot retrieve capabilities from the cache.
     * @param cachedCapabilityList The capabilities which are already stored in the cache.
     */
    private List<Uri> getRequestingFromNetworkUris(
            List<RcsContactUceCapability> cachedCapabilityList) {
        return mUriList.stream()
                .filter(uri -> cachedCapabilityList.stream()
                        .noneMatch(cap -> cap.getContactUri().equals(uri)))
                        .collect(Collectors.toList());
    }

    /*
     * Requests capabilities from IMS. The inherited request is required to override this method
     * to define the behavior of requesting capabilities.
     */
    protected abstract void requestCapabilities(List<Uri> requestCapUris);

    protected void logd(String log) {
        Log.d(LOG_TAG, getLogPrefix().append(log).toString());
    }

    protected void logw(String log) {
        Log.w(LOG_TAG, getLogPrefix().append(log).toString());
    }

    protected void logi(String log) {
        Log.i(LOG_TAG, getLogPrefix().append(log).toString());
    }

    private StringBuilder getLogPrefix() {
        StringBuilder builder = new StringBuilder("[");
        builder.append(mSubId).append("][taskId=").append(mTaskId).append("] ");
        return builder;
    }
}
+151 −219

File changed.

Preview size limit exceeded, changes collapsed.

+58 −30
Original line number Diff line number Diff line
@@ -22,11 +22,14 @@ import android.os.RemoteException;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.aidl.IOptionsResponseCallback;
import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.CommandCode;

import com.android.ims.rcs.uce.options.OptionsController;
import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
import com.android.ims.rcs.uce.util.NetworkSipCode;
import com.android.internal.annotations.VisibleForTesting;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -35,7 +38,7 @@ import java.util.Set;
 * The UceRequest to request the capabilities when the OPTIONS mechanism is supported by the
 * network.
 */
public class OptionsRequest extends UceRequest {
public class OptionsRequest extends CapabilityRequest {

    // The result callback of the capabilities request from the IMS service.
    private IOptionsResponseCallback mResponseCallback = new IOptionsResponseCallback.Stub() {
@@ -66,7 +69,6 @@ public class OptionsRequest extends UceRequest {
            CapabilityRequestResponse requestResponse) {
        super(subId, requestType, taskMgrCallback, requestResponse);
        mOptionsController = optionsController;
        logd("OptionsRequest created");
    }

    @Override
@@ -81,8 +83,8 @@ public class OptionsRequest extends UceRequest {
        OptionsController optionsController = mOptionsController;
        if (optionsController == null) {
            logw("requestCapabilities: request is finished");
            mRequestResponse.setErrorCode(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            handleRequestFailed(true);
            mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            mRequestManagerCallback.notifyRequestError(mCoordinatorId, mTaskId);
            return;
        }

@@ -91,8 +93,8 @@ public class OptionsRequest extends UceRequest {
                RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS);
        if (deviceCap == null) {
            logw("requestCapabilities: Cannot get device capabilities");
            mRequestResponse.setErrorCode(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            handleRequestFailed(true);
            mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            mRequestManagerCallback.notifyRequestError(mCoordinatorId, mTaskId);
            return;
        }

@@ -104,29 +106,22 @@ public class OptionsRequest extends UceRequest {
            optionsController.sendCapabilitiesRequest(mContactUri, featureTags, mResponseCallback);
        } catch (RemoteException e) {
            logw("requestCapabilities exception: " + e);
            mRequestResponse.setErrorCode(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            handleRequestFailed(true);
            mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            mRequestManagerCallback.notifyRequestError(mCoordinatorId, mTaskId);
        }
    }

    @VisibleForTesting
    public IOptionsResponseCallback getResponseCallback() {
        return mResponseCallback;
    }

    // Handle the command error callback.
    private void onCommandError(int cmdError) {
    // Receive the command error callback which is triggered by IOptionsResponseCallback.
    private void onCommandError(@CommandCode int cmdError) {
        logd("onCommandError: error code=" + cmdError);
        if (mIsFinished) {
            return;
        }
        mRequestResponse.setCommandError(cmdError);
        int capError = CapabilityRequestResponse.convertCommandErrorToCapabilityError(cmdError);
        mRequestResponse.setErrorCode(capError);
        mRequestManagerCallback.onRequestFailed(mTaskId);
        mRequestManagerCallback.notifyCommandError(mCoordinatorId, mTaskId);
    }

    // Handle the network response callback.
    // Receive the network response callback which is triggered by IOptionsResponseCallback.
    private void onNetworkResponse(int sipCode, String reason, List<String> remoteCaps) {
        logd("onNetworkResponse: sipCode=" + sipCode + ", reason=" + reason
                + ", remoteCap size=" + ((remoteCaps == null) ? "null" : remoteCaps.size()));
@@ -134,18 +129,51 @@ public class OptionsRequest extends UceRequest {
            return;
        }

        // Set the network response code and the remote contact capabilities
        mRequestResponse.setNetworkResponseCode(sipCode, reason);
        mRequestResponse.setRemoteCapabilities(mContactUri, new HashSet<>(remoteCaps));
        if (remoteCaps == null) {
            remoteCaps = Collections.EMPTY_LIST;
        }

        // Notify the request result
        if (mRequestResponse.isNetworkResponseOK()) {
            mRequestManagerCallback.onCapabilityUpdate(mTaskId);
            mRequestManagerCallback.onRequestSuccess(mTaskId);
        } else {
            int capErrorCode = mRequestResponse.getCapabilityErrorFromSipError();
            mRequestResponse.setErrorCode(capErrorCode);
            mRequestManagerCallback.onRequestFailed(mTaskId);
        // Set the all the results to the request response.
        mRequestResponse.setNetworkResponseCode(sipCode, reason);
        mRequestResponse.setRemoteCapabilities(new HashSet<>(remoteCaps));
        RcsContactUceCapability contactCapabilities = getContactCapabilities(mContactUri, sipCode,
                new HashSet<>(remoteCaps));
        mRequestResponse.addUpdatedCapabilities(Collections.singletonList(contactCapabilities));

        // Notify that the network response is received.
        mRequestManagerCallback.notifyNetworkResponse(mCoordinatorId, mTaskId);
    }

    private RcsContactUceCapability getContactCapabilities(Uri contact, int sipCode,
            Set<String> featureTags) {
        int requestResult = RcsContactUceCapability.REQUEST_RESULT_FOUND;
        if (!mRequestResponse.isNetworkResponseOK()) {
            switch (sipCode) {
                case NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT:
                    // Intentional fallthrough
                case NetworkSipCode.SIP_CODE_TEMPORARILY_UNAVAILABLE:
                    requestResult = RcsContactUceCapability.REQUEST_RESULT_NOT_ONLINE;
                    break;
                case NetworkSipCode.SIP_CODE_NOT_FOUND:
                    // Intentional fallthrough
                case NetworkSipCode.SIP_CODE_DOES_NOT_EXIST_ANYWHERE:
                    requestResult = RcsContactUceCapability.REQUEST_RESULT_NOT_FOUND;
                    break;
                default:
                    requestResult = RcsContactUceCapability.REQUEST_RESULT_NOT_FOUND;
                    break;
            }
        }

        RcsContactUceCapability.OptionsBuilder optionsBuilder
                = new RcsContactUceCapability.OptionsBuilder(contact);
        optionsBuilder.setRequestResult(requestResult);
        optionsBuilder.addFeatureTags(featureTags);
        return optionsBuilder.build();
    }

    @VisibleForTesting
    public IOptionsResponseCallback getResponseCallback() {
        return mResponseCallback;
    }
}
+285 −0

File added.

Preview size limit exceeded, changes collapsed.

+181 −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.request;

import static com.android.ims.rcs.uce.util.NetworkSipCode.SIP_CODE_SERVER_INTERNAL_ERROR;
import static com.android.ims.rcs.uce.util.NetworkSipCode.SIP_SERVICE_UNAVAILABLE;

import android.os.RemoteException;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.aidl.IOptionsRequestCallback;

import com.android.ims.rcs.uce.request.RemoteOptionsRequest.RemoteOptResponse;
import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
import com.android.internal.annotations.VisibleForTesting;

import java.util.Collection;

/**
 * Responsible for the manager the remote options request and triggering the callback to notify
 * the result of the request.
 */
public class RemoteOptionsCoordinator extends UceRequestCoordinator {
    /**
     * The builder of the RemoteOptionsCoordinator.
     */
    public static final class Builder {
        RemoteOptionsCoordinator mRemoteOptionsCoordinator;

        public Builder(int subId, Collection<UceRequest> requests, RequestManagerCallback c) {
            mRemoteOptionsCoordinator = new RemoteOptionsCoordinator(subId, requests, c);
        }

        public Builder setOptionsRequestCallback(IOptionsRequestCallback callback) {
            mRemoteOptionsCoordinator.setOptionsRequestCallback(callback);
            return this;
        }

        public RemoteOptionsCoordinator build() {
            return mRemoteOptionsCoordinator;
        }
    }

    /**
     * Different request updated events will create different {@link RequestResult}. Define the
     * interface to get the {@link RequestResult} instance according to the given task ID and
     * {@link RemoteOptResponse}.
     */
    @FunctionalInterface
    private interface RequestResultCreator {
        RequestResult createRequestResult(long taskId, RemoteOptResponse response);
    }

    // The RequestResult creator of the remote options response.
    private static final RequestResultCreator sRemoteResponseCreator = (taskId, response) -> {
        RcsContactUceCapability capability = response.getRcsContactCapability();
        if (capability != null) {
            return RequestResult.createSuccessResult(taskId);
        } else {
            int errorCode = response.getErrorSipCode().orElse(SIP_CODE_SERVER_INTERNAL_ERROR);
            return RequestResult.createFailedResult(taskId, errorCode, 0L);
        }
    };

    // The callback to notify the result of the remote options request.
    private IOptionsRequestCallback mOptionsReqCallback;

    private RemoteOptionsCoordinator(int subId, Collection<UceRequest> requests,
            RequestManagerCallback requestMgrCallback) {
        super(subId, requests, requestMgrCallback);
        logd("RemoteOptionsCoordinator: created");
    }

    public void setOptionsRequestCallback(IOptionsRequestCallback callback) {
        mOptionsReqCallback = callback;
    }

    @Override
    public void onFinish() {
        logd("RemoteOptionsCoordinator: onFinish");
        mOptionsReqCallback = null;
        super.onFinish();
    }

    @Override
    public void onRequestUpdated(long taskId, int event) {
        if (mIsFinished) return;
        RemoteOptionsRequest request = (RemoteOptionsRequest) getUceRequest(taskId);
        if (request == null) {
            logw("onRequestUpdated: Cannot find RemoteOptionsRequest taskId=" + taskId);
            return;
        }

        logd("onRequestUpdated: taskId=" + taskId + ", event=" + REQUEST_EVENT_DESC.get(event));
        switch (event) {
            case REQUEST_UPDATE_REMOTE_REQUEST_DONE:
                handleRemoteRequestDone(request);
                break;
            default:
                logw("onRequestUpdated: invalid event " + event);
                break;
        }

        // End this instance if all the UceRequests in the coordinator are finished.
        checkAndFinishRequestCoordinator();
    }

    private void handleRemoteRequestDone(RemoteOptionsRequest request) {
        // Trigger the options request callback
        RemoteOptResponse response = request.getRemoteOptResponse();
        RcsContactUceCapability capability = response.getRcsContactCapability();
        if (capability != null) {
            boolean isNumberBlocked = response.isNumberBlocked();
            triggerOptionsReqCallback(capability, isNumberBlocked);
        } else {
            int errorCode = response.getErrorSipCode().orElse(SIP_CODE_SERVER_INTERNAL_ERROR);
            String reason = response.getErrorReason().orElse(SIP_SERVICE_UNAVAILABLE);
            triggerOptionsReqWithErrorCallback(errorCode, reason);
        }

        // Finish this request.
        request.onFinish();

        // Remove this request from the activated collection and notify RequestManager.
        Long taskId = request.getTaskId();
        RequestResult requestResult = sRemoteResponseCreator.createRequestResult(taskId, response);
        moveRequestToFinishedCollection(taskId, requestResult);
    }

    private void triggerOptionsReqCallback(RcsContactUceCapability deviceCaps,
            boolean isRemoteNumberBlocked) {
        try {
            mOptionsReqCallback.respondToCapabilityRequest(deviceCaps, isRemoteNumberBlocked);
        } catch (RemoteException e) {
            logw("triggerOptionsReqCallback exception: " + e);
        }
    }

    private void triggerOptionsReqWithErrorCallback(int errorCode, String reason) {
        try {
            mOptionsReqCallback.respondToCapabilityRequestWithError(errorCode, reason);
        } catch (RemoteException e) {
            logw("triggerOptionsReqWithErrorCallback exception: " + e);
        }
    }

    private void checkAndFinishRequestCoordinator() {
        synchronized (mCollectionLock) {
            // Return because there are requests running.
            if (!mActivatedRequests.isEmpty()) {
                return;
            }
            // Notify UceRequestManager to remove this instance from the collection.
            mRequestManagerCallback.notifyRequestCoordinatorFinished(mCoordinatorId);
            logd("checkAndFinishRequestCoordinator: id=" + mCoordinatorId);
        }
    }

    @VisibleForTesting
    public Collection<UceRequest> getActivatedRequest() {
        return mActivatedRequests.values();
    }

    @VisibleForTesting
    public Collection<RequestResult> getFinishedRequest() {
        return mFinishedRequests.values();
    }
}
Loading