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

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

Check the the reason of the subscribe request to deter whether client should retry or not.

Bug: 181041161
Test: atest RcsUceAdapterTest
Merged-In: I2576cd4601a61944b3c8f4f62096622c530d855a
Change-Id: I2576cd4601a61944b3c8f4f62096622c530d855a
parent 0a9ad5e0
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -181,6 +181,14 @@ public class CapabilityRequestResponse {
        mRetryAfterMillis = Optional.of(retryAfterMillis);
    }

    /**
     * @return The reason of terminating the subscription request. empty string if it has not
     * been given.
     */
    public synchronized String getTerminatedReason() {
        return mTerminatedReason.orElse("");
    }

    /**
     * @return Return the retryAfterMillis, 0L if the value is not present.
     */
+13 −4
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.telephony.ims.aidl.IRcsUceControllerCallback;

import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
import com.android.ims.rcs.uce.presence.pidfparser.PidfParserUtils;
import com.android.ims.rcs.uce.request.SubscriptionTerminatedHelper.TerminatedResult;
import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
import com.android.internal.annotations.VisibleForTesting;

@@ -115,10 +116,18 @@ public class SubscribeRequestCoordinator extends UceRequestCoordinator {
    // The RequestResult creator of the request terminated.
    private static final RequestResultCreator sTerminatedCreator = (taskId, response,
            requestMgrCallback) -> {
        // Check the given terminated reason to determine whether clients should retry or not.
        TerminatedResult terminatedResult = SubscriptionTerminatedHelper.getAnalysisResult(
                response.getTerminatedReason(), response.getRetryAfterMillis());
        if (terminatedResult.getErrorCode().isPresent()) {
            // If the terminated error code is present, it means that the request is failed.
            int errorCode = terminatedResult.getErrorCode().get();
            long terminatedRetry = terminatedResult.getRetryAfterMillis();
            return RequestResult.createFailedResult(taskId, errorCode, terminatedRetry);
        } else if (!response.isNetworkResponseOK() || response.getRetryAfterMillis() > 0L) {
            // If the network response is failed or the retryAfter is not 0, this request is failed.
            long retryAfterMillis = response.getRetryAfterMillis();
            int errorCode = CapabilityRequestResponse.getCapabilityErrorFromSipCode(response);
        // If the network response is failed or the retryAfter is not zero, this request is failed.
        if (!response.isNetworkResponseOK() || retryAfterMillis > 0L) {
            return RequestResult.createFailedResult(taskId, errorCode, retryAfterMillis);
        } else {
            return RequestResult.createSuccessResult(taskId);
+165 −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.telephony.ims.RcsUceAdapter;
import android.telephony.ims.RcsUceAdapter.ErrorCode;
import android.text.TextUtils;
import android.util.Log;

import com.android.ims.rcs.uce.util.UceUtils;

import java.util.Optional;

/**
 * The helper class to analyze the result of the callback onTerminated to determine whether the
 * subscription request should be retried or not.
 */
public class SubscriptionTerminatedHelper {

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

    // The terminated reasons defined in RFC 3265 3.2.4
    private static final String REASON_DEACTIVATED = "deactivated";
    private static final String REASON_PROBATION = "probation";
    private static final String REASON_REJECTED = "rejected";
    private static final String REASON_TIMEOUT = "timeout";
    private static final String REASON_GIVEUP = "giveup";
    private static final String REASON_NORESOURCE = "noresource";

    /**
     * The analysis result of the callback onTerminated.
     */
    static class TerminatedResult {
        private final @ErrorCode Optional<Integer> mErrorCode;
        private final long mRetryAfterMillis;

        public TerminatedResult(@ErrorCode Optional<Integer> errorCode, long retryAfterMillis) {
            mErrorCode = errorCode;
            mRetryAfterMillis = retryAfterMillis;
        }

        /**
         * @return the error code when the request is failed. Optional.empty if the request is
         * successful.
         */
        public Optional<Integer> getErrorCode() {
            return mErrorCode;
        }

        public long getRetryAfterMillis() {
            return mRetryAfterMillis;
        }

        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("TerminatedResult: ")
                    .append("errorCode=").append(mErrorCode)
                    .append(", retryAfterMillis=").append(mRetryAfterMillis);
            return builder.toString();
        }
    }

    /**
     * According to the RFC 3265, Check the given reason to see whether clients should retry the
     * subscribe request.
     * <p>
     * See RFC 3265 3.2.4 for the detail.
     *
     * @param reason The reason why the subscribe request is terminated. The reason is given by the
     * network and it could be empty.
     * @param retryAfterMillis How long should clients wait before retrying.
     */
    public static TerminatedResult getAnalysisResult(String reason, long retryAfterMillis) {
        TerminatedResult result = null;
        if (TextUtils.isEmpty(reason)) {
            /*
             * When the value of retryAfterMillis is larger then zero, the client should retry.
             */
            if (retryAfterMillis > 0L) {
                result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_GENERIC_FAILURE),
                        retryAfterMillis);
            }
        } else if (REASON_DEACTIVATED.equalsIgnoreCase(reason)) {
            /*
             * When the reason is "deactivated", clients should retry immediately.
             */
            long retry = getRequestRetryAfterMillis(retryAfterMillis);
            result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_GENERIC_FAILURE), retry);
        } else if (REASON_PROBATION.equalsIgnoreCase(reason)) {
            /*
             * When the reason is "probation", it means that the subscription has been terminated,
             * but the client should retry at some later time.
             */
            long retry = getRequestRetryAfterMillis(retryAfterMillis);
            result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_GENERIC_FAILURE), retry);
        } else if (REASON_REJECTED.equalsIgnoreCase(reason)) {
            /*
             * When the reason is "rejected", it means that the subscription has been terminated
             * due to chang in authorization policy. Clients should NOT retry.
             */
            result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_NOT_AUTHORIZED), 0L);
        } else if (REASON_TIMEOUT.equalsIgnoreCase(reason) && retryAfterMillis > 0L) {
            /*
             * The subscription has been terminated because it was not refreshed before it expired.
             * The request completes successfully when the retryAfter is not set. Otherwise, the
             * request should retry if the retryAfter is set.
             */
            long retry = getRequestRetryAfterMillis(retryAfterMillis);
            result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_REQUEST_TIMEOUT), retry);
        } else if (REASON_GIVEUP.equalsIgnoreCase(reason)) {
            /*
             * The subscription has been terminated because the notifier could no obtain
             * authorization in a timely fashion. Clients could retry the subscribe request.
             */
            long retry = getRequestRetryAfterMillis(retryAfterMillis);
            result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_NOT_AUTHORIZED), retry);
        } else if (REASON_NORESOURCE.equalsIgnoreCase(reason)) {
            /*
             * The subscription has been terminated because the resource is no longer exists.
             * Clients should NOT retry.
             */
            result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_NOT_FOUND), 0L);
        } else if (retryAfterMillis > 0L) {
            /*
             * Even if the reason is not listed above, clients should retry the request as long as
             * the value of retry is non-zero.
             */
            long retry = getRequestRetryAfterMillis(retryAfterMillis);
            result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_GENERIC_FAILURE), retry);
        }

        // The request should be successful. when the terminated is not in the above cases
        if (result == null) {
            result = new TerminatedResult(Optional.empty(), 0L);
        }

        Log.d(LOG_TAG, "getAnalysisResult: reason=" + reason + ", retry=" + retryAfterMillis +
                ", " + result);
        return result;
    }

    /*
     * Get the appropriated retryAfterMillis for the subscribe request.
     */
    private static long getRequestRetryAfterMillis(long retryAfterMillis) {
        // Return the minimum retry after millis if the given retryAfterMillis is less than the
        // minimum value.
        long minRetryAfterMillis = UceUtils.getMinimumRequestRetryAfterMillis();
        return (retryAfterMillis < minRetryAfterMillis) ? minRetryAfterMillis : retryAfterMillis;
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ public class UceUtils {
    private static final long DEFAULT_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC =
            TimeUnit.DAYS.toSeconds(30);
    private static final long DEFAULT_REQUEST_RETRY_INTERVAL_MS = TimeUnit.MINUTES.toMillis(20);
    private static final long DEFAULT_MINIMUM_REQUEST_RETRY_AFTER_MS = TimeUnit.SECONDS.toMillis(3);

    // The task ID of the UCE request
    private static long TASK_ID = 0L;
@@ -341,4 +342,11 @@ public class UceUtils {
        builder.append(",").append(exitStateTimeMillis);   // exit state time
        return builder.toString();
    }

    /**
     * Get the minimum value of the capabilities request retry after.
     */
    public static long getMinimumRequestRetryAfterMillis() {
        return DEFAULT_MINIMUM_REQUEST_RETRY_AFTER_MS;
    }
}