Merge changes from topic "RCS single registration metrics" into sc-v2-dev

* changes:
  Add metrics for UCE event about Publish, Subscribe, Options and metrics for RCS registration features
  [UCE] Reset the retry count and times related publish when the publish status changed to not_published
  Create UceStatsWriter for RCS metrics
diff --git a/src/java/com/android/ims/rcs/uce/UceController.java b/src/java/com/android/ims/rcs/uce/UceController.java
index c609909..7067790 100644
--- a/src/java/com/android/ims/rcs/uce/UceController.java
+++ b/src/java/com/android/ims/rcs/uce/UceController.java
@@ -80,11 +80,23 @@
         List<EabCapabilityResult> getCapabilitiesFromCache(@NonNull List<Uri> uris);
 
         /**
+         * Retrieve the capabilities associated with the given uris from the cache including
+         * expired capabilities.
+         */
+        List<EabCapabilityResult> getCapabilitiesFromCacheIncludingExpired(@NonNull List<Uri> uris);
+
+        /**
          * Retrieve the contact's capabilities from the availability cache.
          */
         EabCapabilityResult getAvailabilityFromCache(@NonNull Uri uri);
 
         /**
+         * Retrieve the contact's capabilities from the availability cache including expired
+         * capabilities
+         */
+        EabCapabilityResult getAvailabilityFromCacheIncludingExpired(@NonNull Uri uri);
+
+        /**
          * Store the given capabilities to the cache.
          */
         void saveCapabilities(List<RcsContactUceCapability> contactCapabilities);
@@ -476,11 +488,21 @@
         }
 
         @Override
+        public List<EabCapabilityResult> getCapabilitiesFromCacheIncludingExpired(List<Uri> uris) {
+            return mEabController.getCapabilitiesIncludingExpired(uris);
+        }
+
+        @Override
         public EabCapabilityResult getAvailabilityFromCache(Uri contactUri) {
             return mEabController.getAvailability(contactUri);
         }
 
         @Override
+        public EabCapabilityResult getAvailabilityFromCacheIncludingExpired(Uri contactUri) {
+            return mEabController.getAvailabilityIncludingExpired(contactUri);
+        }
+
+        @Override
         public void saveCapabilities(List<RcsContactUceCapability> contactCapabilities) {
             mEabController.saveCapabilities(contactCapabilities);
         }
@@ -755,6 +777,7 @@
     public void removeRequestDisallowedStatus() {
         logd("removeRequestDisallowedStatus");
         mDeviceState.resetDeviceState();
+        mRequestManager.resetThrottlingList();
     }
 
     /**
diff --git a/src/java/com/android/ims/rcs/uce/eab/EabController.java b/src/java/com/android/ims/rcs/uce/eab/EabController.java
index 903a19d..b03e465 100644
--- a/src/java/com/android/ims/rcs/uce/eab/EabController.java
+++ b/src/java/com/android/ims/rcs/uce/eab/EabController.java
@@ -35,11 +35,23 @@
     @NonNull List<EabCapabilityResult> getCapabilities(@NonNull List<Uri> uris);
 
     /**
+     * Get contact capabilities from cache including expired capabilities.
+     * @param uris the uri list to get contact capabilities from cache.
+     * @return The contact capabilities of the given uri list.
+     */
+    @NonNull List<EabCapabilityResult> getCapabilitiesIncludingExpired(@NonNull List<Uri> uris);
+
+    /**
      * Retrieve the contact's capabilities from the availability cache.
      */
     @NonNull EabCapabilityResult getAvailability(@NonNull Uri contactUri);
 
     /**
+     * Retrieve the contact's capabilities from the availability cache.
+     */
+    @NonNull EabCapabilityResult getAvailabilityIncludingExpired(@NonNull Uri contactUri);
+
+    /**
      * Save the capabilities to the EAB database.
      */
     void saveCapabilities(@NonNull List<RcsContactUceCapability> contactCapabilities);
diff --git a/src/java/com/android/ims/rcs/uce/eab/EabControllerImpl.java b/src/java/com/android/ims/rcs/uce/eab/EabControllerImpl.java
index cc1011f..388155a 100644
--- a/src/java/com/android/ims/rcs/uce/eab/EabControllerImpl.java
+++ b/src/java/com/android/ims/rcs/uce/eab/EabControllerImpl.java
@@ -56,6 +56,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.function.Predicate;
 
 /**
@@ -163,6 +164,29 @@
     }
 
     /**
+     * Retrieve the contacts' capabilities from the EAB database including expired capabilities.
+     */
+    @Override
+    public @NonNull List<EabCapabilityResult> getCapabilitiesIncludingExpired(
+            @NonNull List<Uri> uris) {
+        Objects.requireNonNull(uris);
+        if (mIsSetDestroyedFlag) {
+            Log.d(TAG, "EabController destroyed.");
+            return generateDestroyedResult(uris);
+        }
+
+        Log.d(TAG, "getCapabilitiesIncludingExpired uri size=" + uris.size());
+        List<EabCapabilityResult> capabilityResultList = new ArrayList();
+
+        for (Uri uri : uris) {
+            EabCapabilityResult result = generateEabResultIncludingExpired(uri,
+                    this::isCapabilityExpired);
+            capabilityResultList.add(result);
+        }
+        return capabilityResultList;
+    }
+
+    /**
      * Retrieve the contact's capabilities from the availability cache.
      */
     @Override
@@ -179,6 +203,23 @@
     }
 
     /**
+     * Retrieve the contact's capabilities from the availability cache including expired
+     * capabilities.
+     */
+    @Override
+    public @NonNull EabCapabilityResult getAvailabilityIncludingExpired(@NonNull Uri contactUri) {
+        Objects.requireNonNull(contactUri);
+        if (mIsSetDestroyedFlag) {
+            Log.d(TAG, "EabController destroyed.");
+            return new EabCapabilityResult(
+                contactUri,
+                EabCapabilityResult.EAB_CONTROLLER_DESTROYED_FAILURE,
+                null);
+        }
+        return generateEabResultIncludingExpired(contactUri, this::isAvailabilityExpired);
+    }
+
+    /**
      * Update the availability catch and save the capabilities to the EAB database.
      */
     @Override
@@ -296,6 +337,52 @@
         return result;
     }
 
+    private EabCapabilityResult generateEabResultIncludingExpired(Uri contactUri,
+            Predicate<Cursor> isExpiredMethod) {
+        RcsUceCapabilityBuilderWrapper builder = null;
+        EabCapabilityResult result;
+        Optional<Boolean> isExpired = Optional.empty();
+
+        // query EAB provider
+        Uri queryUri = Uri.withAppendedPath(
+                Uri.withAppendedPath(EabProvider.ALL_DATA_URI, String.valueOf(mSubId)),
+                getNumberFromUri(mContext, contactUri));
+        Cursor cursor = mContext.getContentResolver().query(queryUri, null, null, null, null);
+
+        if (cursor != null && cursor.getCount() != 0) {
+            while (cursor.moveToNext()) {
+                // Record whether it has expired.
+                if (!isExpired.isPresent()) {
+                    isExpired = Optional.of(isExpiredMethod.test(cursor));
+                }
+                if (builder == null) {
+                    builder = createNewBuilder(contactUri, cursor);
+                } else {
+                    updateCapability(contactUri, cursor, builder);
+                }
+            }
+            cursor.close();
+
+            // Determine the query result
+            int eabResult = EabCapabilityResult.EAB_QUERY_SUCCESSFUL;
+            if (isExpired.orElse(false)) {
+                eabResult = EabCapabilityResult.EAB_CONTACT_EXPIRED_FAILURE;
+            }
+
+            if (builder.getMechanism() == CAPABILITY_MECHANISM_PRESENCE) {
+                PresenceBuilder presenceBuilder = builder.getPresenceBuilder();
+                result = new EabCapabilityResult(contactUri, eabResult, presenceBuilder.build());
+            } else {
+                OptionsBuilder optionsBuilder = builder.getOptionsBuilder();
+                result = new EabCapabilityResult(contactUri, eabResult, optionsBuilder.build());
+            }
+        } else {
+            result = new EabCapabilityResult(contactUri,
+                    EabCapabilityResult.EAB_CONTACT_NOT_FOUND_FAILURE, null);
+        }
+        return result;
+    }
+
     private void updateCapability(Uri contactUri, Cursor cursor,
                 RcsUceCapabilityBuilderWrapper builderWrapper) {
         if (builderWrapper.getMechanism() == CAPABILITY_MECHANISM_PRESENCE) {
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 f7a4acc..17cec90 100644
--- a/src/java/com/android/ims/rcs/uce/request/CapabilityRequest.java
+++ b/src/java/com/android/ims/rcs/uce/request/CapabilityRequest.java
@@ -23,6 +23,7 @@
 
 import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
 import com.android.ims.rcs.uce.eab.EabCapabilityResult;
+import com.android.ims.rcs.uce.presence.pidfparser.PidfParserUtils;
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
 import com.android.ims.rcs.uce.util.UceUtils;
 import com.android.internal.annotations.VisibleForTesting;
@@ -140,20 +141,36 @@
             return;
         }
 
-        // Get the capabilities from the cache.
-        final List<RcsContactUceCapability> cachedCapList
-                = isSkipGettingFromCache() ? Collections.EMPTY_LIST : getCapabilitiesFromCache();
+        // Get the contact capabilities from the cache including the expired capabilities.
+        final List<EabCapabilityResult> eabResultList = getCapabilitiesFromCache();
+
+        // Get all the unexpired capabilities from the EAB result list and add to the response.
+        final List<RcsContactUceCapability> cachedCapList = isSkipGettingFromCache() ?
+                Collections.EMPTY_LIST : getUnexpiredCapabilities(eabResultList);
         mRequestResponse.addCachedCapabilities(cachedCapList);
 
         logd("executeRequest: cached capabilities size=" + cachedCapList.size());
 
+        // Get the rest contacts which are not in the cache or has expired.
+        final List<Uri> expiredUris = getRequestingFromNetworkUris(cachedCapList);
+
+        // For those uris that are not in the cache or have expired, we should request their
+        // capabilities from the network. However, we still need to check whether these contacts
+        // are in the throttling list. If the contact is in the throttling list, even if it has
+        // expired, we will get the cached capabilities.
+        final List<RcsContactUceCapability> throttlingUris =
+                getFromThrottlingList(expiredUris, eabResultList);
+        mRequestResponse.addCachedCapabilities(throttlingUris);
+
+        logd("executeRequest: contacts in throttling list size=" + throttlingUris.size());
+
         // Notify that the cached capabilities are updated.
-        if (!cachedCapList.isEmpty()) {
+        if (!cachedCapList.isEmpty() || !throttlingUris.isEmpty()) {
             mRequestManagerCallback.notifyCachedCapabilitiesUpdated(mCoordinatorId, mTaskId);
         }
 
         // Get the rest contacts which need to request capabilities from the network.
-        final List<Uri> requestCapUris = getRequestingFromNetworkUris(cachedCapList);
+        List<Uri> requestCapUris = getRequestingFromNetworkUris(cachedCapList, throttlingUris);
 
         logd("executeRequest: requestCapUris size=" + requestCapUris.size());
 
@@ -193,21 +210,30 @@
     }
 
     // Get the cached capabilities by the given request type.
-    private List<RcsContactUceCapability> getCapabilitiesFromCache() {
+    private List<EabCapabilityResult> getCapabilitiesFromCache() {
         List<EabCapabilityResult> resultList = null;
         if (mRequestType == REQUEST_TYPE_CAPABILITY) {
-            resultList = mRequestManagerCallback.getCapabilitiesFromCache(mUriList);
+            resultList = mRequestManagerCallback.getCapabilitiesFromCacheIncludingExpired(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);
+            EabCapabilityResult eabResult =
+                    mRequestManagerCallback.getAvailabilityFromCacheIncludingExpired(uri);
             resultList = new ArrayList<>();
             resultList.add(eabResult);
         }
         if (resultList == null) {
             return Collections.emptyList();
         }
-        return resultList.stream()
+        return resultList;
+    }
+
+    /**
+     * Get the unexpired contact capabilities from the given EAB result list.
+     * @param list the query result from the EAB
+     */
+    private List<RcsContactUceCapability> getUnexpiredCapabilities(List<EabCapabilityResult> list) {
+        return list.stream()
                 .filter(Objects::nonNull)
                 .filter(result -> result.getStatus() == EabCapabilityResult.EAB_QUERY_SUCCESSFUL)
                 .map(EabCapabilityResult::getContactCapabilities)
@@ -227,6 +253,67 @@
     }
 
     /**
+     * Get the contact uris which cannot retrieve capabilities from the cache.
+     * @param cachedCapList The capabilities which are already stored in the cache.
+     * @param throttlingUris The capabilities which are in the throttling list.
+     */
+    private List<Uri> getRequestingFromNetworkUris(List<RcsContactUceCapability> cachedCapList,
+            List<RcsContactUceCapability> throttlingUris) {
+        // We won't request the network query for those contacts in the cache and in the
+        // throttling list. Merging the two list and get the rest contact uris.
+        List<RcsContactUceCapability> notNetworkQueryList = new ArrayList<>(cachedCapList);
+        notNetworkQueryList.addAll(throttlingUris);
+        return getRequestingFromNetworkUris(notNetworkQueryList);
+    }
+
+    /**
+     * Get the contact capabilities for those uri are in the throttling list. If the contact uri is
+     * in the throttling list, the capabilities will be retrieved from cache even if it has expired.
+     * If the capabilities cannot be found, return the non-RCS contact capabilities instead.
+     * @param expiredUris the expired/unknown uris to check whether are in the throttling list
+     * @return the contact capabilities for the uris are in the throttling list
+     */
+    private List<RcsContactUceCapability> getFromThrottlingList(final List<Uri> expiredUris,
+            final List<EabCapabilityResult> eabResultList) {
+        List<RcsContactUceCapability> resultList = new ArrayList<>();
+        List<RcsContactUceCapability> notFoundFromCacheList = new ArrayList<>();
+
+        // Retrieve the uris put in the throttling list from the expired/unknown contacts.
+        List<Uri> throttlingUris = mRequestManagerCallback.getInThrottlingListUris(expiredUris);
+
+        // For these uris in the throttling list, check whether their capabilities are in the cache.
+        List<EabCapabilityResult> throttlingUriFoundInEab = new ArrayList<>();
+        for (Uri uri : throttlingUris) {
+            for (EabCapabilityResult eabResult : eabResultList) {
+                if (eabResult.getContact().equals(uri)) {
+                    throttlingUriFoundInEab.add(eabResult);
+                    break;
+                }
+            }
+        }
+
+        throttlingUriFoundInEab.forEach(eabResult -> {
+            if (eabResult.getStatus() == EabCapabilityResult.EAB_QUERY_SUCCESSFUL ||
+                eabResult.getStatus() == EabCapabilityResult.EAB_CONTACT_EXPIRED_FAILURE) {
+                // The capabilities are found, add to the result list
+                resultList.add(eabResult.getContactCapabilities());
+            } else {
+                // Cannot get the capabilities from cache, create the non-RCS capabilities instead.
+                notFoundFromCacheList.add(PidfParserUtils.getNotFoundContactCapabilities(
+                        eabResult.getContact()));
+            }
+        });
+
+        if (!notFoundFromCacheList.isEmpty()) {
+            resultList.addAll(notFoundFromCacheList);
+        }
+
+        logd("getFromThrottlingList: requesting uris in the list size=" + throttlingUris.size() +
+                ", generate non-RCS size=" + notFoundFromCacheList.size());
+        return resultList;
+    }
+
+    /**
      * Set the timeout timer of this request.
      */
     protected void setupRequestTimeoutTimer() {
diff --git a/src/java/com/android/ims/rcs/uce/request/ContactThrottlingList.java b/src/java/com/android/ims/rcs/uce/request/ContactThrottlingList.java
new file mode 100644
index 0000000..da67f6c
--- /dev/null
+++ b/src/java/com/android/ims/rcs/uce/request/ContactThrottlingList.java
@@ -0,0 +1,119 @@
+/*
+ * 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.util.Log;
+
+import com.android.ims.rcs.uce.util.UceUtils;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * The class is used to store when the contact's capabilities request result is inconclusive.
+ */
+public class ContactThrottlingList {
+    private static final String LOG_TAG = UceUtils.getLogPrefix() + "ThrottlingList";
+
+    private static class ContactInfo {
+        Uri mContactUri;
+        int mSipCode;
+        Instant mThrottleEndTimestamp;
+
+        public ContactInfo(Uri contactUri, int sipCode, Instant timestamp) {
+            mContactUri = contactUri;
+            mSipCode = sipCode;
+            mThrottleEndTimestamp = timestamp;
+        }
+    }
+
+    private final int mSubId;
+    private final List<ContactInfo> mThrottlingList = new ArrayList<>();
+
+    public ContactThrottlingList(int subId) {
+        mSubId = subId;
+    }
+
+    public synchronized void reset() {
+        mThrottlingList.clear();
+    }
+
+    public synchronized void addToThrottlingList(List<Uri> uriList, int sipCode) {
+        // Clean up the expired contacts before starting.
+        cleanUpExpiredContacts();
+
+        List<Uri> addToThrottlingList = getNotInThrottlingListUris(uriList);
+        long expiration = UceUtils.getAvailabilityCacheExpiration(mSubId);
+        Instant timestamp = Instant.now().plusSeconds(expiration);
+
+        List<ContactInfo> list = addToThrottlingList.stream().map(uri ->
+                new ContactInfo(uri, sipCode, timestamp)).collect(Collectors.toList());
+
+        int previousSize = mThrottlingList.size();
+        mThrottlingList.addAll(list);
+
+        logd("addToThrottlingList: previous size=" + previousSize +
+                ", current size=" + mThrottlingList.size() + ", expired time=" + timestamp);
+    }
+
+    private synchronized List<Uri> getNotInThrottlingListUris(List<Uri> uriList) {
+        List<Uri> throttlingUris = mThrottlingList.stream().map(contactInfo ->
+                contactInfo.mContactUri).collect(Collectors.toList());
+        List<Uri> addToThrottlingUris = new ArrayList<>(uriList);
+        addToThrottlingUris.removeAll(throttlingUris);
+        return addToThrottlingUris;
+    }
+
+    public synchronized List<Uri> getInThrottlingListUris(List<Uri> uriList) {
+        // Clean up the expired contacts before starting.
+        cleanUpExpiredContacts();
+
+        return uriList.stream()
+                .filter(uri -> mThrottlingList.stream()
+                    .anyMatch(contactInfo -> contactInfo.mContactUri.equals(uri)))
+                    .collect(Collectors.toList());
+    }
+
+    /**
+     * Clean up the expired contacts from the throttling list.
+     */
+    private synchronized void cleanUpExpiredContacts() {
+        final int previousSize = mThrottlingList.size();
+        List<ContactInfo> expiredContacts = mThrottlingList.stream()
+                .filter(contactInfo -> Instant.now()
+                        .isAfter(contactInfo.mThrottleEndTimestamp))
+                        .collect(Collectors.toList());
+        mThrottlingList.removeAll(expiredContacts);
+
+        logd("cleanUpExpiredContacts: previous size=" + previousSize +
+                ", current size=" + mThrottlingList.size());
+    }
+
+    private void logd(String log) {
+        Log.d(LOG_TAG, getLogPrefix().append(log).toString());
+    }
+
+    private StringBuilder getLogPrefix() {
+        StringBuilder builder = new StringBuilder("[");
+        builder.append(mSubId);
+        builder.append("] ");
+        return builder;
+    }
+}
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 8913340..f44686a 100644
--- a/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java
+++ b/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java
@@ -25,12 +25,14 @@
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 
 import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
+import com.android.ims.rcs.uce.eab.EabCapabilityResult;
 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.ims.rcs.uce.UceStatsWriter;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
@@ -311,6 +313,7 @@
     private RequestResult handleNetworkResponseFailed(SubscribeRequest request) {
         final long taskId = request.getTaskId();
         final CapabilityRequestResponse response = request.getRequestResponse();
+        final List<Uri> requestUris = response.getNotReceiveCapabilityUpdatedContact();
         RequestResult requestResult = null;
 
         if (response.isNotFound()) {
@@ -318,8 +321,7 @@
             // updated callback from the ImsService afterward. Therefore, we create the capabilities
             // with the result REQUEST_RESULT_NOT_FOUND by ourself and will trigger the
             // capabilities received callback to the clients later.
-            List<Uri> uriList = request.getContactUri();
-            List<RcsContactUceCapability> capabilityList = uriList.stream().map(uri ->
+            List<RcsContactUceCapability> capabilityList = requestUris.stream().map(uri ->
                     PidfParserUtils.getNotFoundContactCapabilities(uri))
                     .collect(Collectors.toList());
             response.addUpdatedCapabilities(capabilityList);
@@ -327,9 +329,18 @@
             // We treat the NOT FOUND is a successful result.
             requestResult = sNetworkRespSuccessfulCreator.createRequestResult(taskId, response,
                     mRequestManagerCallback);
-        }
+        } else {
+            // The request result is unsuccessful and it's not the NOT FOUND error. we need to get
+            // the capabilities from the cache.
+            List<RcsContactUceCapability> capabilitiesList =
+                    getCapabilitiesFromCacheIncludingExpired(requestUris);
+            response.addUpdatedCapabilities(capabilitiesList);
 
-        if (requestResult == null) {
+            // Add to the throttling list for the inconclusive result of the contacts.
+            mRequestManagerCallback.addToThrottlingList(requestUris,
+                    response.getResponseSipCode().orElse(
+                            com.android.ims.rcs.uce.util.NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT));
+
             requestResult = sNetworkRespErrorCreator.createRequestResult(taskId, response,
                     mRequestManagerCallback);
         }
@@ -337,6 +348,41 @@
     }
 
     /**
+     * Get the contact capabilities from the cache even if the capabilities have expired. If the
+     * capabilities doesn't exist, create the non-RCS capabilities instead.
+     * @param uris the uris to get the capabilities from cache.
+     * @return The contact capabilities for the given uris.
+     */
+    private List<RcsContactUceCapability> getCapabilitiesFromCacheIncludingExpired(List<Uri> uris) {
+        List<RcsContactUceCapability> resultList = new ArrayList<>();
+        List<RcsContactUceCapability> notFoundFromCacheList = new ArrayList<>();
+
+        // Get the capabilities from the cache.
+        List<EabCapabilityResult> eabResultList =
+                mRequestManagerCallback.getCapabilitiesFromCacheIncludingExpired(uris);
+
+        eabResultList.forEach(eabResult -> {
+            if (eabResult.getStatus() == EabCapabilityResult.EAB_QUERY_SUCCESSFUL ||
+                eabResult.getStatus() == EabCapabilityResult.EAB_CONTACT_EXPIRED_FAILURE) {
+                // The capabilities are found, add to the result list
+                resultList.add(eabResult.getContactCapabilities());
+            } else {
+                // Cannot get the capabilities from cache, create the non-RCS capabilities instead.
+                notFoundFromCacheList.add(PidfParserUtils.getNotFoundContactCapabilities(
+                        eabResult.getContact()));
+            }
+        });
+
+        if (!notFoundFromCacheList.isEmpty()) {
+            resultList.addAll(notFoundFromCacheList);
+        }
+
+        logd("getCapabilitiesFromCacheIncludingExpired: requesting uris size=" + uris.size() +
+                ", capabilities not found from cache size=" + notFoundFromCacheList.size());
+        return resultList;
+    }
+
+    /**
      * This method is called when the given SubscribeRequest received the onNotifyCapabilitiesUpdate
      * callback from the ImsService.
      */
@@ -441,20 +487,39 @@
     }
 
     /**
-     * This method is called when the framework does not receive receive the result for
-     * capabilities request.
+     * This method is called when the framework did not receive the capabilities request result.
      */
     private void handleRequestTimeout(SubscribeRequest request) {
         CapabilityRequestResponse response = request.getRequestResponse();
+        List<Uri> requestUris = response.getNotReceiveCapabilityUpdatedContact();
         logd("handleRequestTimeout: " + response);
+        logd("handleRequestTimeout: not received updated uri size=" + requestUris.size());
 
-        // Finish this request
-        request.onFinish();
+        // Add to the throttling list for the inconclusive result of the contacts.
+        mRequestManagerCallback.addToThrottlingList(requestUris,
+                com.android.ims.rcs.uce.util.NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT);
+
+        // Get the capabilities from the cache instead and add to the response.
+        List<RcsContactUceCapability> capabilitiesList =
+                getCapabilitiesFromCacheIncludingExpired(requestUris);
+        response.addUpdatedCapabilities(capabilitiesList);
+
+        // Trigger capabilities updated callback if there is any.
+        List<RcsContactUceCapability> updatedCapList = response.getUpdatedContactCapability();
+        if (!updatedCapList.isEmpty()) {
+            triggerCapabilitiesReceivedCallback(updatedCapList);
+            response.removeUpdatedCapabilities(updatedCapList);
+        }
 
         // Remove this request from the activated collection and notify RequestManager.
         long taskId = request.getTaskId();
         RequestResult requestResult = sRequestTimeoutCreator.createRequestResult(taskId,
                 response, mRequestManagerCallback);
+
+        // Finish this request
+        request.onFinish();
+
+        // Remove this request from the activated collection and notify RequestManager.
         moveRequestToFinishedCollection(taskId, requestResult);
     }
 
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 3e12ba3..c5fa64c 100644
--- a/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java
+++ b/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java
@@ -148,11 +148,21 @@
         List<EabCapabilityResult> getCapabilitiesFromCache(List<Uri> uriList);
 
         /**
+         * Retrieve the contact capabilities from the cache including the expired capabilities.
+         */
+        List<EabCapabilityResult> getCapabilitiesFromCacheIncludingExpired(List<Uri> uriList);
+
+        /**
          * Retrieve the contact availability from the cache.
          */
         EabCapabilityResult getAvailabilityFromCache(Uri uri);
 
         /**
+         * Retrieve the contact availability from the cache including the expired capabilities.
+         */
+        EabCapabilityResult getAvailabilityFromCacheIncludingExpired(Uri uri);
+
+        /**
          * Store the given contact capabilities to the cache.
          */
         void saveCapabilities(List<RcsContactUceCapability> contactCapabilities);
@@ -241,6 +251,19 @@
          * to remove the coordinator from the UceRequestRepository.
          */
         void notifyRequestCoordinatorFinished(long requestCoordinatorId);
+
+        /**
+         * Check whether the given uris are in the throttling list.
+         * @param uriList the uris to check if it is in the throttling list
+         * @return the uris in the throttling list
+         */
+        List<Uri> getInThrottlingListUris(List<Uri> uriList);
+
+        /**
+         * Add the given uris to the throttling list because the capabilities request result
+         * is inconclusive.
+         */
+        void addToThrottlingList(List<Uri> uriList, int sipCode);
     }
 
     private RequestManagerCallback mRequestMgrCallback = new RequestManagerCallback() {
@@ -255,11 +278,21 @@
         }
 
         @Override
+        public List<EabCapabilityResult> getCapabilitiesFromCacheIncludingExpired(List<Uri> uris) {
+            return mControllerCallback.getCapabilitiesFromCacheIncludingExpired(uris);
+        }
+
+        @Override
         public EabCapabilityResult getAvailabilityFromCache(Uri uri) {
             return mControllerCallback.getAvailabilityFromCache(uri);
         }
 
         @Override
+        public EabCapabilityResult getAvailabilityFromCacheIncludingExpired(Uri uri) {
+            return mControllerCallback.getAvailabilityFromCacheIncludingExpired(uri);
+        }
+
+        @Override
         public void saveCapabilities(List<RcsContactUceCapability> contactCapabilities) {
             mControllerCallback.saveCapabilities(contactCapabilities);
         }
@@ -350,12 +383,23 @@
         public void notifyRequestCoordinatorFinished(long requestCoordinatorId) {
             mHandler.sendRequestCoordinatorFinishedMessage(requestCoordinatorId);
         }
+
+        @Override
+        public List<Uri> getInThrottlingListUris(List<Uri> uriList) {
+            return mThrottlingList.getInThrottlingListUris(uriList);
+        }
+
+        @Override
+        public void addToThrottlingList(List<Uri> uriList, int sipCode) {
+            mThrottlingList.addToThrottlingList(uriList, sipCode);
+        }
     };
 
     private final int mSubId;
     private final Context mContext;
     private final UceRequestHandler mHandler;
     private final UceRequestRepository mRequestRepository;
+    private final ContactThrottlingList mThrottlingList;
     private volatile boolean mIsDestroyed;
 
     private OptionsController mOptionsCtrl;
@@ -367,6 +411,7 @@
         mContext = context;
         mControllerCallback = c;
         mHandler = new UceRequestHandler(this, looper);
+        mThrottlingList = new ContactThrottlingList(mSubId);
         mRequestRepository = new UceRequestRepository(subId, mRequestMgrCallback);
         logi("create");
     }
@@ -379,6 +424,7 @@
         mControllerCallback = c;
         mHandler = new UceRequestHandler(this, looper);
         mRequestRepository = requestRepository;
+        mThrottlingList = new ContactThrottlingList(mSubId);
     }
 
     /**
@@ -402,10 +448,18 @@
         logi("onDestroy");
         mIsDestroyed = true;
         mHandler.onDestroy();
+        mThrottlingList.reset();
         mRequestRepository.onDestroy();
     }
 
     /**
+     * Clear the throttling list.
+     */
+    public void resetThrottlingList() {
+        mThrottlingList.reset();
+    }
+
+    /**
      * Send a new capability request. It is called by UceController.
      */
     public void sendCapabilityRequest(List<Uri> uriList, boolean skipFromCache,
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 c88de0b..d93f2a0 100644
--- a/src/java/com/android/ims/rcs/uce/util/UceUtils.java
+++ b/src/java/com/android/ims/rcs/uce/util/UceUtils.java
@@ -53,6 +53,9 @@
     private static final long DEFAULT_CAP_REQUEST_TIMEOUT_AFTER_MS = TimeUnit.MINUTES.toMillis(3);
     private static Optional<Long> OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.empty();
 
+    // The default value of the availability cache expiration.
+    private static final long DEFAULT_AVAILABILITY_CACHE_EXPIRATION_SEC = 60L;   // 60 seconds
+
     // The task ID of the UCE request
     private static long TASK_ID = 0L;
 
@@ -400,4 +403,26 @@
         }
         return numberParts[0];
     }
+
+    /**
+     * Get the availability expiration from provisioning manager.
+     * @param subId The subscription ID
+     * @return the number of seconds for the availability cache expiration.
+     */
+    public static long getAvailabilityCacheExpiration(int subId) {
+        long value = -1;
+        try {
+            ProvisioningManager pm = ProvisioningManager.createForSubscriptionId(subId);
+            value = pm.getProvisioningIntValue(
+                    ProvisioningManager.KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC);
+        } catch (Exception e) {
+            Log.w(LOG_TAG, "Exception in getAvailabilityCacheExpiration: " + e);
+        }
+
+        if (value <= 0) {
+            Log.w(LOG_TAG, "The availability expiration cannot be less than 0.");
+            value = DEFAULT_AVAILABILITY_CACHE_EXPIRATION_SEC;
+        }
+        return value;
+    }
 }
diff --git a/tests/src/com/android/ims/rcs/uce/request/CapabilityRequestTest.java b/tests/src/com/android/ims/rcs/uce/request/CapabilityRequestTest.java
index 4aef42e..0f1168b 100644
--- a/tests/src/com/android/ims/rcs/uce/request/CapabilityRequestTest.java
+++ b/tests/src/com/android/ims/rcs/uce/request/CapabilityRequestTest.java
@@ -95,7 +95,8 @@
         eabResultList.add(eabResult2);
 
         doReturn(false).when(mDeviceStateResult).isRequestForbidden();
-        doReturn(eabResultList).when(mReqMgrCallback).getCapabilitiesFromCache(any());
+        doReturn(eabResultList).when(mReqMgrCallback)
+                .getCapabilitiesFromCacheIncludingExpired(any());
 
         // Execute the request.
         request.executeRequest();
@@ -161,7 +162,8 @@
         eabResultList.add(eabResult);
 
         doReturn(false).when(mDeviceStateResult).isRequestForbidden();
-        doReturn(eabResultList).when(mReqMgrCallback).getCapabilitiesFromCache(any());
+        doReturn(eabResultList).when(mReqMgrCallback)
+                .getCapabilitiesFromCacheIncludingExpired(any());
 
         // Execute the request.
         request.executeRequest();