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

Commit 8e2ea410 authored by James.cf Lin's avatar James.cf Lin Committed by Automerger Merge Worker
Browse files

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

Split contact capabilities requests with multiple contacts into multiple individual requests. am: a7853f67 am: e6c30b95

Original change: https://android-review.googlesource.com/c/platform/frameworks/opt/net/ims/+/1657619

Change-Id: Iec8ce1173804e9d460951b03edde671e614939ba
parents de555928 e6c30b95
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