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

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

Handle the SIP code 489 BAD EVENT of the UCE request response.

Disallow UCE to send the request when receive the 489(BAD EVENT) from the Presence server.

Bug: 186131340
Test: atest ImsServiceTest RcsUceAdapterTest
Change-Id: I4206e6ff9d68866c5bea73b1006be7a89e728bff
parent 26b1e963
Loading
Loading
Loading
Loading
+78 −122
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.ims.rcs.uce;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.Uri;
import android.os.HandlerThread;
@@ -36,6 +35,7 @@ import android.util.LocalLog;
import android.util.Log;

import com.android.ims.RcsFeatureManager;
import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
import com.android.ims.rcs.uce.eab.EabCapabilityResult;
import com.android.ims.rcs.uce.eab.EabController;
import com.android.ims.rcs.uce.eab.EabControllerImpl;
@@ -54,9 +54,6 @@ import com.android.internal.os.SomeArgs;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -96,25 +93,31 @@ public class UceController {
        RcsContactUceCapability getDeviceCapabilities(@CapabilityMechanism int mechanism);

        /**
         * 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.
         * Refresh the device state. It is called when receive the UCE request response.
         * @param sipCode The SIP code of the request response.
         * @param reason The reason from the network response.
         */
        void updateRequestForbidden(boolean isForbidden, @Nullable Integer errorCode,
                long retryAfterMillis);
        void refreshDeviceState(int sipCode, String reason);

        /**
         * Get the milliseconds need to wait for retry.
         * @return The milliseconds need to wait
         * Reset the device state when then device disallowed state is expired.
         */
        long getRetryAfterMillis();
        void resetDeviceState();

        /**
         * Check if UCE request is forbidden by the network.
         * @return true when the UCE is forbidden by the network
         * Get the current device state to check if the device is allowed to send UCE requests.
         */
        boolean isRequestForbiddenByNetwork();
        DeviceStateResult getDeviceState();

        /**
         * Setup timer to exit device disallowed state.
         */
        void setupResetDeviceStateTimer(long resetAfterSec);

        /**
         * The device state is already reset, clear the timer.
         */
        void clearResetDeviceStateTimer();

        /**
         * The method is called when the given contacts' capabilities are expired and need to be
@@ -283,17 +286,14 @@ public class UceController {
    private SubscribeController mSubscribeController;
    private OptionsController mOptionsController;
    private UceRequestManager mRequestManager;

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

    // The device state to execute UCE requests.
    private UceDeviceState mDeviceState;
    // The cache of the capability request event triggered by ImsService
    private final CachedCapabilityEvent mCachedCapabilityEvent;

    public UceController(Context context, int subId) {
        mSubId = subId;
        mContext = context;
        mServerState = new ServerState();
        mCachedCapabilityEvent = new CachedCapabilityEvent();
        mRcsConnectedState = RCS_STATE_DISCONNECTED;
        logi("create");
@@ -301,14 +301,15 @@ public class UceController {
        initLooper();
        initControllers();
        initRequestManager();
        initUceDeviceState();
    }

    @VisibleForTesting
    public UceController(Context context, int subId, ServerState serverState,
    public UceController(Context context, int subId, UceDeviceState deviceState,
            ControllerFactory controllerFactory, RequestManagerFactory requestManagerFactory) {
        mSubId = subId;
        mContext = context;
        mServerState = serverState;
        mDeviceState = deviceState;
        mControllerFactory = controllerFactory;
        mRequestManagerFactory = requestManagerFactory;
        mCachedCapabilityEvent = new CachedCapabilityEvent();
@@ -341,6 +342,11 @@ public class UceController {
        mRequestManager.setOptionsController(mOptionsController);
    }

    private void initUceDeviceState() {
        mDeviceState = new UceDeviceState(mSubId, mContext, mCtrlCallback);
        mDeviceState.checkSendResetDeviceStateTimer();
    }

    /**
     * The RcsFeature has been connected to the framework. This method runs on main thread.
     */
@@ -376,8 +382,6 @@ public class UceController {
            mRcsFeatureManager.removeCapabilityEventCallback(mCapabilityEventListener);
            mRcsFeatureManager = null;
        }
        // Reset Service specific state
        mServerState.updateRequestForbidden(false, null, 0L);
        // Notify each controllers that RCS is disconnected.
        mEabController.onRcsDisconnected();
        mPublishController.onRcsDisconnected();
@@ -459,19 +463,28 @@ public class UceController {
        }

        @Override
        public void updateRequestForbidden(boolean isForbidden, @Nullable Integer errorCode,
                long retryAfterMillis) {
            mServerState.updateRequestForbidden(isForbidden, errorCode, retryAfterMillis);
        public void refreshDeviceState(int sipCode, String reason) {
            mDeviceState.refreshDeviceState(sipCode, reason);
        }

        @Override
        public void resetDeviceState() {
            mDeviceState.resetDeviceState();
        }

        @Override
        public DeviceStateResult getDeviceState() {
            return mDeviceState.getCurrentState();
        }

        @Override
        public long getRetryAfterMillis() {
            return mServerState.getRetryAfterMillis();
        public void setupResetDeviceStateTimer(long resetAfterSec) {
            mPublishController.setupResetDeviceStateTimer(resetAfterSec);
        }

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

        @Override
@@ -553,19 +566,21 @@ public class UceController {
            return;
        }

        // 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, errorCode=" + errorCode
                    + ", retryAfter=" + retryAfter);
            errorCode = (errorCode != null) ? errorCode : RcsUceAdapter.ERROR_FORBIDDEN;
            c.onError(errorCode, retryAfter);
        // Return if the device is not allowed to execute UCE requests.
        DeviceStateResult deviceStateResult = mDeviceState.getCurrentState();
        if (deviceStateResult.isRequestForbidden()) {
            int deviceState = deviceStateResult.getDeviceState();
            int errorCode = deviceStateResult.getErrorCode()
                    .orElse(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            long retryAfterMillis = deviceStateResult.getRequestRetryAfterMillis();
            logw("requestCapabilities: The device is disallowed, deviceState= " + deviceState +
                    ", errorCode=" + errorCode + ", retryAfterMillis=" + retryAfterMillis);
            c.onError(errorCode, retryAfterMillis);
            return;
        }

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

@@ -590,14 +605,16 @@ public class UceController {
            return;
        }

        // 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, errorCode=" + errorCode
                + ", retryAfter=" + retryAfter);
            errorCode = (errorCode != null) ? errorCode : RcsUceAdapter.ERROR_FORBIDDEN;
            c.onError(errorCode, retryAfter);
        // Return if the device is not allowed to execute UCE requests.
        DeviceStateResult deviceStateResult = mDeviceState.getCurrentState();
        if (deviceStateResult.isRequestForbidden()) {
            int deviceState = deviceStateResult.getDeviceState();
            int errorCode = deviceStateResult.getErrorCode()
                    .orElse(RcsUceAdapter.ERROR_GENERIC_FAILURE);
            long retryAfterMillis = deviceStateResult.getRequestRetryAfterMillis();
            logw("requestAvailability: The device is disallowed, deviceState= " + deviceState +
                    ", errorCode=" + errorCode + ", retryAfterMillis=" + retryAfterMillis);
            c.onError(errorCode, retryAfterMillis);
            return;
        }

@@ -611,8 +628,8 @@ 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);
        // Reset the device state when the service triggers to publish the device's capabilities
        mDeviceState.resetDeviceState();
        // Send the publish request.
        mPublishController.requestPublishCapabilitiesFromService(triggerType);
    }
@@ -702,6 +719,16 @@ public class UceController {
        return mPublishController.getLastPidfXml();
    }

    /**
     * Remove the device disallowed state.
     * <p>
     * Used for testing ONLY.
     */
    public void removeRequestDisallowedStatus() {
        logd("removeRequestDisallowedStatus");
        mDeviceState.resetDeviceState();
    }

    /**
     * Get the subscription ID.
     */
@@ -728,77 +755,6 @@ public class UceController {
        return mRcsConnectedState == RCS_STATE_CONNECTED;
    }

    /**
     * The internal class to store the server state which sent from the network. It will help to
     * check if the network allows the UCE request.
     */
    @VisibleForTesting
    public static class ServerState {
        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 mServerStateLock = new Object();

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

        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.i(LOG_TAG, "updateRequestForbidden: isForbidden=" + mIsForbidden
                        + ", errorCode=" + mForbiddenErrorCode + ", time=" + mAllowedTimestamp);
            }
        }

        public boolean isRequestForbidden() {
            synchronized (mServerStateLock) {
                if (mIsForbidden && mAllowedTimestamp != null) {
                    return Instant.now().isBefore(mAllowedTimestamp);
                }
                return mIsForbidden;
            }
        }

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

        public long getRetryAfterMillis() {
            synchronized (mServerStateLock) {
                if (!mIsForbidden || mAllowedTimestamp == null) {
                    return 0L;
                }
                Duration duration = Duration.between(Instant.now(), mAllowedTimestamp);
                long retryAfterMillis = duration.toMillis();
                if (retryAfterMillis < 0) {
                    return 0L;
                }
                return retryAfterMillis;
            }
        }
    }

    public void dump(PrintWriter printWriter) {
        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
        pw.println("UceController" + "[subId: " + mSubId + "]:");
+371 −0

File added.

Preview size limit exceeded, changes collapsed.

+15 −0
Original line number Diff line number Diff line
@@ -136,6 +136,11 @@ public interface PublishController extends ControllerBase {
         * Update the value of the publish throttle.
         */
        void updatePublishThrottle(int value);

        /**
         * Update the device state with the publish request result.
         */
        void refreshDeviceState(int SipCode, String reason);
    }

    /**
@@ -202,6 +207,16 @@ public interface PublishController extends ControllerBase {
     */
    void unregisterPublishStateCallback(@NonNull IRcsUcePublishStateCallback c);

    /**
     * Setup the timer to reset the device state.
     */
    void setupResetDeviceStateTimer(long resetAfterSec);

    /**
     * Clear the reset device state timer.
     */
    void clearResetDeviceStateTimer();

    /**
     * Dump the state of this PublishController to the printWriter.
     */
+75 −7
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.util.Log;

import com.android.ims.RcsFeatureManager;
import com.android.ims.rcs.uce.UceController.UceControllerCallback;
import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
import com.android.ims.rcs.uce.util.UceUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
@@ -51,6 +52,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * The implementation of PublishController.
@@ -270,6 +272,18 @@ public class PublishControllerImpl implements PublishController {
        }
    }

    @Override
    public void setupResetDeviceStateTimer(long resetAfterSec) {
        logd("setupResetDeviceStateTimer: resetAfterSec=" + resetAfterSec);
        mPublishHandler.sendResetDeviceStateTimerMessage(resetAfterSec);
    }

    @Override
    public void clearResetDeviceStateTimer() {
        logd("clearResetDeviceStateTimer");
        mPublishHandler.clearResetDeviceStateTimer();
    }

    // Clear all the publish state callbacks since the publish controller instance is destroyed.
    private void clearPublishStateCallbacks() {
        synchronized (mPublishStateLock) {
@@ -346,6 +360,11 @@ public class PublishControllerImpl implements PublishController {
                    logd("updatePublishThrottle: value=" + value);
                    mPublishProcessor.updatePublishThrottle(value);
                }

                @Override
                public void refreshDeviceState(int sipCode, String reason) {
                    mUceCtrlCallback.refreshDeviceState(sipCode, reason);
                }
            };

    /**
@@ -369,6 +388,7 @@ public class PublishControllerImpl implements PublishController {
        private static final int MSG_REQUEST_CMD_ERROR = 9;
        private static final int MSG_REQUEST_NETWORK_RESPONSE = 10;
        private static final int MSG_REQUEST_CANCELED = 11;
        private static final int MSG_RESET_DEVICE_STATE = 12;

        private final WeakReference<PublishControllerImpl> mPublishControllerRef;

@@ -444,6 +464,10 @@ public class PublishControllerImpl implements PublishController {
                    long taskId = (Long) message.obj;
                    publishCtrl.handleRequestCanceledMessage(taskId);
                    break;

                case MSG_RESET_DEVICE_STATE:
                    publishCtrl.handleResetDeviceStateMessage();
                    break;
            }
        }

@@ -611,6 +635,28 @@ public class PublishControllerImpl implements PublishController {
            removeMessages(MSG_REQUEST_CANCELED);
        }

        public void sendResetDeviceStateTimerMessage(long resetAfterSec) {
            PublishControllerImpl publishCtrl = mPublishControllerRef.get();
            if (publishCtrl == null) {
                return;
            }
            if (publishCtrl.mIsDestroyedFlag) return;
            // Remove old timer and setup the new timer.
            removeMessages(MSG_RESET_DEVICE_STATE);
            Message message = obtainMessage();
            message.what = MSG_RESET_DEVICE_STATE;
            sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(resetAfterSec));
        }

        public void clearResetDeviceStateTimer() {
            PublishControllerImpl publishCtrl = mPublishControllerRef.get();
            if (publishCtrl == null) {
                return;
            }
            if (publishCtrl.mIsDestroyedFlag) return;
            removeMessages(MSG_RESET_DEVICE_STATE);
        }

        private static Map<Integer, String> EVENT_DESCRIPTION = new HashMap<>();
        static {
            EVENT_DESCRIPTION.put(MSG_RCS_CONNECTED, "RCS_CONNECTED");
@@ -624,6 +670,7 @@ public class PublishControllerImpl implements PublishController {
            EVENT_DESCRIPTION.put(MSG_REQUEST_CMD_ERROR, "REQUEST_CMD_ERROR");
            EVENT_DESCRIPTION.put(MSG_REQUEST_NETWORK_RESPONSE, "REQUEST_NETWORK_RESPONSE");
            EVENT_DESCRIPTION.put(MSG_REQUEST_CANCELED, "REQUEST_CANCELED");
            EVENT_DESCRIPTION.put(MSG_RESET_DEVICE_STATE, "RESET_DEVICE_STATE");
        }
    }

@@ -636,11 +683,21 @@ public class PublishControllerImpl implements PublishController {
            logd("isPublishRequestAllowed: capability presence uce is not enabled.");
            return false;
        }

        // The first PUBLISH request is required to be triggered from the service.
        if (!mReceivePublishFromService) {
            logd("isPublishRequestAllowed: Have not received the first PUBLISH from the service.");
            return false;
        }

        // Check whether the device state is not allowed to execute the PUBLISH request.
        DeviceStateResult deviceState = mUceCtrlCallback.getDeviceState();
        if (deviceState.isRequestForbidden()) {
            logd("isPublishRequestAllowed: The device state is disallowed. "
                    + deviceState.getDeviceState());
            return false;
        }

        // Check whether there is already a publish request running or not. When the running
        // request is finished and there is a pending request, it will send a new request.
        if (mPublishProcessor.isPublishingNow()) {
@@ -780,16 +837,22 @@ public class PublishControllerImpl implements PublishController {

    private void handleRequestPublishMessage(@PublishTriggerType int type) {
        if (mIsDestroyedFlag) return;
        if (mUceCtrlCallback.isRequestForbiddenByNetwork()) {
            logd("handleRequestPublishMessage: The network forbids UCE requests: type=" + type);
            return;
        }

        logd("handleRequestPublishMessage: type=" + type);

        // Set the flag if the publish is triggered by the ImsService.
        if (type == PublishController.PUBLISH_TRIGGER_SERVICE && !mReceivePublishFromService) {
        // Set the PUBLISH FROM SERVICE flag and reset the device state if the PUBLISH request is
        // triggered by the ImsService.
        if (type == PublishController.PUBLISH_TRIGGER_SERVICE) {
            // Set the flag
            if (!mReceivePublishFromService) {
                mReceivePublishFromService = true;
            }
            // Reset device state
            DeviceStateResult deviceState = mUceCtrlCallback.getDeviceState();
            if (deviceState.isRequestForbidden()) {
                mUceCtrlCallback.resetDeviceState();
            }
        }

        // Set the pending flag and return if the request is not allowed.
        if (!isPublishRequestAllowed()) {
@@ -824,6 +887,11 @@ public class PublishControllerImpl implements PublishController {
        mPublishProcessor.cancelPublishRequest(taskId);
    }

    private void handleResetDeviceStateMessage() {
        if(mIsDestroyedFlag) return;
        mUceCtrlCallback.resetDeviceState();
    }

    @VisibleForTesting
    public void setPublishStateCallback(RemoteCallbackList<IRcsUcePublishStateCallback> list) {
        mPublishStateCallbacks = list;
+6 −0
Original line number Diff line number Diff line
@@ -337,6 +337,12 @@ public class PublishProcessor {
        String pidfXml = requestResponse.getPidfXml();
        mPublishCtrlCallback.updatePublishRequestResult(publishState, responseTime, pidfXml);

        // Refresh the device state with the publish request result.
        requestResponse.getResponseSipCode().ifPresent(sipCode -> {
            String reason = requestResponse.getResponseReason().orElse("");
            mPublishCtrlCallback.refreshDeviceState(sipCode, reason);
        });

        // Finish the request and check if there is pending request.
        setRequestEnded(requestResponse);
        checkAndSendPendingRequest();
Loading