Fix the retry logic

1. During retry, if query start is triggered by internet connection, it is ignored and retries continue.
2. The retry count is reset only when 'rebooting' or 'turning airplane mode on and off' or 'query start is triggered by after refresh time' or 'SIM removed or changed'.
3. If internet connection is disconnected and connected during retry, the retry count is maintained.
4. The following conditions are checked before starting retry query:
   - Carrier config support satellite entitlement
   - Internet connection
   - Comparing the last query time from the refresh time value in the carrier config.
   - Retry count
5. The following conditions are checked before query start:
   - Carrier config support satellite entitlement
   - Internet connection
   - Query in progress
   - Comparing the last query time from the refresh time value in the carrier config.
   - Retry count
6. Lock for the subId based objects.
7. Retries continue even if one connection is disconnected while both data and Wi-Fi are connected.
8. Consider scenarios where '503 with retry-after' and 'ExponentialBackoff' cases can alternate.
9. With the SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT set, onSatelliteEntitlementStatusUpdated is called to remove the SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT after the KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL is change to false.
10. When SIM is removed, then reset the this subId's retry related objects.

Bug: 332971062
Bug: 333936749
Test: atest SatelliteEntitlementControllerTest
Test: manual test with entitlement test app b/332971062#comment3

Change-Id: I9ac45e3161a0538816e7a62f6374e3df21fbfd07
diff --git a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java
index d193a7d..8c2693b 100644
--- a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java
+++ b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java
@@ -40,6 +40,7 @@
 import android.telephony.SubscriptionManager;
 
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.ExponentialBackoff;
 import com.android.internal.telephony.flags.FeatureFlags;
@@ -49,6 +50,7 @@
 
 import java.time.Instant;
 import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -65,7 +67,6 @@
     /** Message code used in handleMessage() */
     private static final int CMD_START_QUERY_ENTITLEMENT = 1;
     private static final int CMD_RETRY_QUERY_ENTITLEMENT = 2;
-    private static final int CMD_STOP_RETRY_QUERY_ENTITLEMENT = 3;
 
     /** Retry on next trigger event. */
     private static final int HTTP_RESPONSE_500 = 500;
@@ -74,7 +75,7 @@
     private static final int HTTP_RESPONSE_503 = 503;
     /** Default query refresh time is 1 month. */
 
-    private static final int DEFAULT_QUERY_REFRESH_DAYS = 30;
+    private static final int DEFAULT_QUERY_REFRESH_DAYS = 7;
     private static final long INITIAL_DELAY_MILLIS = TimeUnit.MINUTES.toMillis(10); // 10 min
     private static final long MAX_DELAY_MILLIS = TimeUnit.DAYS.toMillis(5); // 5 days
     private static final int MULTIPLIER = 2;
@@ -89,15 +90,25 @@
     @NonNull private final Context mContext;
     private final Object mLock = new Object();
     /** Map key : subId, value : ExponentialBackoff. */
+    @GuardedBy("mLock")
     private Map<Integer, ExponentialBackoff> mExponentialBackoffPerSub = new HashMap<>();
     /** Map key : subId, value : SatelliteEntitlementResult. */
+    @GuardedBy("mLock")
     private Map<Integer, SatelliteEntitlementResult> mSatelliteEntitlementResultPerSub =
             new HashMap<>();
     /** Map key : subId, value : the last query time to millis. */
+    @GuardedBy("mLock")
     private Map<Integer, Long> mLastQueryTimePerSub = new HashMap<>();
     /** Map key : subId, value : Count the number of retries caused by the 'ExponentialBackoff' and
      * '503 error case with the Retry-After header'. */
+    @GuardedBy("mLock")
     private Map<Integer, Integer> mRetryCountPerSub = new HashMap<>();
+    /** Map key : subId, value : Whether query is in progress. */
+    @GuardedBy("mLock")
+    private Map<Integer, Boolean> mIsEntitlementInProgressPerSub = new HashMap<>();
+    /** Map key : slotId, value : The last used subId. */
+    @GuardedBy("mLock")
+    private Map<Integer, Integer> mSubIdPerSlot = new HashMap<>();
 
     /**
      * Create the SatelliteEntitlementController singleton instance.
@@ -140,11 +151,6 @@
             public void onAvailable(Network network) {
                 handleInternetConnected();
             }
-
-            @Override
-            public void onLost(Network network) {
-                handleInternetDisconnected();
-            }
         };
         NetworkRequest networkrequest = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();
@@ -164,9 +170,6 @@
             case CMD_RETRY_QUERY_ENTITLEMENT:
                 handleCmdRetryQueryEntitlement(msg.arg1);
                 break;
-            case CMD_STOP_RETRY_QUERY_ENTITLEMENT:
-                stopExponentialBackoff(msg.arg1);
-                break;
             default:
                 logd("do not used this message");
         }
@@ -174,14 +177,35 @@
 
     private void handleCarrierConfigChanged(int slotIndex, int subId, int carrierId,
             int specificCarrierId) {
-        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            return;
-        }
         logd("handleCarrierConfigChanged(): slotIndex(" + slotIndex + "), subId("
                 + subId + "), carrierId(" + carrierId + "), specificCarrierId("
                 + specificCarrierId + ")");
+        processSimChanged(slotIndex, subId);
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return;
+        }
 
         sendEmptyMessage(CMD_START_QUERY_ENTITLEMENT);
+        synchronized (mLock) {
+            mSubIdPerSlot.put(slotIndex, subId);
+        }
+    }
+
+    // When SIM is removed or changed, then reset the previous subId's retry related objects.
+    private void processSimChanged(int slotIndex, int subId) {
+        int previousSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        synchronized (mLock) {
+            previousSubId = mSubIdPerSlot.getOrDefault(slotIndex,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        }
+        logd("processSimChanged prev subId:" + previousSubId);
+        if (previousSubId != subId) {
+            synchronized (mLock) {
+                mSubIdPerSlot.remove(slotIndex);
+            }
+            logd("processSimChanged resetEntitlementQueryPerSubId");
+            resetEntitlementQueryPerSubId(previousSubId);
+        }
     }
 
     private class SatelliteEntitlementControllerReceiver extends BroadcastReceiver {
@@ -214,15 +238,6 @@
         sendEmptyMessage(CMD_START_QUERY_ENTITLEMENT);
     }
 
-    private void handleInternetDisconnected() {
-        mExponentialBackoffPerSub.forEach((key, value) -> {
-            Message message = obtainMessage();
-            message.what = CMD_STOP_RETRY_QUERY_ENTITLEMENT;
-            message.arg1 = key;
-            sendMessage(message);
-        });
-    }
-
     /**
      * Check if the device can request to entitlement server (if there is an internet connection and
      * if the throttle time has passed since the last request), and then pass the response to
@@ -230,34 +245,44 @@
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public void handleCmdStartQueryEntitlement() {
-        if (!isInternetConnected()) {
-            logd("Internet disconnected");
-            return;
-        }
-
         for (int subId : mSubscriptionManagerService.getActiveSubIdList(true)) {
-            if (!shouldQueryEntitlement(subId)) {
-                return;
+            if (!shouldStartQueryEntitlement(subId)) {
+                continue;
             }
 
             // Check the satellite service query result from the entitlement server for the
             // satellite service.
             try {
-                mSatelliteEntitlementResultPerSub.remove(subId);
-                mSatelliteEntitlementResultPerSub.put(subId, getSatelliteEntitlementApi(
-                        subId).checkEntitlementStatus());
+                synchronized (mLock) {
+                    mIsEntitlementInProgressPerSub.put(subId, true);
+                    mSatelliteEntitlementResultPerSub.put(subId, getSatelliteEntitlementApi(
+                            subId).checkEntitlementStatus());
+                }
             } catch (ServiceEntitlementException e) {
                 loge(e.toString());
                 if (!isInternetConnected()) {
-                    logd("handleCmdStartQueryEntitlement: disconnected. " + e);
+                    logd("StartQuery: disconnected. " + e);
+                    synchronized (mLock) {
+                        mIsEntitlementInProgressPerSub.remove(subId);
+                    }
                     return;
                 }
-                if (shouldHandleErrorResponse(e, subId)) {
-                    logd("handleCmdStartQueryEntitlement: handle response.");
-                    return;
+                if (isPermanentError(e)) {
+                    logd("StartQuery: shouldPermanentError.");
+                    queryCompleted(subId);
+                    continue;
+                } else if (isRetryAfterError(e)) {
+                    long retryAfterSeconds = parseSecondsFromRetryAfter(e.getRetryAfter());
+                    logd("StartQuery: next retry will be in " + TimeUnit.SECONDS.toMillis(
+                            retryAfterSeconds) + " sec");
+                    sendMessageDelayed(obtainMessage(CMD_RETRY_QUERY_ENTITLEMENT, subId, 0),
+                            TimeUnit.SECONDS.toMillis(retryAfterSeconds));
+                    stopExponentialBackoff(subId);
+                    continue;
+                } else {
+                    startExponentialBackoff(subId);
+                    continue;
                 }
-                startExponentialBackoff(subId);
-                return;
             }
             queryCompleted(subId);
         }
@@ -267,9 +292,12 @@
      * query. */
     private void resetEntitlementQueryCounts(String event) {
         logd("resetEntitlementQueryCounts: " + event);
-        mLastQueryTimePerSub = new HashMap<>();
-        mExponentialBackoffPerSub = new HashMap<>();
-        mRetryCountPerSub = new HashMap<>();
+        synchronized (mLock) {
+            mLastQueryTimePerSub = new HashMap<>();
+            mExponentialBackoffPerSub = new HashMap<>();
+            mRetryCountPerSub = new HashMap<>();
+            mIsEntitlementInProgressPerSub = new HashMap<>();
+        }
     }
 
     /**
@@ -283,65 +311,79 @@
      * MAX_RETRY_COUNT is reached using the ExponentialBackoff.
      */
     private void handleCmdRetryQueryEntitlement(int subId) {
-        logd("handleCmdRetryQueryEntitlement: " + subId);
+        if (!shouldRetryQueryEntitlement(subId)) {
+            return;
+        }
         try {
             synchronized (mLock) {
+                int currentRetryCount = getRetryCount(subId);
+                mRetryCountPerSub.put(subId, currentRetryCount + 1);
+                logd("[" + subId + "] retry cnt:" + getRetryCount(subId));
                 mSatelliteEntitlementResultPerSub.put(subId, getSatelliteEntitlementApi(
                         subId).checkEntitlementStatus());
             }
         } catch (ServiceEntitlementException e) {
+            loge(e.toString());
+            if (!isRetryAvailable(subId)) {
+                logd("retryQuery: unavailable.");
+                queryCompleted(subId);
+                return;
+            }
             if (!isInternetConnected()) {
-                logd("retryQuery: Internet disconnected. reset the retry and after the "
-                        + "internet is connected then the first query is triggered." + e);
+                logd("retryQuery: Internet disconnected.");
                 stopExponentialBackoff(subId);
+                synchronized (mLock) {
+                    mIsEntitlementInProgressPerSub.remove(subId);
+                }
                 return;
             }
-            if (shouldHandleErrorResponse(e, subId)) {
-                logd("retryQuery: handle response.");
+            if (isPermanentError(e)) {
+                logd("retryQuery: shouldPermanentError.");
+                queryCompleted(subId);
+                return;
+            } else if (isRetryAfterError(e)) {
+                long retryAfterSeconds = parseSecondsFromRetryAfter(e.getRetryAfter());
+                logd("retryQuery: next retry will be in " + TimeUnit.SECONDS.toMillis(
+                        retryAfterSeconds) + " sec");
+                sendMessageDelayed(obtainMessage(CMD_RETRY_QUERY_ENTITLEMENT, subId, 0),
+                        TimeUnit.SECONDS.toMillis(retryAfterSeconds));
                 stopExponentialBackoff(subId);
                 return;
+            } else {
+                ExponentialBackoff exponentialBackoff = null;
+                synchronized (mLock) {
+                    exponentialBackoff = mExponentialBackoffPerSub.get(subId);
+                }
+                if (exponentialBackoff == null) {
+                    startExponentialBackoff(subId);
+                } else {
+                    exponentialBackoff.notifyFailed();
+                    logd("retryQuery: The next retry will be in "
+                            + exponentialBackoff.getCurrentDelay() + " ms.");
+                }
+                return;
             }
-            mExponentialBackoffPerSub.get(subId).notifyFailed();
-            mRetryCountPerSub.put(subId,
-                    mRetryCountPerSub.getOrDefault(subId, 0) + 1);
-            logd("handleCmdRetryQueryEntitlement:" + e + "[" + subId + "] cnt="
-                    + mRetryCountPerSub.getOrDefault(subId, 0) + "] Retrying in "
-                    + mExponentialBackoffPerSub.get(subId).getCurrentDelay() + " ms.");
         }
+        queryCompleted(subId);
     }
 
-    /** Only handle '500' and '503 with retry-after header' error responses received.
-     * If the 500 response is received, no retry until the next trigger event occurs.
-     * If the 503 response with Retry-After header, retry is attempted according to the value in the
-     * Retry-After header up to MAX_RETRY_COUNT.
-     * In other cases, it performs an exponential backoff process. */
-    private boolean shouldHandleErrorResponse(ServiceEntitlementException e, int subId) {
+    // If the 500 response is received, no retry until the next trigger event occurs.
+    private boolean isPermanentError(ServiceEntitlementException e) {
+        return e.getHttpStatus() == HTTP_RESPONSE_500;
+    }
+
+    /** If the 503 response with Retry-After header, retry is attempted according to the value in
+     * the Retry-After header up to MAX_RETRY_COUNT. */
+    private boolean isRetryAfterError(ServiceEntitlementException e) {
         int responseCode = e.getHttpStatus();
-        logd("shouldHandleErrorResponse: received the " + responseCode);
+        logd("shouldRetryAfterError: received the " + responseCode);
         if (responseCode == HTTP_RESPONSE_503 && e.getRetryAfter() != null
                 && !e.getRetryAfter().isEmpty()) {
-            if (mRetryCountPerSub.getOrDefault(subId, 0) >= MAX_RETRY_COUNT) {
-                logd("The 503 retry after reaching the " + MAX_RETRY_COUNT
-                        + "The retry will not be attempted until the next trigger event.");
-                queryCompleted(subId);
-                return true;
-            }
             long retryAfterSeconds = parseSecondsFromRetryAfter(e.getRetryAfter());
             if (retryAfterSeconds == -1) {
                 logd("Unable parsing the retry-after. try to exponential backoff.");
                 return false;
             }
-            mRetryCountPerSub.put(subId, mRetryCountPerSub.getOrDefault(subId, 0) + 1);
-            logd("[" + subId + "] cnt=" + mRetryCountPerSub.getOrDefault(subId, 0)
-                    + " Retrying in " + TimeUnit.SECONDS.toMillis(retryAfterSeconds) + " sec");
-            Message message = obtainMessage();
-            message.what = CMD_RETRY_QUERY_ENTITLEMENT;
-            message.arg1 = subId;
-            sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(retryAfterSeconds));
-            return true;
-        } else if (responseCode == HTTP_RESPONSE_500) {
-            logd("The retry on the next trigger event.");
-            queryCompleted(subId);
             return true;
         }
         return false;
@@ -364,46 +406,34 @@
     }
 
     private void startExponentialBackoff(int subId) {
+        ExponentialBackoff exponentialBackoff = null;
         stopExponentialBackoff(subId);
-        mExponentialBackoffPerSub.put(subId,
-                new ExponentialBackoff(INITIAL_DELAY_MILLIS, MAX_DELAY_MILLIS,
-                        MULTIPLIER, this.getLooper(), () -> {
-                    synchronized (mLock) {
-                        if (mSatelliteEntitlementResultPerSub.containsKey(subId)) {
-                            logd("handleCmdStartQueryEntitlement: get the response "
-                                    + "successfully.");
-                            mExponentialBackoffPerSub.get(subId).stop();
-                            queryCompleted(subId);
-                            return;
+        synchronized (mLock) {
+            mExponentialBackoffPerSub.put(subId,
+                    new ExponentialBackoff(INITIAL_DELAY_MILLIS, MAX_DELAY_MILLIS,
+                            MULTIPLIER, this.getLooper(), () -> {
+                        synchronized (mLock) {
+                            sendMessage(obtainMessage(CMD_RETRY_QUERY_ENTITLEMENT, subId, 0));
                         }
-
-                        if (mRetryCountPerSub.getOrDefault(subId, 0) >= MAX_RETRY_COUNT) {
-                            logd("The ExponentialBackoff is  stopped after reaching the "
-                                    + MAX_RETRY_COUNT + ". The retry don't attempted until the"
-                                    + " refresh time expires.");
-                            mExponentialBackoffPerSub.get(subId).stop();
-                            queryCompleted(subId);
-                            return;
-                        }
-                        if (!mSatelliteEntitlementResultPerSub.containsKey(subId)) {
-                            handleCmdRetryQueryEntitlement(subId);
-                        }
-                    }
-                }));
-        mExponentialBackoffPerSub.get(subId).start();
-        mRetryCountPerSub.put(subId, mRetryCountPerSub.getOrDefault(subId, 0) + 1);
-        logd("start ExponentialBackoff [" + mRetryCountPerSub.getOrDefault(subId, 0)
-                + "] Retrying in " + mExponentialBackoffPerSub.get(subId).getCurrentDelay()
-                + " ms.");
+                    }));
+            exponentialBackoff = mExponentialBackoffPerSub.get(subId);
+        }
+        if (exponentialBackoff != null) {
+            exponentialBackoff.start();
+            logd("start ExponentialBackoff, cnt: " + getRetryCount(subId) + ". Retrying in "
+                    + exponentialBackoff.getCurrentDelay() + " ms.");
+        }
     }
 
     /** If the Internet connection is lost during the ExponentialBackoff, stop the
      * ExponentialBackoff and reset it. */
     private void stopExponentialBackoff(int subId) {
-        if (isExponentialBackoffInProgress(subId)) {
-            logd("stopExponentialBackoff: reset ExponentialBackoff");
-            mExponentialBackoffPerSub.get(subId).stop();
-            mExponentialBackoffPerSub.remove(subId);
+        synchronized (mLock) {
+            if (mExponentialBackoffPerSub.get(subId) != null) {
+                logd("stopExponentialBackoff: reset ExponentialBackoff");
+                mExponentialBackoffPerSub.get(subId).stop();
+                mExponentialBackoffPerSub.remove(subId);
+            }
         }
     }
 
@@ -413,10 +443,18 @@
      * And then it send a delayed message to trigger the query again after A refresh day has passed.
      */
     private void queryCompleted(int subId) {
-        if (!mSatelliteEntitlementResultPerSub.containsKey(subId)) {
-            logd("queryCompleted: create default SatelliteEntitlementResult");
-            mSatelliteEntitlementResultPerSub.put(subId,
-                    SatelliteEntitlementResult.getDefaultResult());
+        SatelliteEntitlementResult entitlementResult;
+        synchronized (mLock) {
+            if (!mSatelliteEntitlementResultPerSub.containsKey(subId)) {
+                logd("queryCompleted: create default SatelliteEntitlementResult");
+                mSatelliteEntitlementResultPerSub.put(subId,
+                        SatelliteEntitlementResult.getDefaultResult());
+            }
+            entitlementResult = mSatelliteEntitlementResultPerSub.get(subId);
+            stopExponentialBackoff(subId);
+            mIsEntitlementInProgressPerSub.remove(subId);
+            logd("reset retry count for refresh query");
+            mRetryCountPerSub.remove(subId);
         }
 
         saveLastQueryTime(subId);
@@ -427,35 +465,78 @@
                 getSatelliteEntitlementStatusRefreshDays(subId)));
         logd("queryCompleted: updateSatelliteEntitlementStatus");
         updateSatelliteEntitlementStatus(subId,
-                mSatelliteEntitlementResultPerSub.get(subId).getEntitlementStatus()
-                        == SATELLITE_ENTITLEMENT_STATUS_ENABLED,
-                mSatelliteEntitlementResultPerSub.get(subId).getAllowedPLMNList(),
-                mSatelliteEntitlementResultPerSub.get(subId).getBarredPLMNList());
-        stopExponentialBackoff(subId);
-        mRetryCountPerSub.remove(subId);
+                entitlementResult.getEntitlementStatus() == SATELLITE_ENTITLEMENT_STATUS_ENABLED,
+                entitlementResult.getAllowedPLMNList(), entitlementResult.getBarredPLMNList());
     }
 
-    /** Check whether there is a saved subId. Returns true if there is a saved subId,
-     * otherwise return false.*/
-    private boolean isExponentialBackoffInProgress(int subId) {
-        return mExponentialBackoffPerSub.containsKey(subId);
+    private boolean shouldStartQueryEntitlement(int subId) {
+        logd("shouldStartQueryEntitlement " + subId);
+        if (!shouldRetryQueryEntitlement(subId)) {
+            return false;
+        }
+
+        synchronized (mLock) {
+            if (mIsEntitlementInProgressPerSub.getOrDefault(subId, false)) {
+                logd("In progress retry");
+                return false;
+            }
+        }
+        return true;
     }
 
-    /**
-     * Check if the subId can query the entitlement server to get the satellite configuration.
-     */
-    private boolean shouldQueryEntitlement(int subId) {
+    private boolean shouldRetryQueryEntitlement(int subId) {
         if (!isSatelliteEntitlementSupported(subId)) {
             logd("Doesn't support entitlement query for satellite.");
+            resetSatelliteEntitlementRestrictedReason(subId);
             return false;
         }
 
-        if (isExponentialBackoffInProgress(subId)) {
-            logd("In progress ExponentialBackoff.");
+        if (!isInternetConnected()) {
+            stopExponentialBackoff(subId);
+            synchronized (mLock) {
+                mIsEntitlementInProgressPerSub.remove(subId);
+            }
+            logd("Internet disconnected");
             return false;
         }
 
-        return shouldRefreshEntitlementStatus(subId);
+        if (!shouldRefreshEntitlementStatus(subId)) {
+            return false;
+        }
+
+        return isRetryAvailable(subId);
+    }
+
+    // update for removing the satellite entitlement restricted reason
+    private void resetSatelliteEntitlementRestrictedReason(int subId) {
+        SatelliteEntitlementResult previousResult;
+        SatelliteEntitlementResult enabledResult = new SatelliteEntitlementResult(
+                SATELLITE_ENTITLEMENT_STATUS_ENABLED, new ArrayList<>(), new ArrayList<>());
+        synchronized (mLock) {
+            previousResult = mSatelliteEntitlementResultPerSub.get(subId);
+        }
+        if (previousResult != null && previousResult.getEntitlementStatus()
+                != SATELLITE_ENTITLEMENT_STATUS_ENABLED) {
+            logd("set enabled status for removing satellite entitlement restricted reason");
+            synchronized (mLock) {
+                mSatelliteEntitlementResultPerSub.put(subId, enabledResult);
+            }
+            updateSatelliteEntitlementStatus(subId, true, enabledResult.getAllowedPLMNList(),
+                    enabledResult.getBarredPLMNList());
+        }
+        resetEntitlementQueryPerSubId(subId);
+    }
+
+    private void resetEntitlementQueryPerSubId(int subId) {
+        logd("resetEntitlementQueryPerSubId: " + subId);
+        stopExponentialBackoff(subId);
+        synchronized (mLock) {
+            mLastQueryTimePerSub.remove(subId);
+            mRetryCountPerSub.remove(subId);
+            mIsEntitlementInProgressPerSub.remove(subId);
+        }
+        removeMessages(CMD_RETRY_QUERY_ENTITLEMENT,
+                obtainMessage(CMD_RETRY_QUERY_ENTITLEMENT, subId, 0));
     }
 
     /**
@@ -489,7 +570,9 @@
     /** If there is a value stored in the cache, it is used. If there is no value stored in the
      * cache, it is considered the first query. */
     private long getLastQueryTime(int subId) {
-        return mLastQueryTimePerSub.getOrDefault(subId, 0L);
+        synchronized (mLock) {
+            return mLastQueryTimePerSub.getOrDefault(subId, 0L);
+        }
     }
 
     /** Return the satellite entitlement status refresh days from carrier config. */
@@ -499,6 +582,14 @@
                 DEFAULT_QUERY_REFRESH_DAYS);
     }
 
+    private boolean isRetryAvailable(int subId) {
+        if (getRetryCount(subId) >= MAX_RETRY_COUNT) {
+            logd("The retry will not be attempted until the next trigger event.");
+            return false;
+        }
+        return true;
+    }
+
     /** Return the satellite entitlement supported bool from carrier config. */
     private boolean isSatelliteEntitlementSupported(int subId) {
         return getConfigForSubId(subId).getBoolean(
@@ -520,7 +611,15 @@
 
     private void saveLastQueryTime(int subId) {
         long lastQueryTimeMillis = System.currentTimeMillis();
-        mLastQueryTimePerSub.put(subId, lastQueryTimeMillis);
+        synchronized (mLock) {
+            mLastQueryTimePerSub.put(subId, lastQueryTimeMillis);
+        }
+    }
+
+    private int getRetryCount(int subId) {
+        synchronized (mLock) {
+            return mRetryCountPerSub.getOrDefault(subId, 0);
+        }
     }
 
     /**
diff --git a/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java
index dd9ea65..4dcef2a 100644
--- a/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java
+++ b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java
@@ -16,8 +16,13 @@
 
 package com.android.phone.satellite.entitlement;
 
+import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_HTTP_STATUS_NOT_SUCCESS;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_DISABLED;
 import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -29,8 +34,11 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -55,6 +63,7 @@
 import com.android.internal.telephony.ExponentialBackoff;
 import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.libraries.entitlement.ServiceEntitlementException;
 
 import org.junit.After;
 import org.junit.Before;
@@ -62,6 +71,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -70,6 +81,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class SatelliteEntitlementControllerTest extends TelephonyTestBase {
@@ -77,9 +89,13 @@
     private static final int SUB_ID = 0;
     private static final int SUB_ID_2 = 1;
     private static final int[] ACTIVE_SUB_ID = {SUB_ID};
-    private static final int DEFAULT_QUERY_REFRESH_DAY = 30;
+    private static final int DEFAULT_QUERY_REFRESH_DAY = 7;
     private static final List<String> PLMN_ALLOWED_LIST = Arrays.asList("31026", "302820");
     private static final List<String> PLMN_BARRED_LIST = Arrays.asList("12345", "98765");
+    private static final List<String> EMPTY_PLMN_LIST = new ArrayList<>();
+    private static final int CMD_START_QUERY_ENTITLEMENT = 1;
+    private static final int CMD_RETRY_QUERY_ENTITLEMENT = 2;
+    private static final int MAX_RETRY_COUNT = 5;
     @Mock
     CarrierConfigManager mCarrierConfigManager;
     @Mock
@@ -90,7 +106,6 @@
     @Mock SatelliteEntitlementApi mSatelliteEntitlementApi;
     @Mock SatelliteEntitlementResult mSatelliteEntitlementResult;
     @Mock SatelliteController mSatelliteController;
-    @Mock ExponentialBackoff mExponentialBackoff;
     private PersistableBundle mCarrierConfigBundle;
     private TestSatelliteEntitlementController mSatelliteEntitlementController;
     private Handler mHandler;
@@ -157,7 +172,8 @@
     }
 
     @Test
-    public void testIsQueryAvailable() throws Exception {
+    public void testShouldStartQueryEntitlement() throws Exception {
+        logd("testShouldStartQueryEntitlement");
         doReturn(ACTIVE_SUB_ID).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
 
         // Verify don't start the query when KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL is false.
@@ -171,19 +187,8 @@
 
         mCarrierConfigBundle.putBoolean(
                 CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
-        // Verify don't start the query when ExponentialBackoff is in progressed.
-        replaceInstance(SatelliteEntitlementController.class, "mExponentialBackoffPerSub",
-                mSatelliteEntitlementController, Map.of(SUB_ID, mExponentialBackoff));
-        mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
-
-        verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
-        verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
-                anyBoolean(), anyList(), anyList(), any());
-
-        replaceInstance(SatelliteEntitlementController.class, "mExponentialBackoffPerSub",
-                mSatelliteEntitlementController, new HashMap<>());
         // Verify don't start the query when Internet is disconnected.
-        doReturn(ACTIVE_SUB_ID).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+        clearInvocationsForMock();
         setInternetConnected(false);
         mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
 
@@ -200,8 +205,36 @@
         verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
                 anyBoolean(), anyList(), anyList(), any());
 
-        // Verify start the query when isQueryAvailable return true
         setLastQueryTime(0L);
+        // Verify don't start the query when retry count is reached max
+        setLastQueryTime(0L);
+        Map<Integer, Integer> mRetryCountPerSub = new HashMap<>();
+        mRetryCountPerSub.put(SUB_ID, MAX_RETRY_COUNT);
+        replaceInstance(SatelliteEntitlementController.class, "mRetryCountPerSub",
+                mSatelliteEntitlementController, mRetryCountPerSub);
+        mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+        verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+        verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+                anyBoolean(), anyList(), anyList(), any());
+
+        replaceInstance(SatelliteEntitlementController.class, "mRetryCountPerSub",
+                mSatelliteEntitlementController, new HashMap<>());
+
+        // Verify don't start the query when query is in progressed.
+        Map<Integer, Boolean> mIsEntitlementInProgressPerSub = new HashMap<>();
+        mIsEntitlementInProgressPerSub.put(SUB_ID, true);
+        replaceInstance(SatelliteEntitlementController.class, "mIsEntitlementInProgressPerSub",
+                mSatelliteEntitlementController, mIsEntitlementInProgressPerSub);
+        mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+        verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+        verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+                anyBoolean(), anyList(), anyList(), any());
+
+        replaceInstance(SatelliteEntitlementController.class, "mIsEntitlementInProgressPerSub",
+                mSatelliteEntitlementController, new HashMap<>());
+        // Verify the query starts when ShouldStartQueryEntitlement returns true.
         doReturn(mSatelliteEntitlementResult).when(
                 mSatelliteEntitlementApi).checkEntitlementStatus();
         setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST,
@@ -215,6 +248,7 @@
 
     @Test
     public void testCheckSatelliteEntitlementStatus() throws Exception {
+        logd("testCheckSatelliteEntitlementStatus");
         setIsQueryAvailableTrue();
         // Verify don't call the checkSatelliteEntitlementStatus when getActiveSubIdList is empty.
         doReturn(new int[]{}).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
@@ -238,7 +272,7 @@
         // Verify call the updateSatelliteEntitlementStatus with satellite service is disabled
         // , empty PLMNAllowed and empty PLMNBarred.
         verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID),
-                eq(false), eq(new ArrayList<>()), eq(new ArrayList<>()), any());
+                eq(false), eq(EMPTY_PLMN_LIST), eq(EMPTY_PLMN_LIST), any());
 
         // Verify call the checkSatelliteEntitlementStatus with the subscribed result.
         clearInvocationsForMock();
@@ -276,7 +310,7 @@
 
         verify(mSatelliteEntitlementApi).checkEntitlementStatus();
         verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID), eq(true),
-                eq(PLMN_ALLOWED_LIST), eq(new ArrayList<>()), any());
+                eq(PLMN_ALLOWED_LIST), eq(EMPTY_PLMN_LIST), any());
 
         // Verify call the updateSatelliteEntitlementStatus with satellite service is enable,
         // empty PLMNAllowedList and PLMNBarredList.
@@ -288,7 +322,7 @@
 
         verify(mSatelliteEntitlementApi).checkEntitlementStatus();
         verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID), eq(true),
-                eq(new ArrayList<>()), eq(new ArrayList<>()), any());
+                eq(EMPTY_PLMN_LIST), eq(EMPTY_PLMN_LIST), any());
 
         // Verify call the updateSatelliteEntitlementStatus with satellite service is enable,
         // empty PLMNAllowedList and availablePLMNBarredList.
@@ -300,17 +334,14 @@
 
         verify(mSatelliteEntitlementApi).checkEntitlementStatus();
         verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID), eq(true),
-                eq(new ArrayList<>()), eq(PLMN_BARRED_LIST), any());
+                eq(EMPTY_PLMN_LIST), eq(PLMN_BARRED_LIST), any());
     }
 
     @Test
     public void testCheckSatelliteEntitlementStatusWhenInternetConnected() throws Exception {
-        Field fieldNetworkCallback = SatelliteEntitlementController.class.getDeclaredField(
-                "mNetworkCallback");
-        fieldNetworkCallback.setAccessible(true);
+        logd("testCheckSatelliteEntitlementStatusWhenInternetConnected");
         ConnectivityManager.NetworkCallback networkCallback =
-                (ConnectivityManager.NetworkCallback) fieldNetworkCallback.get(
-                        mSatelliteEntitlementController);
+                (ConnectivityManager.NetworkCallback) getValue("mNetworkCallback");
         Network mockNetwork = mock(Network.class);
 
         // Verify the called the checkSatelliteEntitlementStatus when Internet is connected.
@@ -330,6 +361,7 @@
 
     @Test
     public void testCheckSatelliteEntitlementStatusWhenCarrierConfigChanged() throws Exception {
+        logd("testCheckSatelliteEntitlementStatusWhenCarrierConfigChanged");
         // Verify the called the checkSatelliteEntitlementStatus when CarrierConfigChanged
         // occurred and Internet is connected.
         setInternetConnected(true);
@@ -344,6 +376,440 @@
                 eq(PLMN_ALLOWED_LIST), eq(PLMN_BARRED_LIST), any());
     }
 
+    @Test
+    public void testCheckWhenStartCmdIsReceivedDuringRetry() throws Exception {
+        logd("testCheckWhenStartCmdIsReceivedDuringRetry");
+        // Verify that start cmd is ignored and retry is performed up to 5 times when start cmd
+        // occurs during retries.
+        setIsQueryAvailableTrue();
+        set503RetryAfterResponse();
+        Map<Integer, Integer> retryCountPerSub =
+                (Map<Integer, Integer>) getValue("mRetryCountPerSub");
+
+        // Verify that the first query.
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(1)).checkEntitlementStatus();
+        // Verify that the retry count is 0 after receiving a 503 with retry-after header in
+        // response.
+        assertTrue(retryCountPerSub.getOrDefault(SUB_ID, 0) == 0);
+
+        // Verify that the retry count is 1 for the second query when receiving a 503 with
+        // retry-after header in response.
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(2)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 1);
+
+        // Verify that the retry count is 2 for the third query when receiving a 503 with
+        // retry-after header in response.
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(3)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 2);
+
+        // Verify that start CMD is ignored during retries.
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(3)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 2);
+
+        // Verify that the retry count is 3 for the forth query when receiving a 503 with
+        // retry-after header in response.
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(4)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 3);
+
+        // Verify that the retry count is 4 for the fifth query when receiving a 503 with
+        // retry-after header in response.
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(5)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 4);
+
+        // Verify that start CMD is ignored during retries.
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(5)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 4);
+
+        // Verify that the retry count is 5 for the sixth query when receiving a 503 with
+        // retry-after header in response.
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(6)).checkEntitlementStatus();
+        assertNull(retryCountPerSub.get(SUB_ID));
+
+        // Verify only called onSatelliteEntitlementStatusUpdated once.
+        verify(mSatelliteController, times(1)).onSatelliteEntitlementStatusUpdated(eq(SUB_ID),
+                eq(false), eq(EMPTY_PLMN_LIST), eq(EMPTY_PLMN_LIST), any());
+
+        // Verify that the query is not restarted after reaching the maximum retry count even if
+        // a start cmd is received.
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(6)).checkEntitlementStatus();
+        assertNull(retryCountPerSub.get(SUB_ID));
+
+        // Verify that the query is not restarted after reaching the maximum retry count even if
+        // a start cmd is received.
+        sendMessage(CMD_RETRY_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(6)).checkEntitlementStatus();
+        assertNull(retryCountPerSub.get(SUB_ID));
+    }
+
+    @Test
+    public void testCheckAfterInternetConnectionChangedDuringRetry() throws Exception {
+        logd("testCheckAfterInternetConnectionChangedDuringRetry");
+        // Verify that the retry count is maintained even when internet connection is lost and
+        // connected during retries, and that up to 5 retries are performed.
+        setIsQueryAvailableTrue();
+        set503RetryAfterResponse();
+        Map<Integer, Integer> retryCountPerSub =
+                (Map<Integer, Integer>) getValue("mRetryCountPerSub");
+
+        // Verify that the first query.
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(1)).checkEntitlementStatus();
+        // Verify that the retry count is 0 after receiving a 503 with retry-after header in
+        // response.
+        assertTrue(retryCountPerSub.getOrDefault(SUB_ID, 0) == 0);
+
+        // Verify that the retry count is 1 for the second query when receiving a 503 with
+        // retry-after header in response.
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(2)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 1);
+
+        // Verify that no query is executed and the retry count does not increase when internet
+        // connection is lost during the second retry.
+        setInternetConnected(false);
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(2)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 1);
+
+        // Verify that the query is started when internet connection is restored and that the
+        // retry count does not increase.
+        setInternetConnected(true);
+        logd("internet connected again");
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(3)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 1);
+
+        // Verify that the retry count is increases after received a 503 with retry-after header
+        // in response.
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(4)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 2);
+
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(5)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 3);
+
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(6)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 4);
+
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(7)).checkEntitlementStatus();
+        assertNull(retryCountPerSub.get(SUB_ID));
+
+        // Verify that the query is not restarted after reaching the maximum retry count even if
+        // a start cmd is received.
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(7)).checkEntitlementStatus();
+        assertNull(retryCountPerSub.get(SUB_ID));
+
+        // Verify that the query is not restarted after reaching the maximum retry count even if
+        // a retry cmd is received.
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(7)).checkEntitlementStatus();
+        assertNull(retryCountPerSub.get(SUB_ID));
+
+        // Verify only called onSatelliteEntitlementStatusUpdated once.
+        verify(mSatelliteController, times(1)).onSatelliteEntitlementStatusUpdated(eq(SUB_ID),
+                eq(false), eq(EMPTY_PLMN_LIST), eq(EMPTY_PLMN_LIST), any());
+    }
+
+    @Test
+    public void testStartQueryEntitlementStatus_error500() throws Exception {
+        logd("testStartQueryEntitlementStatus_error500");
+        setIsQueryAvailableTrue();
+        Map<Integer, Integer> retryCountPerSub =
+                (Map<Integer, Integer>) getValue("mRetryCountPerSub");
+        setErrorResponse(500);
+
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(1)).checkEntitlementStatus();
+        assertNull(retryCountPerSub.get(SUB_ID));
+        verify(mSatelliteController, times(1)).onSatelliteEntitlementStatusUpdated(eq(SUB_ID),
+                eq(false), eq(EMPTY_PLMN_LIST), eq(EMPTY_PLMN_LIST), any());
+    }
+
+    @Test
+    public void testStartQueryEntitlementStatus_error503_retrySuccess() throws Exception {
+        logd("testStartQueryEntitlementStatus_error503_retrySuccess");
+        setIsQueryAvailableTrue();
+        set503RetryAfterResponse();
+        Map<Integer, Integer> retryCountPerSub =
+                (Map<Integer, Integer>) getValue("mRetryCountPerSub");
+
+        // Verify that the first query.
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(1)).checkEntitlementStatus();
+        assertNull(retryCountPerSub.get(SUB_ID));
+
+        // Verify whether the query has been retried and verify called
+        // onSatelliteEntitlementStatusUpdated after receive a success case.
+        doReturn(mSatelliteEntitlementResult).when(
+                mSatelliteEntitlementApi).checkEntitlementStatus();
+        setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST,
+                PLMN_BARRED_LIST);
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(2)).checkEntitlementStatus();
+        assertNull(retryCountPerSub.get(SUB_ID));
+        verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID), eq(true),
+                eq(PLMN_ALLOWED_LIST), eq(PLMN_BARRED_LIST), any());
+    }
+
+    @Test
+    public void testStartQueryEntitlementStatus_otherError_retrySuccess() throws Exception {
+        logd("testStartQueryEntitlementStatus_otherError_retrySuccess");
+        setIsQueryAvailableTrue();
+        Map<Integer, Integer> retryCountPerSub =
+                (Map<Integer, Integer>) getValue("mRetryCountPerSub");
+        Map<Integer, Boolean> isEntitlementInProgressPerSub =
+                (Map<Integer, Boolean>) getValue("mIsEntitlementInProgressPerSub");
+        Map<Integer, ExponentialBackoff> exponentialBackoffPerSub =
+                (Map<Integer, ExponentialBackoff>) getValue("mExponentialBackoffPerSub");
+        setErrorResponse(400);
+
+        // Verify start the exponentialBackoff.
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(1)).checkEntitlementStatus();
+        assertNull(retryCountPerSub.get(SUB_ID));
+        assertTrue(isEntitlementInProgressPerSub.get(SUB_ID));
+        assertNotNull(exponentialBackoffPerSub.get(SUB_ID));
+        // Verify don't call the onSatelliteEntitlementStatusUpdated.
+        verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+                anyBoolean(), anyList(), anyList(), any());
+
+        // Verify the retry in progress.
+        sendMessage(CMD_RETRY_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(2)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 1);
+        // Verify don't call the onSatelliteEntitlementStatusUpdated.
+        verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+                anyBoolean(), anyList(), anyList(), any());
+
+        // Received the 200 response, Verify call the onSatelliteEntitlementStatusUpdated.
+        setIsQueryAvailableTrue();
+        doReturn(mSatelliteEntitlementResult).when(
+                mSatelliteEntitlementApi).checkEntitlementStatus();
+        setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST,
+                PLMN_BARRED_LIST);
+
+        sendMessage(CMD_RETRY_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi, times(3)).checkEntitlementStatus();
+        assertTrue(retryCountPerSub.get(SUB_ID) == 1);
+        verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID), eq(true),
+                eq(PLMN_ALLOWED_LIST), eq(PLMN_BARRED_LIST), any());
+    }
+
+    @Test
+    public void testSatelliteEntitlementSupportedChangedFromSupportToNotSupport() throws Exception {
+        logd("testSatelliteEntitlementSupportedChangedFromSupportToNotSupport");
+        setIsQueryAvailableTrue();
+
+        // KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL changed from Support(entitlement status
+        // disabled) to not support.
+        doReturn(mSatelliteEntitlementResult).when(
+                mSatelliteEntitlementApi).checkEntitlementStatus();
+        setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_DISABLED, EMPTY_PLMN_LIST,
+                EMPTY_PLMN_LIST);
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+
+        // Verify call the onSatelliteEntitlementStatusUpdated - entitlement status false
+        verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+        verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(anyInt(),
+                eq(false), eq(EMPTY_PLMN_LIST), eq(EMPTY_PLMN_LIST), any());
+
+        // Verify call the onSatelliteEntitlementStatusUpdated - entitlement status true
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+
+        verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(anyInt(),
+                eq(true), eq(EMPTY_PLMN_LIST), eq(EMPTY_PLMN_LIST), any());
+
+        // KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL changed from Support(entitlement status
+        // enabled) to not support.
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        doReturn(mSatelliteEntitlementResult).when(
+                mSatelliteEntitlementApi).checkEntitlementStatus();
+        setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST,
+                PLMN_BARRED_LIST);
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+
+        // Verify call the onSatelliteEntitlementStatusUpdated - entitlement status true.
+        verify(mSatelliteEntitlementApi, times(2)).checkEntitlementStatus();
+        verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(anyInt(),
+                eq(true), eq(PLMN_ALLOWED_LIST), eq(PLMN_BARRED_LIST), any());
+
+        // Verify not call the onSatelliteEntitlementStatusUpdated.
+        clearInvocationsForMock();
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+
+        verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+                eq(true), eq(EMPTY_PLMN_LIST), eq(EMPTY_PLMN_LIST), any());
+    }
+
+    @Test
+    public void testStartQueryEntitlementStatus_refreshStatus() throws Exception {
+        logd("testStartQueryEntitlementStatus_refreshStatus");
+        setIsQueryAvailableTrue();
+        mCarrierConfigBundle.putInt(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 1);
+
+        // Verify start query and success.
+        doReturn(mSatelliteEntitlementResult).when(
+                mSatelliteEntitlementApi).checkEntitlementStatus();
+        setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST,
+                PLMN_BARRED_LIST);
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+
+        verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+        verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(anyInt(),
+                anyBoolean(), anyList(), anyList(), any());
+
+        // After move to the refresh time, verify the query started and success.
+        setLastQueryTime(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1) - 1000);
+        mTestableLooper.moveTimeForward(TimeUnit.DAYS.toMillis(1));
+        mTestableLooper.processAllMessages();
+
+        verify(mSatelliteEntitlementApi, times(2)).checkEntitlementStatus();
+        verify(mSatelliteController, times(2)).onSatelliteEntitlementStatusUpdated(anyInt(),
+                anyBoolean(), anyList(), anyList(), any());
+    }
+
+    @Test
+    public void testStartQueryEntitlementStatus_internetDisconnectedAndConnectedAgain()
+            throws Exception {
+        logd("testStartQueryEntitlementStatus_internetDisconnectedAndConnectedAgain");
+        setIsQueryAvailableTrue();
+
+        // Verify the query does not start if there is no internet connection.
+        setInternetConnected(false);
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+
+        verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+        verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+                anyBoolean(), anyList(), anyList(), any());
+
+        // Verify the query start and success after internet connected.
+        setInternetConnected(true);
+        doReturn(mSatelliteEntitlementResult).when(
+                mSatelliteEntitlementApi).checkEntitlementStatus();
+        setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST,
+                PLMN_BARRED_LIST);
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+
+        verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+        verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID), eq(true),
+                eq(PLMN_ALLOWED_LIST), eq(PLMN_BARRED_LIST), any());
+    }
+
+    @Test
+    public void testStartQueryEntitlementStatus_error503_error500() throws Exception {
+        logd("testStartQueryEntitlementStatus_error503_error500");
+        setIsQueryAvailableTrue();
+        set503RetryAfterResponse();
+
+        // Verify that the first query was triggered and that onSatelliteEntitlementStatusUpdated
+        // was not called after received a 503 error.
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+        verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+                anyBoolean(), anyList(), anyList(), any());
+
+        // Verify whether the second query has been triggered and whether
+        // onSatelliteEntitlementStatusUpdated has been called after received the 500 error.
+        reset(mSatelliteEntitlementApi);
+        setErrorResponse(500);
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+        verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID),
+                eq(false), eq(EMPTY_PLMN_LIST), eq(EMPTY_PLMN_LIST), any());
+    }
+
+    @Test
+    public void testStartQueryEntitlementStatus_error503_otherError() throws Exception {
+        logd("testStartQueryEntitlementStatus_error503_otherError");
+        setIsQueryAvailableTrue();
+        set503RetryAfterResponse();
+
+        // Verify that the first query was triggered and that onSatelliteEntitlementStatusUpdated
+        // was not called after received a 503 error.
+        sendMessage(CMD_START_QUERY_ENTITLEMENT, SUB_ID);
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+        verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+                anyBoolean(), anyList(), anyList(), any());
+
+        // Verify whether the second query was triggered and onSatelliteEntitlementStatusUpdated
+        // was not called after received a 503 error without valid retry-after header.
+        reset(mSatelliteEntitlementApi);
+        setErrorResponse(503);
+        mTestableLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(1));
+        mTestableLooper.processAllMessages();
+        verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+        verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+                anyBoolean(), anyList(), anyList(), any());
+
+        // Verify whether the third query was triggered and onSatelliteEntitlementStatusUpdated
+        // was called after received a success case.
+        doReturn(mSatelliteEntitlementResult).when(
+                mSatelliteEntitlementApi).checkEntitlementStatus();
+        setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST,
+                PLMN_BARRED_LIST);
+        mTestableLooper.moveTimeForward(TimeUnit.MINUTES.toMillis(10));
+        mTestableLooper.processAllMessages();
+
+        verify(mSatelliteEntitlementApi, times(2)).checkEntitlementStatus();
+        verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID), eq(true),
+                eq(PLMN_ALLOWED_LIST), eq(PLMN_BARRED_LIST), any());
+    }
+
     private void triggerCarrierConfigChanged() {
         for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
                 : mCarrierConfigChangedListenerList) {
@@ -354,6 +820,16 @@
         mTestableLooper.processAllMessages();
     }
 
+    private void triggerCarrierConfigChanged(int subId) {
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ subId, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        mTestableLooper.processAllMessages();
+    }
+
     private void clearInvocationsForMock() {
         clearInvocations(mSatelliteEntitlementApi);
         clearInvocations(mSatelliteController);
@@ -365,11 +841,15 @@
                 CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
         replaceInstance(SatelliteEntitlementController.class, "mRetryCountPerSub",
                 mSatelliteEntitlementController, new HashMap<>());
+        replaceInstance(SatelliteEntitlementController.class, "mIsEntitlementInProgressPerSub",
+                mSatelliteEntitlementController, new HashMap<>());
         setInternetConnected(true);
         setLastQueryTime(0L);
         replaceInstance(SatelliteEntitlementController.class,
                 "mSatelliteEntitlementResultPerSub", mSatelliteEntitlementController,
                 new HashMap<>());
+        replaceInstance(SatelliteEntitlementController.class,
+                "mSubIdPerSlot", mSatelliteEntitlementController, new HashMap<>());
     }
 
     private void setInternetConnected(boolean connected) {
@@ -398,6 +878,41 @@
         lastQueryTimePerSub.put(SUB_ID, lastQueryTime);
     }
 
+    private void set503RetryAfterResponse() throws Exception {
+        when(mSatelliteEntitlementApi.checkEntitlementStatus()).thenAnswer(
+                new Answer() {
+                    @Override
+                    public Object answer(InvocationOnMock invocation) throws Throwable {
+                        throw new ServiceEntitlementException(
+                                ERROR_HTTP_STATUS_NOT_SUCCESS, 503, "1", "503 occurred");
+                    }
+                }
+        );
+    }
+
+    private void setErrorResponse(int errorCode) throws Exception {
+        when(mSatelliteEntitlementApi.checkEntitlementStatus()).thenAnswer(
+                new Answer() {
+                    @Override
+                    public Object answer(InvocationOnMock invocation) throws Throwable {
+                        throw new ServiceEntitlementException(
+                                ERROR_HTTP_STATUS_NOT_SUCCESS, errorCode, "",
+                                errorCode + " occurred");
+                    }
+                }
+        );
+    }
+
+    private void sendMessage(int what, int subId) {
+        mSatelliteEntitlementController.handleMessage(mHandler.obtainMessage(what, subId, 0));
+    }
+
+    private Object getValue(String originalObjectName) throws Exception {
+        Field field = SatelliteEntitlementController.class.getDeclaredField(originalObjectName);
+        field.setAccessible(true);
+        return field.get(mSatelliteEntitlementController);
+    }
+
     public static class TestSatelliteEntitlementController extends SatelliteEntitlementController {
         private SatelliteEntitlementApi mInjectSatelliteEntitlementApi;