[automerger skipped] Merge Android 24Q2 Release (ab/11526283) to aosp-main-future am: f586b15694 -s ours

am skip reason: Merged-In Ice37eea8315c33013963a0db5779b0d1849a1974 with SHA-1 ba68f46b16 is already in history

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

Change-Id: Icf3b65d91f2875806cbf5af305793fcca5e72192
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/src/java/com/android/ims/rcs/uce/UceController.java b/src/java/com/android/ims/rcs/uce/UceController.java
index aeab061..ecf924b 100644
--- a/src/java/com/android/ims/rcs/uce/UceController.java
+++ b/src/java/com/android/ims/rcs/uce/UceController.java
@@ -52,6 +52,7 @@
 import com.android.ims.rcs.uce.util.UceUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -150,11 +151,12 @@
     @VisibleForTesting
     public interface RequestManagerFactory {
         UceRequestManager createRequestManager(Context context, int subId, Looper looper,
-                UceControllerCallback callback);
+                UceControllerCallback callback, FeatureFlags featureFlags);
     }
 
-    private RequestManagerFactory mRequestManagerFactory = (context, subId, looper, callback) ->
-            new UceRequestManager(context, subId, looper, callback);
+    private RequestManagerFactory mRequestManagerFactory =
+            (context, subId, looper, callback, featureFlags) ->
+                    new UceRequestManager(context, subId, looper, callback, featureFlags);
 
     /**
      * Used to inject Controller instances for testing.
@@ -345,12 +347,14 @@
     private UceDeviceState mDeviceState;
     // The cache of the capability request event triggered by ImsService
     private final CachedCapabilityEvent mCachedCapabilityEvent;
+    private final FeatureFlags mFeatureFlags;
 
-    public UceController(Context context, int subId) {
+    public UceController(Context context, int subId, FeatureFlags featureFlags) {
         mSubId = subId;
         mContext = context;
         mCachedCapabilityEvent = new CachedCapabilityEvent();
         mRcsConnectedState = RCS_STATE_DISCONNECTED;
+        mFeatureFlags = featureFlags;
         logi("create");
 
         initLooper();
@@ -361,7 +365,8 @@
 
     @VisibleForTesting
     public UceController(Context context, int subId, UceDeviceState deviceState,
-            ControllerFactory controllerFactory, RequestManagerFactory requestManagerFactory) {
+            ControllerFactory controllerFactory, RequestManagerFactory requestManagerFactory,
+            FeatureFlags featureFlags) {
         mSubId = subId;
         mContext = context;
         mDeviceState = deviceState;
@@ -369,6 +374,7 @@
         mRequestManagerFactory = requestManagerFactory;
         mCachedCapabilityEvent = new CachedCapabilityEvent();
         mRcsConnectedState = RCS_STATE_DISCONNECTED;
+        mFeatureFlags = featureFlags;
         initLooper();
         initControllers();
         initRequestManager();
@@ -392,7 +398,7 @@
 
     private void initRequestManager() {
         mRequestManager = mRequestManagerFactory.createRequestManager(mContext, mSubId, mLooper,
-                mCtrlCallback);
+                mCtrlCallback, mFeatureFlags);
         mRequestManager.setSubscribeController(mSubscribeController);
         mRequestManager.setOptionsController(mOptionsController);
     }
@@ -474,6 +480,7 @@
         mPublishController.onCarrierConfigChanged();
         mSubscribeController.onCarrierConfigChanged();
         mOptionsController.onCarrierConfigChanged();
+        mRequestManager.onCarrierConfigChanged();
     }
 
     private void handleCachedCapabilityEvent() {
diff --git a/src/java/com/android/ims/rcs/uce/request/CapabilityRequest.java b/src/java/com/android/ims/rcs/uce/request/CapabilityRequest.java
index 17cec90..248b079 100644
--- a/src/java/com/android/ims/rcs/uce/request/CapabilityRequest.java
+++ b/src/java/com/android/ims/rcs/uce/request/CapabilityRequest.java
@@ -51,6 +51,8 @@
     protected volatile long mCoordinatorId;
     protected volatile boolean mIsFinished;
     protected volatile boolean mSkipGettingFromCache;
+    protected int mCurrentRetryCount;
+    protected boolean mRetryEnabled;
 
     public CapabilityRequest(int subId, @UceRequestType int type, RequestManagerCallback callback) {
         mSubId = subId;
@@ -59,6 +61,8 @@
         mRequestManagerCallback = callback;
         mRequestResponse = new CapabilityRequestResponse();
         mTaskId = UceUtils.generateTaskId();
+        mCurrentRetryCount = 0;
+        mRetryEnabled = false;
     }
 
     @VisibleForTesting
@@ -70,6 +74,8 @@
         mRequestManagerCallback = callback;
         mRequestResponse = requestResponse;
         mTaskId = UceUtils.generateTaskId();
+        mCurrentRetryCount = 0;
+        mRetryEnabled = false;
     }
 
     @Override
@@ -185,6 +191,22 @@
         }
     }
 
+    public int getRetryCount() {
+        return mCurrentRetryCount;
+    }
+
+    public void setRetryCount(int retryCount) {
+        mCurrentRetryCount = retryCount;
+    }
+
+    public boolean isRetryEnabled() {
+        return mRetryEnabled;
+    }
+
+    public void setRetryEnabled(boolean enabled) {
+        mRetryEnabled = enabled;
+    }
+
     // Check whether this request is allowed to be executed or not.
     private boolean isRequestAllowed() {
         if (mUriList == null || mUriList.isEmpty()) {
diff --git a/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java b/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java
index a306eb4..b3b4d14 100644
--- a/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java
+++ b/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java
@@ -17,14 +17,15 @@
 package com.android.ims.rcs.uce.request;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.net.Uri;
 import android.os.RemoteException;
+import android.telephony.CarrierConfigManager;
 import android.telephony.ims.RcsContactTerminatedReason;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.ISubscribeResponseCallback;
+import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.CommandCode;
 
 import com.android.ims.rcs.uce.eab.EabCapabilityResult;
@@ -34,6 +35,7 @@
 import com.android.ims.rcs.uce.presence.subscribe.SubscribeController;
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -46,6 +48,7 @@
  * network.
  */
 public class SubscribeRequest extends CapabilityRequest {
+    public static final int MAX_RETRY_COUNT = 1;
 
     // The result callback of the capabilities request from IMS service.
     private final ISubscribeResponseCallback mResponseCallback =
@@ -73,20 +76,24 @@
             };
 
     private SubscribeController mSubscribeController;
+    private final FeatureFlags mFeatureFlags;
 
     public SubscribeRequest(int subId, @UceRequestType int requestType,
-            RequestManagerCallback taskMgrCallback, SubscribeController subscribeController) {
+            RequestManagerCallback taskMgrCallback, SubscribeController subscribeController,
+            FeatureFlags featureFlags) {
         super(subId, requestType, taskMgrCallback);
         mSubscribeController = subscribeController;
+        mFeatureFlags = featureFlags;
         logd("SubscribeRequest created");
     }
 
     @VisibleForTesting
     public SubscribeRequest(int subId, @UceRequestType int requestType,
             RequestManagerCallback taskMgrCallback, SubscribeController subscribeController,
-            CapabilityRequestResponse requestResponse) {
+            CapabilityRequestResponse requestResponse, FeatureFlags featureFlags) {
         super(subId, requestType, taskMgrCallback, requestResponse);
         mSubscribeController = subscribeController;
+        mFeatureFlags = featureFlags;
     }
 
     @Override
@@ -126,6 +133,26 @@
             logw("onCommandError: request is already finished");
             return;
         }
+
+        if (mFeatureFlags.enableSipSubscribeRetry()
+                && cmdError == RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT
+                && isRetryEnabled()) {
+            int retryCount = getRetryCount();
+            if (retryCount < MAX_RETRY_COUNT) {
+                CapabilityRequest request = new SubscribeRequest(mSubId, mRequestType,
+                        mRequestManagerCallback, mSubscribeController, mFeatureFlags);
+                request.setContactUri(getContactUri());
+                request.setRetryCount(retryCount + 1);
+                // Do not use the cached capability to retry
+                request.setSkipGettingFromCache(true);
+
+                mRequestManagerCallback.sendSubscribeRetryRequest(request);
+                logd("onCommandError: Retry subscribe request");
+            } else {
+                logd("onCommandError: Reached max retry");
+            }
+        }
+
         mRequestResponse.setCommandError(cmdError);
         mRequestManagerCallback.notifyCommandError(mCoordinatorId, mTaskId);
     }
diff --git a/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java b/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java
index 26143f9..4232704 100644
--- a/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java
+++ b/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java
@@ -578,8 +578,10 @@
      */
     private void triggerCapabilitiesReceivedCallback(List<RcsContactUceCapability> capList) {
         try {
-            logd("triggerCapabilitiesCallback: size=" + capList.size());
-            mCapabilitiesCallback.onCapabilitiesReceived(capList);
+            if (mCapabilitiesCallback != null) {
+                logd("triggerCapabilitiesCallback: size=" + capList.size());
+                mCapabilitiesCallback.onCapabilitiesReceived(capList);
+            }
         } catch (RemoteException e) {
             logw("triggerCapabilitiesCallback exception: " + e);
         } finally {
@@ -592,8 +594,10 @@
      */
     private void triggerCompletedCallback(@Nullable SipDetails details) {
         try {
-            logd("triggerCompletedCallback");
-            mCapabilitiesCallback.onComplete(details);
+            if (mCapabilitiesCallback != null) {
+                logd("triggerCompletedCallback");
+                mCapabilitiesCallback.onComplete(details);
+            }
         } catch (RemoteException e) {
             logw("triggerCompletedCallback exception: " + e);
         } finally {
@@ -607,8 +611,11 @@
     private void triggerErrorCallback(int errorCode, long retryAfterMillis,
             @Nullable SipDetails details) {
         try {
-            logd("triggerErrorCallback: errorCode=" + errorCode + ", retry=" + retryAfterMillis);
-            mCapabilitiesCallback.onError(errorCode, retryAfterMillis, details);
+            if (mCapabilitiesCallback != null) {
+                logd("triggerErrorCallback: errorCode=" + errorCode
+                        + ", retry=" + retryAfterMillis);
+                mCapabilitiesCallback.onError(errorCode, retryAfterMillis, details);
+            }
         } catch (RemoteException e) {
             logw("triggerErrorCallback exception: " + e);
         } finally {
diff --git a/src/java/com/android/ims/rcs/uce/request/UceRequest.java b/src/java/com/android/ims/rcs/uce/request/UceRequest.java
index 197f4ba..c2b0160 100644
--- a/src/java/com/android/ims/rcs/uce/request/UceRequest.java
+++ b/src/java/com/android/ims/rcs/uce/request/UceRequest.java
@@ -70,4 +70,30 @@
      * Execute the request.
      */
     void executeRequest();
+
+    /**
+     * @return The number of retries for the current request.
+     */
+    default int getRetryCount() {
+        return 0;
+    };
+
+    /**
+     * Set the number of retries for the current request.
+     */
+    default void setRetryCount(int retries) {
+    };
+
+    /**
+     * @return The whether to allow request retry.
+     */
+    default boolean isRetryEnabled() {
+        return false;
+    };
+
+    /**
+     * Set whether to allow request retry.
+     */
+    default void setRetryEnabled(boolean enabled) {
+    };
 }
diff --git a/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java b/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java
index 8ff39c1..8955ec4 100644
--- a/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java
+++ b/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java
@@ -16,12 +16,16 @@
 
 package com.android.ims.rcs.uce.request;
 
+import static com.android.ims.rcs.uce.request.UceRequest.REQUEST_TYPE_CAPABILITY;
+
 import android.content.Context;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsContactUceCapability.CapabilityMechanism;
@@ -46,6 +50,7 @@
 import com.android.ims.rcs.uce.util.UceUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -107,6 +112,11 @@
          * @return true if the given phone number is blocked by the network.
          */
         boolean isNumberBlocked(Context context, String phoneNumber);
+
+        /**
+         * @return subscribe retry duration in milliseconds.
+         */
+        long getSubscribeRetryDuration(Context context, int subId);
     }
 
     private static UceUtilsProxy sUceUtilsProxy = new UceUtilsProxy() {
@@ -139,11 +149,20 @@
         public boolean isNumberBlocked(Context context, String phoneNumber) {
             return UceUtils.isNumberBlocked(context, phoneNumber);
         }
+
+        @Override
+        public long getSubscribeRetryDuration(Context context, int subId) {
+            return UceUtils.getSubscribeRetryInterval(context, subId);
+        }
     };
 
     @VisibleForTesting
     public void setsUceUtilsProxy(UceUtilsProxy uceUtilsProxy) {
         sUceUtilsProxy = uceUtilsProxy;
+
+        // Update values for testing
+        mRetryDuration = sUceUtilsProxy.getSubscribeRetryDuration(mContext, mSubId);
+        mRetryEnabled = mRetryDuration >= 0L;
     }
 
     /**
@@ -277,6 +296,11 @@
          * is inconclusive.
          */
         void addToThrottlingList(List<Uri> uriList, int sipCode);
+
+        /**
+         * Send subscribe request to retry.
+         */
+        void sendSubscribeRetryRequest(UceRequest request);
     }
 
     private RequestManagerCallback mRequestMgrCallback = new RequestManagerCallback() {
@@ -406,6 +430,11 @@
         public void addToThrottlingList(List<Uri> uriList, int sipCode) {
             mThrottlingList.addToThrottlingList(uriList, sipCode);
         }
+
+        @Override
+        public void sendSubscribeRetryRequest(UceRequest request) {
+            UceRequestManager.this.sendSubscribeRetryRequest(request);
+        }
     };
 
     private final int mSubId;
@@ -413,31 +442,39 @@
     private final UceRequestHandler mHandler;
     private final UceRequestRepository mRequestRepository;
     private final ContactThrottlingList mThrottlingList;
+    private final FeatureFlags mFeatureFlags;
     private volatile boolean mIsDestroyed;
 
     private OptionsController mOptionsCtrl;
     private SubscribeController mSubscribeCtrl;
     private UceControllerCallback mControllerCallback;
+    private boolean mRetryEnabled;
+    private long mRetryDuration;
 
-    public UceRequestManager(Context context, int subId, Looper looper, UceControllerCallback c) {
+    public UceRequestManager(Context context, int subId, Looper looper, UceControllerCallback c,
+            FeatureFlags featureFlags) {
         mSubId = subId;
         mContext = context;
         mControllerCallback = c;
         mHandler = new UceRequestHandler(this, looper);
         mThrottlingList = new ContactThrottlingList(mSubId);
         mRequestRepository = new UceRequestRepository(subId, mRequestMgrCallback);
+        mRetryDuration = sUceUtilsProxy.getSubscribeRetryDuration(mContext, mSubId);
+        mRetryEnabled = mRetryDuration >= 0L;
+        mFeatureFlags = featureFlags;
         logi("create");
     }
 
     @VisibleForTesting
     public UceRequestManager(Context context, int subId, Looper looper, UceControllerCallback c,
-            UceRequestRepository requestRepository) {
+            UceRequestRepository requestRepository, FeatureFlags featureFlags) {
         mSubId = subId;
         mContext = context;
         mControllerCallback = c;
         mHandler = new UceRequestHandler(this, looper);
         mRequestRepository = requestRepository;
         mThrottlingList = new ContactThrottlingList(mSubId);
+        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -473,6 +510,16 @@
     }
 
     /**
+     * Notify carrier config changed and update cached value.
+     */
+    public void onCarrierConfigChanged() {
+        mRetryDuration = sUceUtilsProxy.getSubscribeRetryDuration(mContext, mSubId);
+        mRetryEnabled = mRetryDuration >= 0L;
+
+        logd("carrier config changed : retry duration = " + mRetryDuration);
+    }
+
+    /**
      * Send a new capability request. It is called by UceController.
      */
     public void sendCapabilityRequest(List<Uri> uriList, boolean skipFromCache,
@@ -481,7 +528,7 @@
             callback.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L, null);
             return;
         }
-        sendRequestInternal(UceRequest.REQUEST_TYPE_CAPABILITY, uriList, skipFromCache, callback);
+        sendRequestInternal(REQUEST_TYPE_CAPABILITY, uriList, skipFromCache, callback);
     }
 
     /**
@@ -497,6 +544,35 @@
                 Collections.singletonList(uri), false /* skipFromCache */, callback);
     }
 
+    private void sendSubscribeRetryRequest(UceRequest request) {
+        if (!mFeatureFlags.enableSipSubscribeRetry()) {
+            logw("Retry subscribe is not allowed");
+            return;
+        }
+
+        if (request == null || !SubscribeRequest.class.isInstance(request)) {
+            logw("parameter is not available for retry");
+            return;
+        }
+
+        // Handle subscribe retry in background, don't need to consider the cached capabilities
+        // and the callback to notify the result of operation.
+        UceRequestCoordinator requestCoordinator =
+                createSubscribeRequestCoordinatorForRetry(request);
+
+        if (requestCoordinator == null) {
+            logw("createSubscribeRequestCoordinator failed for retry");
+            return;
+        }
+        addRequestCoordinator(requestCoordinator);
+
+        // Send delay message to retry.
+        Long coordinatorId = requestCoordinator.getCoordinatorId();
+        Long taskId = request.getTaskId();
+        mHandler.sendRequestMessage(coordinatorId, taskId, mRetryDuration);
+        logd("sent message for retry " + coordinatorId + " " + taskId + " " + mRetryDuration);
+    }
+
     private void sendRequestInternal(@UceRequestType int type, List<Uri> uriList,
             boolean skipFromCache, IRcsUceControllerCallback callback) throws RemoteException {
         UceRequestCoordinator requestCoordinator = null;
@@ -538,7 +614,7 @@
         logd(builder.toString());
 
         // Add this RequestCoordinator to the UceRequestRepository.
-        addRequestCoordinator(requestCoordinator);
+        addRequestCoordinatorAndDispatch(requestCoordinator);
     }
 
     /**
@@ -584,7 +660,7 @@
     private List<RcsContactUceCapability> getCapabilitiesFromCache(int requestType,
             List<Uri> uriList) {
         List<EabCapabilityResult> resultList = Collections.emptyList();
-        if (requestType == UceRequest.REQUEST_TYPE_CAPABILITY) {
+        if (requestType == REQUEST_TYPE_CAPABILITY) {
             resultList = mRequestMgrCallback.getCapabilitiesFromCache(uriList);
         } else if (requestType == UceRequest.REQUEST_TYPE_AVAILABILITY) {
             // Always get the first element if the request type is availability.
@@ -602,7 +678,6 @@
     private UceRequestCoordinator createSubscribeRequestCoordinator(final @UceRequestType int type,
             final List<Uri> uriList, boolean skipFromCache, IRcsUceControllerCallback callback) {
         SubscribeRequestCoordinator.Builder builder;
-
         if (!sUceUtilsProxy.isPresenceGroupSubscribeEnabled(mContext, mSubId)) {
             // When the group subscribe is disabled, each contact is required to be encapsulated
             // into individual UceRequest.
@@ -628,7 +703,10 @@
                         individualUri = Collections.singletonList(getSipUriFromUri(uri));
                     }
                 }
+
                 UceRequest request = createSubscribeRequest(type, individualUri, skipFromCache);
+                // Set whether retry is allowed
+                request.setRetryEnabled(mRetryEnabled);
                 requestList.add(request);
             });
             builder = new SubscribeRequestCoordinator.Builder(mSubId, requestList,
@@ -645,14 +723,22 @@
                 for (int index = 0; index < rclMaxNumber; index++) {
                     subUriList.add(uriList.get(count * rclMaxNumber + index));
                 }
-                requestList.add(createSubscribeRequest(type, subUriList, skipFromCache));
+
+                UceRequest request = createSubscribeRequest(type, subUriList, skipFromCache);
+                // Set whether retry is allowed
+                request.setRetryEnabled(mRetryEnabled);
+                requestList.add(request);
             }
 
             List<Uri> subUriList = new ArrayList<>();
             for (int i = numRequestCoordinators * rclMaxNumber; i < uriList.size(); i++) {
                 subUriList.add(uriList.get(i));
             }
-            requestList.add(createSubscribeRequest(type, subUriList, skipFromCache));
+
+            UceRequest request = createSubscribeRequest(type, subUriList, skipFromCache);
+            // Set whether retry is allowed
+            request.setRetryEnabled(mRetryEnabled);
+            requestList.add(request);
 
             builder = new SubscribeRequestCoordinator.Builder(mSubId, requestList,
                     mRequestMgrCallback);
@@ -661,6 +747,17 @@
         return builder.build();
     }
 
+    private UceRequestCoordinator createSubscribeRequestCoordinatorForRetry(UceRequest request) {
+        SubscribeRequestCoordinator.Builder builder;
+
+        List<UceRequest> requestList = new ArrayList<>();
+        requestList.add(request);
+        builder = new SubscribeRequestCoordinator.Builder(mSubId, requestList,
+                mRequestMgrCallback);
+        builder.setCapabilitiesCallback(null);
+        return builder.build();
+    }
+
     private UceRequestCoordinator createOptionsRequestCoordinator(@UceRequestType int type,
             List<Uri> uriList, IRcsUceControllerCallback callback) {
         OptionsRequestCoordinator.Builder builder;
@@ -678,7 +775,7 @@
     private CapabilityRequest createSubscribeRequest(int type, List<Uri> uriList,
             boolean skipFromCache) {
         CapabilityRequest request = new SubscribeRequest(mSubId, type, mRequestMgrCallback,
-                mSubscribeCtrl);
+                mSubscribeCtrl, mFeatureFlags);
         request.setContactUri(uriList);
         request.setSkipGettingFromCache(skipFromCache);
         return request;
@@ -723,7 +820,7 @@
         logd(builder.toString());
 
         // Add this RequestCoordinator to the UceRequestRepository.
-        addRequestCoordinator(requestCoordinator);
+        addRequestCoordinatorAndDispatch(requestCoordinator);
     }
 
     private static class UceRequestHandler extends Handler {
@@ -732,7 +829,6 @@
         private static final int EVENT_REQUEST_TIMEOUT = 3;
         private static final int EVENT_REQUEST_FINISHED = 4;
         private static final int EVENT_COORDINATOR_FINISHED = 5;
-
         private final Map<Long, SomeArgs> mRequestTimeoutTimers;
         private final WeakReference<UceRequestManager> mUceRequestMgrRef;
 
@@ -926,14 +1022,18 @@
         }
     }
 
-    private void addRequestCoordinator(UceRequestCoordinator coordinator) {
-        mRequestRepository.addRequestCoordinator(coordinator);
+    private void addRequestCoordinatorAndDispatch(UceRequestCoordinator coordinator) {
+        mRequestRepository.addRequestCoordinatorAndDispatch(coordinator);
     }
 
     private UceRequestCoordinator removeRequestCoordinator(Long coordinatorId) {
         return mRequestRepository.removeRequestCoordinator(coordinatorId);
     }
 
+    private void addRequestCoordinator(UceRequestCoordinator coordinator) {
+        mRequestRepository.addRequestCoordinator(coordinator);
+    }
+
     private UceRequestCoordinator getRequestCoordinator(Long coordinatorId) {
         return mRequestRepository.getRequestCoordinator(coordinatorId);
     }
diff --git a/src/java/com/android/ims/rcs/uce/request/UceRequestRepository.java b/src/java/com/android/ims/rcs/uce/request/UceRequestRepository.java
index 1d2c1e8..f50c5e4 100644
--- a/src/java/com/android/ims/rcs/uce/request/UceRequestRepository.java
+++ b/src/java/com/android/ims/rcs/uce/request/UceRequestRepository.java
@@ -53,7 +53,7 @@
      * Add new UceRequestCoordinator and notify the RequestDispatcher to check whether the given
      * requests can be executed or not.
      */
-    public synchronized void addRequestCoordinator(UceRequestCoordinator coordinator) {
+    public synchronized void addRequestCoordinatorAndDispatch(UceRequestCoordinator coordinator) {
         if (mDestroyed) return;
         mRequestCoordinators.put(coordinator.getCoordinatorId(), coordinator);
         mDispatcher.addRequest(coordinator.getCoordinatorId(),
@@ -69,6 +69,14 @@
     }
 
     /**
+     * Add new UceRequestCoordinator for retry only and not execute the given request.
+     */
+    public synchronized void addRequestCoordinator(UceRequestCoordinator coordinator) {
+        if (mDestroyed) return;
+        mRequestCoordinators.put(coordinator.getCoordinatorId(), coordinator);
+    }
+
+    /**
      * Retrieve the RequestCoordinator associated with the given coordinatorId.
      */
     public synchronized UceRequestCoordinator getRequestCoordinator(Long coordinatorId) {
diff --git a/src/java/com/android/ims/rcs/uce/util/UceUtils.java b/src/java/com/android/ims/rcs/uce/util/UceUtils.java
index c5f2b12..dadf582 100644
--- a/src/java/com/android/ims/rcs/uce/util/UceUtils.java
+++ b/src/java/com/android/ims/rcs/uce/util/UceUtils.java
@@ -459,4 +459,23 @@
         }
         return value;
     }
+
+    /**
+     * The time interval of milliseconds for the subscribe retry.
+     *
+     * @return The time interval of milliseconds for the subscribe retry.
+     */
+    public static long getSubscribeRetryInterval(Context context, int subId) {
+        CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
+        if (configManager == null) {
+            return -1L;
+        }
+        PersistableBundle config = configManager.getConfigForSubId(subId);
+        if (config == null) {
+            return -1L;
+        }
+
+        return config.getLong(
+                CarrierConfigManager.Ims.KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG, -1L);
+    }
 }
diff --git a/tests/src/com/android/ims/rcs/uce/UceControllerTest.java b/tests/src/com/android/ims/rcs/uce/UceControllerTest.java
index 79702ed..6f7104b 100644
--- a/tests/src/com/android/ims/rcs/uce/UceControllerTest.java
+++ b/tests/src/com/android/ims/rcs/uce/UceControllerTest.java
@@ -42,6 +42,7 @@
 import com.android.ims.rcs.uce.presence.subscribe.SubscribeController;
 import com.android.ims.rcs.uce.request.UceRequestManager;
 import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -71,6 +72,7 @@
     @Mock UceController.UceControllerCallback mCallback;
     @Mock IRcsUceControllerCallback mCapabilitiesCallback;
     @Mock IOptionsRequestCallback mOptionsRequestCallback;
+    @Mock FeatureFlags mFeatureFlags;
 
     private int mSubId = 1;
 
@@ -86,7 +88,7 @@
         doReturn(mOptionsController).when(mControllerFactory).createOptionsController(any(),
                 eq(mSubId));
         doReturn(mTaskManager).when(mTaskManagerFactory).createRequestManager(any(), eq(mSubId),
-                any(), any());
+                any(), any(), any());
         doReturn(mDeviceStateResult).when(mDeviceState).getCurrentState();
     }
 
@@ -280,7 +282,7 @@
 
     private UceController createUceController() {
         UceController uceController = new UceController(mContext, mSubId, mDeviceState,
-                mControllerFactory, mTaskManagerFactory);
+                mControllerFactory, mTaskManagerFactory, mFeatureFlags);
         uceController.setUceControllerCallback(mCallback);
         return uceController;
     }
diff --git a/tests/src/com/android/ims/rcs/uce/request/SubscribeRequestTest.java b/tests/src/com/android/ims/rcs/uce/request/SubscribeRequestTest.java
index 543ad6d..ee0f7be 100644
--- a/tests/src/com/android/ims/rcs/uce/request/SubscribeRequestTest.java
+++ b/tests/src/com/android/ims/rcs/uce/request/SubscribeRequestTest.java
@@ -18,6 +18,7 @@
 
 import static android.telephony.ims.stub.RcsCapabilityExchangeImplBase.COMMAND_CODE_NOT_SUPPORTED;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -25,6 +26,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.net.Uri;
@@ -32,6 +34,7 @@
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.ISubscribeResponseCallback;
+import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -39,11 +42,13 @@
 import com.android.ims.ImsTestBase;
 import com.android.ims.rcs.uce.presence.subscribe.SubscribeController;
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import java.util.ArrayList;
@@ -51,10 +56,13 @@
 
 @RunWith(AndroidJUnit4.class)
 public class SubscribeRequestTest extends ImsTestBase {
+    private static final Uri CONTACT1 = Uri.fromParts("sip", "test1", null);
+    private static final Uri CONTACT2 = Uri.fromParts("sip", "test2", null);
 
     @Mock SubscribeController mSubscribeController;
     @Mock CapabilityRequestResponse mRequestResponse;
     @Mock RequestManagerCallback mRequestManagerCallback;
+    @Mock FeatureFlags mFeatureFlags;
 
     private int mSubId = 1;
     private long mCoordId = 1;
@@ -62,6 +70,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
+        doReturn(false).when(mFeatureFlags).enableSipSubscribeRetry();
     }
 
     @After
@@ -173,9 +182,88 @@
         verify(mRequestManagerCallback).notifyTerminated(eq(mCoordId), anyLong());
     }
 
+    @Test
+    @SmallTest
+    public void testSipSubscribeRetryWithDisabledFeatureFlag() throws Exception {
+        doReturn(false).when(mFeatureFlags).enableSipSubscribeRetry();
+
+        SubscribeRequest subscribeRequest = getSubscribeRequest();
+        List<Uri> uriList = new ArrayList<>();
+        uriList.add(CONTACT1);
+        uriList.add(CONTACT2);
+        subscribeRequest.setContactUri(uriList);
+        subscribeRequest.setRetryEnabled(true);
+        subscribeRequest.setRetryCount(0);
+        ISubscribeResponseCallback callback = subscribeRequest.getResponseCallback();
+        int errorCommand = RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT;
+        callback.onCommandError(errorCommand);
+
+        verify(mRequestResponse).setCommandError(eq(errorCommand));
+        verify(mRequestResponse, never()).setSipDetails(any(SipDetails.class));
+        verify(mRequestManagerCallback).notifyCommandError(eq(mCoordId), anyLong());
+
+        // Verify that subscribe request is not retried
+        verify(mRequestManagerCallback, never()).sendSubscribeRetryRequest(any());
+    }
+
+    @Test
+    @SmallTest
+    public void testSipSubscribeRetry() throws Exception {
+        doReturn(true).when(mFeatureFlags).enableSipSubscribeRetry();
+
+        SubscribeRequest subscribeRequest = getSubscribeRequest();
+        List<Uri> uriList = new ArrayList<>();
+        uriList.add(CONTACT1);
+        uriList.add(CONTACT2);
+        subscribeRequest.setContactUri(uriList);
+        subscribeRequest.setRetryEnabled(true);
+        subscribeRequest.setRetryCount(0);
+        ISubscribeResponseCallback callback = subscribeRequest.getResponseCallback();
+        int errorCommand = RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT;
+        callback.onCommandError(errorCommand);
+
+        verify(mRequestResponse).setCommandError(eq(errorCommand));
+        verify(mRequestResponse, never()).setSipDetails(any(SipDetails.class));
+        verify(mRequestManagerCallback).notifyCommandError(eq(mCoordId), anyLong());
+
+        ArgumentCaptor<CapabilityRequest> captor =
+                ArgumentCaptor.forClass(CapabilityRequest.class);
+        verify(mRequestManagerCallback, times(1)).sendSubscribeRetryRequest(captor.capture());
+        CapabilityRequest retrySubscribeRequest = captor.getValue();
+
+        // The number of contacts is the same, but the retryCount is greater than 1.
+        assertEquals(subscribeRequest.getContactUri().size(),
+                retrySubscribeRequest.getContactUri().size());
+        assertEquals(subscribeRequest.getRetryCount() + 1, retrySubscribeRequest.getRetryCount());
+    }
+
+    @Test
+    @SmallTest
+    public void testSipSubscribeReachMaxRetry() throws Exception {
+        doReturn(true).when(mFeatureFlags).enableSipSubscribeRetry();
+
+        // Reach max retry request and received onCommandError with COMMAND_CODE_REQUEST_TIMEOUT.
+        SubscribeRequest subscribeRetryRequest = getSubscribeRequest();
+        List<Uri> uriList = new ArrayList<>();
+        uriList.add(CONTACT1);
+        uriList.add(CONTACT2);
+        subscribeRetryRequest.setContactUri(uriList);
+        subscribeRetryRequest.setRetryEnabled(true);
+        subscribeRetryRequest.setRetryCount(SubscribeRequest.MAX_RETRY_COUNT);
+        ISubscribeResponseCallback callback = subscribeRetryRequest.getResponseCallback();
+        int errorCommand = RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT;
+        callback.onCommandError(errorCommand);
+
+        verify(mRequestResponse).setCommandError(eq(errorCommand));
+        verify(mRequestResponse, never()).setSipDetails(any(SipDetails.class));
+        verify(mRequestManagerCallback).notifyCommandError(eq(mCoordId), anyLong());
+        // Verify that retry method was not called.
+        verify(mRequestManagerCallback, never()).sendSubscribeRetryRequest(any());
+    }
+
     private SubscribeRequest getSubscribeRequest() {
         SubscribeRequest request = new SubscribeRequest(mSubId, UceRequest.REQUEST_TYPE_CAPABILITY,
-                mRequestManagerCallback, mSubscribeController, mRequestResponse);
+                mRequestManagerCallback, mSubscribeController, mRequestResponse, mFeatureFlags);
         request.setRequestCoordinatorId(mCoordId);
         return request;
     }
diff --git a/tests/src/com/android/ims/rcs/uce/request/UceRequestManagerTest.java b/tests/src/com/android/ims/rcs/uce/request/UceRequestManagerTest.java
index fa8214e..a3dc588 100644
--- a/tests/src/com/android/ims/rcs/uce/request/UceRequestManagerTest.java
+++ b/tests/src/com/android/ims/rcs/uce/request/UceRequestManagerTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
@@ -52,21 +53,24 @@
 import com.android.ims.rcs.uce.UceController;
 import com.android.ims.rcs.uce.UceController.UceControllerCallback;
 import com.android.ims.rcs.uce.eab.EabCapabilityResult;
+import com.android.ims.rcs.uce.presence.subscribe.SubscribeController;
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
 import com.android.ims.rcs.uce.request.UceRequestManager.UceUtilsProxy;
 import com.android.ims.rcs.uce.util.FeatureTags;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
 @RunWith(AndroidJUnit4.class)
 public class UceRequestManagerTest extends ImsTestBase {
 
@@ -76,6 +80,9 @@
     @Mock UceRequestRepository mRequestRepository;
     @Mock IRcsUceControllerCallback mCapabilitiesCallback;
     @Mock IOptionsRequestCallback mOptionsReqCallback;
+    @Mock SubscribeController mSubscribeController;
+    @Mock CapabilityRequestResponse mRequestResponse;
+    @Mock FeatureFlags mFeatureFlags;
 
     private int mSubId = 1;
     private long mTaskId = 1L;
@@ -98,32 +105,35 @@
     @SmallTest
     public void testSendCapabilityRequest() throws Exception {
         UceRequestManager requestManager = getUceRequestManager();
-        requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, false, false, true, 10));
+        requestManager.setsUceUtilsProxy(getUceUtilsProxy(
+                true, true, false, false, true, 10, false, 0));
 
         List<Uri> uriList = new ArrayList<>();
         uriList.add(Uri.fromParts("sip", "test", null));
         requestManager.sendCapabilityRequest(uriList, false, mCapabilitiesCallback);
 
-        verify(mRequestRepository).addRequestCoordinator(any());
+        verify(mRequestRepository).addRequestCoordinatorAndDispatch(any());
     }
 
     @Test
     @SmallTest
     public void testSendAvailabilityRequest() throws Exception {
         UceRequestManager requestManager = getUceRequestManager();
-        requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, false, false, true, 10));
+        requestManager.setsUceUtilsProxy(getUceUtilsProxy(
+                true, true, false, false, true, 10, false, 0));
 
         Uri uri = Uri.fromParts("sip", "test", null);
         requestManager.sendAvailabilityRequest(uri, mCapabilitiesCallback);
 
-        verify(mRequestRepository).addRequestCoordinator(any());
+        verify(mRequestRepository).addRequestCoordinatorAndDispatch(any());
     }
 
     @Test
     @SmallTest
     public void testRequestDestroyed() throws Exception {
         UceRequestManager requestManager = getUceRequestManager();
-        requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10));
+        requestManager.setsUceUtilsProxy(getUceUtilsProxy(
+                true, true, true, false, true, 10, false, 0));
 
         requestManager.onDestroy();
 
@@ -145,7 +155,9 @@
     @SmallTest
     public void testCacheHitShortcut() throws Exception {
         UceRequestManager requestManager = getUceRequestManager();
-        requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10));
+        requestManager.setsUceUtilsProxy(getUceUtilsProxy(
+                true, true, true, false, true, 10, false, 0));
+
         Handler handler = requestManager.getUceRequestHandler();
 
         List<Uri> uriList = new ArrayList<>();
@@ -167,7 +179,7 @@
                 Collectors.toList()));
         verify(mCapabilitiesCallback).onComplete(eq(null));
         // The cache should have been hit, so no network requests should have been generated.
-        verify(mRequestRepository, never()).addRequestCoordinator(any());
+        verify(mRequestRepository, never()).addRequestCoordinatorAndDispatch(any());
     }
 
     /**
@@ -178,7 +190,9 @@
     @SmallTest
     public void testCacheExpiredShortcut() throws Exception {
         UceRequestManager requestManager = getUceRequestManager();
-        requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10));
+        requestManager.setsUceUtilsProxy(getUceUtilsProxy(
+                true, true, true, false, true, 10, false, 0));
+
         Handler handler = requestManager.getUceRequestHandler();
 
         List<Uri> uriList = new ArrayList<>();
@@ -199,7 +213,7 @@
         verify(mCapabilitiesCallback, never()).onCapabilitiesReceived(any());
         verify(mCapabilitiesCallback, never()).onComplete(any());
         // A network request should have been generated for the expired contact.
-        verify(mRequestRepository).addRequestCoordinator(any());
+        verify(mRequestRepository).addRequestCoordinatorAndDispatch(any());
     }
 
     /**
@@ -212,7 +226,9 @@
     @SmallTest
     public void testCacheHitShortcutForSubsetOfCaps() throws Exception {
         UceRequestManager requestManager = getUceRequestManager();
-        requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10));
+        requestManager.setsUceUtilsProxy(getUceUtilsProxy(
+                true, true, true, false, true, 10, false, 0));
+
         Handler handler = requestManager.getUceRequestHandler();
 
         List<Uri> uriList = new ArrayList<>();
@@ -244,14 +260,16 @@
         verify(mCapabilitiesCallback, never()).onComplete(any());
         // The cache should have been hit, but there was also entry that was not in the cache, so
         // ensure that is requested.
-        verify(mRequestRepository).addRequestCoordinator(any());
+        verify(mRequestRepository).addRequestCoordinatorAndDispatch(any());
     }
 
     @Test
     @SmallTest
     public void testRequestManagerCallback() throws Exception {
         UceRequestManager requestManager = getUceRequestManager();
-        requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10));
+        requestManager.setsUceUtilsProxy(getUceUtilsProxy(
+                true, true, true, false, true, 10, false, 0));
+
         RequestManagerCallback requestMgrCallback = requestManager.getRequestManagerCallback();
         Handler handler = requestManager.getUceRequestHandler();
 
@@ -331,7 +349,8 @@
     @SmallTest
     public void testRetrieveCapForRemote() throws Exception {
         UceRequestManager requestManager = getUceRequestManager();
-        requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10));
+        requestManager.setsUceUtilsProxy(getUceUtilsProxy(
+                true, true, true, false, true, 10, false, 0));
 
         Uri contact = Uri.fromParts("sip", "test", null);
         List<String> remoteCapList = new ArrayList<>();
@@ -339,17 +358,39 @@
         remoteCapList.add(FeatureTags.FEATURE_TAG_FILE_TRANSFER);
         requestManager.retrieveCapabilitiesForRemote(contact, remoteCapList, mOptionsReqCallback);
 
+        verify(mRequestRepository).addRequestCoordinatorAndDispatch(any());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendSubscribeRetryRequest() throws Exception {
+        doReturn(true).when(mFeatureFlags).enableSipSubscribeRetry();
+
+        UceRequestManager requestManager = getUceRequestManager();
+        requestManager.setsUceUtilsProxy(getUceUtilsProxy(
+                true, true, false, false, true, 10, true, 30000));
+
+        RequestManagerCallback requestManagerCallback = requestManager.getRequestManagerCallback();
+
+        SubscribeRequest subscribeRetryRequest = new SubscribeRequest(mSubId,
+                UceRequest.REQUEST_TYPE_CAPABILITY, requestManagerCallback, mSubscribeController,
+                mRequestResponse, mFeatureFlags);
+
+        requestManagerCallback.sendSubscribeRetryRequest(subscribeRetryRequest);
+
         verify(mRequestRepository).addRequestCoordinator(any());
+        verify(mRequestRepository, never()).addRequestCoordinatorAndDispatch(any());
     }
 
     private UceRequestManager getUceRequestManager() {
         UceRequestManager manager = new UceRequestManager(mContext, mSubId, Looper.getMainLooper(),
-                mCallback, mRequestRepository);
+                mCallback, mRequestRepository, mFeatureFlags);
         return manager;
     }
 
     private UceUtilsProxy getUceUtilsProxy(boolean presenceCapEnabled, boolean supportPresence,
-            boolean supportOptions, boolean isBlocked, boolean groupSubscribe, int rclMaximum) {
+            boolean supportOptions, boolean isBlocked, boolean groupSubscribe, int rclMaximum,
+            boolean isRetryEnabled, long retryTime) {
         return new UceUtilsProxy() {
             @Override
             public boolean isPresenceCapExchangeEnabled(Context context, int subId) {
@@ -380,6 +421,11 @@
             public boolean isNumberBlocked(Context context, String phoneNumber) {
                 return isBlocked;
             }
+
+            @Override
+            public long getSubscribeRetryDuration(Context context, int subId) {
+                return retryTime;
+            }
         };
     }
 }