Merge "Validate cached allowed state when MCC change." into main
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 611b6a2..2cd0336 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -13972,9 +13972,9 @@
         TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID,
                 "setCachedLocationCountryCode");
-        return TelephonyCountryDetector.getInstance(getDefaultPhone().getContext()).setCountryCodes(
-                reset, currentNetworkCountryCodes, cachedNetworkCountryCodes, locationCountryCode,
-                locationCountryCodeTimestampNanos);
+        return TelephonyCountryDetector.getInstance(getDefaultPhone().getContext(), mFeatureFlags)
+                .setCountryCodes(reset, currentNetworkCountryCodes, cachedNetworkCountryCodes,
+                        locationCountryCode, locationCountryCodeTimestampNanos);
     }
 
     /**
diff --git a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
index d6a9b37..75a1fb4 100644
--- a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
@@ -85,7 +85,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
-import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -130,10 +129,11 @@
     private static final boolean DEBUG = !"user".equals(Build.TYPE);
     private static final int MAX_CACHE_SIZE = 50;
 
-    private static final int CMD_IS_SATELLITE_COMMUNICATION_ALLOWED = 1;
+    protected static final int CMD_IS_SATELLITE_COMMUNICATION_ALLOWED = 1;
     protected static final int EVENT_WAIT_FOR_CURRENT_LOCATION_TIMEOUT = 2;
     protected static final int EVENT_KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT = 3;
     protected static final int EVENT_CONFIG_DATA_UPDATED = 4;
+    protected static final int CMD_HANDLE_COUNTRY_CODE_CHANGED = 5;
 
     private static SatelliteAccessController sInstance;
 
@@ -224,6 +224,25 @@
     @Nullable
     private PersistentLogger mPersistentLogger = null;
 
+    private final Object mPossibleChangeInSatelliteAllowedRegionLock = new Object();
+    @GuardedBy("mPossibleChangeInSatelliteAllowedRegionLock")
+    private boolean mIsSatelliteAllowedRegionPossiblyChanged = false;
+    protected long mLastLocationQueryForPossibleChangeInAllowedRegionTimeNanos = 0;
+
+    protected int mRetryCountForValidatingPossibleChangeInAllowedRegion;
+    protected static final int
+            DEFAULT_DELAY_MINUTES_BEFORE_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION = 10;
+    protected static final int
+            DEFAULT_MAX_RETRY_COUNT_FOR_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION = 3;
+    protected static final int DEFAULT_THROTTLE_INTERVAL_FOR_LOCATION_QUERY_MINUTES = 10;
+
+    private long mRetryIntervalToEvaluateUserInSatelliteAllowedRegion = 0;
+    private int mMaxRetryCountForValidatingPossibleChangeInAllowedRegion = 0;
+    private long mLocationQueryThrottleIntervalNanos = 0;
+
+    @NonNull
+    protected ResultReceiver mHandlerForSatelliteAllowedResult;
+
     /**
      * Map key: binder of the callback, value: callback to receive the satellite communication
      * allowed state changed events.
@@ -234,10 +253,11 @@
     @GuardedBy("mSatelliteCommunicationAllowStateLock")
     private boolean mCurrentSatelliteAllowedState = false;
 
-    private static final long ALLOWED_STATE_CACHE_VALID_DURATION_HOURS =
-            Duration.ofHours(4).toNanos();
+    protected static final long ALLOWED_STATE_CACHE_VALID_DURATION_NANOS =
+            TimeUnit.HOURS.toNanos(4);
+
     private boolean mLatestSatelliteCommunicationAllowed;
-    private long mLatestSatelliteCommunicationAllowedSetTime;
+    protected long mLatestSatelliteCommunicationAllowedSetTime;
 
     private long mLocationQueryStartTimeMillis;
     private long mOnDeviceLookupStartTimeMillis;
@@ -249,10 +269,13 @@
      * @param context                           The context associated with the
      *                                          {@link SatelliteAccessController} instance.
      * @param featureFlags                      The FeatureFlags that are supported.
-     * @param locationManager                   The LocationManager for querying current location of
+     * @param locationManager                   The LocationManager for querying current
+     *                                          location of
      *                                          the device.
-     * @param looper                            The Looper to run the SatelliteAccessController on.
-     * @param satelliteOnDeviceAccessController The on-device satellite access controller instance.
+     * @param looper                            The Looper to run the SatelliteAccessController
+     *                                          on.
+     * @param satelliteOnDeviceAccessController The on-device satellite access controller
+     *                                          instance.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected SatelliteAccessController(@NonNull Context context,
@@ -269,7 +292,13 @@
         mLocationManager = locationManager;
         mTelecomManager = telecomManager;
         mSatelliteOnDeviceAccessController = satelliteOnDeviceAccessController;
-        mCountryDetector = TelephonyCountryDetector.getInstance(context);
+
+        mCountryDetector = TelephonyCountryDetector.getInstance(context, mFeatureFlags);
+        mCountryDetector.registerForCountryCodeChanged(this,
+                CMD_HANDLE_COUNTRY_CODE_CHANGED, null);
+        initializeHandlerForSatelliteAllowedResult();
+        setIsSatelliteAllowedRegionPossiblyChanged(false);
+
         mSatelliteController = SatelliteController.getInstance();
         mControllerMetricsStats = ControllerMetricsStats.getInstance();
         mAccessControllerMetricsStats = AccessControllerMetricsStats.getInstance();
@@ -391,6 +420,9 @@
                 AsyncResult ar = (AsyncResult) msg.obj;
                 updateSatelliteConfigData((Context) ar.userObj);
                 break;
+            case CMD_HANDLE_COUNTRY_CODE_CHANGED:
+                handleSatelliteAllowedRegionPossiblyChanged();
+                break;
             default:
                 plogw("SatelliteAccessControllerHandler: unexpected message code: " + msg.what);
                 break;
@@ -752,6 +784,11 @@
         mLocationFreshDurationNanos = getSatelliteLocationFreshDurationFromOverlayConfig(context);
         mAccessControllerMetricsStats.setConfigDataSource(
                 SatelliteConstants.CONFIG_DATA_SOURCE_DEVICE_CONFIG);
+        mRetryIntervalToEvaluateUserInSatelliteAllowedRegion =
+                getDelayBeforeRetryValidatingPossibleChangeInSatelliteAllowedRegionMillis(context);
+        mMaxRetryCountForValidatingPossibleChangeInAllowedRegion =
+                getMaxRetryCountForValidatingPossibleChangeInAllowedRegion(context);
+        mLocationQueryThrottleIntervalNanos = getLocationQueryThrottleIntervalNanos(context);
     }
 
     private void loadConfigUpdaterConfigs() {
@@ -938,11 +975,15 @@
             }
             mSatelliteAllowResultReceivers.clear();
         }
+        if (!shouldRetryValidatingPossibleChangeInAllowedRegion(resultCode)) {
+            setIsSatelliteAllowedRegionPossiblyChanged(false);
+        }
         reportMetrics(resultCode, allowed);
     }
 
     /**
-     * Telephony-internal logic to verify if satellite access is restricted at the current location.
+     * Telephony-internal logic to verify if satellite access is restricted at the current
+     * location.
      */
     private void checkSatelliteAccessRestrictionForCurrentLocation() {
         synchronized (mLock) {
@@ -972,6 +1013,89 @@
         }
     }
 
+    private boolean shouldRetryValidatingPossibleChangeInAllowedRegion(int resultCode) {
+        return (resultCode == SATELLITE_RESULT_LOCATION_NOT_AVAILABLE);
+    }
+
+    private void initializeHandlerForSatelliteAllowedResult() {
+        mHandlerForSatelliteAllowedResult = new ResultReceiver(null) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                plogd("query satellite allowed for current "
+                        + "location, resultCode=" + resultCode + ", resultData=" + resultData);
+                synchronized (mPossibleChangeInSatelliteAllowedRegionLock) {
+                    if (shouldRetryValidatingPossibleChangeInAllowedRegion(resultCode)
+                            && (mRetryCountForValidatingPossibleChangeInAllowedRegion
+                            < mMaxRetryCountForValidatingPossibleChangeInAllowedRegion)) {
+                        mRetryCountForValidatingPossibleChangeInAllowedRegion++;
+                        plogd("mRetryCountForValidatingPossibleChangeInAllowedRegion is "
+                                + mRetryCountForValidatingPossibleChangeInAllowedRegion);
+                        sendDelayedRequestAsync(CMD_IS_SATELLITE_COMMUNICATION_ALLOWED,
+                                new Pair<>(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                                        mHandlerForSatelliteAllowedResult),
+                                mRetryIntervalToEvaluateUserInSatelliteAllowedRegion);
+                    } else {
+                        mRetryCountForValidatingPossibleChangeInAllowedRegion = 0;
+                        plogd("Stop retry validating the possible change in satellite allowed "
+                                + "region");
+                    }
+                }
+            }
+        };
+    }
+
+    private void handleSatelliteAllowedRegionPossiblyChanged() {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            ploge("handleSatelliteAllowedRegionPossiblyChanged: "
+                    + "The feature flag oemEnabledSatelliteFlag() is not enabled");
+            return;
+        }
+        synchronized (mPossibleChangeInSatelliteAllowedRegionLock) {
+            logd("handleSatelliteAllowedRegionPossiblyChanged");
+            setIsSatelliteAllowedRegionPossiblyChanged(true);
+            requestIsCommunicationAllowedForCurrentLocation(
+                    SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    mHandlerForSatelliteAllowedResult);
+        }
+    }
+
+    protected boolean allowLocationQueryForSatelliteAllowedCheck() {
+        synchronized (mPossibleChangeInSatelliteAllowedRegionLock) {
+            if (!isCommunicationAllowedCacheValid()) {
+                logd("allowLocationQueryForSatelliteAllowedCheck: cache is not valid");
+                return true;
+            }
+
+            if (isSatelliteAllowedRegionPossiblyChanged() && !isLocationQueryThrottled()) {
+                logd("allowLocationQueryForSatelliteAllowedCheck: location query is not throttled");
+                return true;
+            }
+        }
+        logd("allowLocationQueryForSatelliteAllowedCheck: false");
+        return false;
+    }
+
+    private boolean isLocationQueryThrottled() {
+        if (mLastLocationQueryForPossibleChangeInAllowedRegionTimeNanos == 0) {
+            plogv("isLocationQueryThrottled: "
+                    + "mLastLocationQueryForPossibleChangeInAllowedRegionTimeNanos is 0, return "
+                    + "false");
+            return false;
+        }
+
+        long currentTime = getElapsedRealtimeNanos();
+        if (currentTime - mLastLocationQueryForPossibleChangeInAllowedRegionTimeNanos
+                > mLocationQueryThrottleIntervalNanos) {
+            plogv("isLocationQueryThrottled: currentTime - "
+                    + "mLastLocationQueryForPossibleChangeInAllowedRegionTimeNanos is "
+                    + "bigger than " + mLocationQueryThrottleIntervalNanos + " so return false");
+            return false;
+        }
+
+        plogd("isLocationQueryThrottled : true");
+        return true;
+    }
+
     /**
      * Telephony-internal logic to verify if satellite access is restricted from the location query.
      */
@@ -982,14 +1106,14 @@
         } else {
             if (mLocationManager.isLocationEnabled()) {
                 plogd("location query is allowed");
-                if (isCommunicationAllowedCacheValid()) {
+                if (allowLocationQueryForSatelliteAllowedCheck()) {
+                    executeLocationQuery();
+                } else {
                     Bundle bundle = new Bundle();
                     bundle.putBoolean(KEY_SATELLITE_COMMUNICATION_ALLOWED,
                             mLatestSatelliteCommunicationAllowed);
                     sendSatelliteAllowResultToReceivers(SATELLITE_RESULT_SUCCESS, bundle,
                             mLatestSatelliteCommunicationAllowed);
-                } else {
-                    executeLocationQuery();
                 }
             } else {
                 plogv("location query is not allowed");
@@ -1006,9 +1130,9 @@
      */
     private boolean isCommunicationAllowedCacheValid() {
         if (mLatestSatelliteCommunicationAllowedSetTime > 0) {
-            long currentTime = SystemClock.elapsedRealtimeNanos();
+            long currentTime = getElapsedRealtimeNanos();
             if ((currentTime - mLatestSatelliteCommunicationAllowedSetTime)
-                    <= ALLOWED_STATE_CACHE_VALID_DURATION_HOURS) {
+                    <= ALLOWED_STATE_CACHE_VALID_DURATION_NANOS) {
                 logv("isCommunicationAllowedCacheValid: cache is valid");
                 return true;
             }
@@ -1018,7 +1142,15 @@
     }
 
     private void executeLocationQuery() {
-        plogv("executeLocationQuery");
+        plogd("executeLocationQuery");
+        synchronized (mPossibleChangeInSatelliteAllowedRegionLock) {
+            if (isSatelliteAllowedRegionPossiblyChanged()) {
+                mLastLocationQueryForPossibleChangeInAllowedRegionTimeNanos =
+                        getElapsedRealtimeNanos();
+                plogd("mLastLocationQueryForPossibleChangeInAllowedRegionTimeNanos is set "
+                        + mLastLocationQueryForPossibleChangeInAllowedRegionTimeNanos);
+            }
+        }
         synchronized (mLock) {
             mFreshLastKnownLocation = getFreshLastKnownLocation();
             checkSatelliteAccessRestrictionUsingOnDeviceData();
@@ -1167,7 +1299,7 @@
                 sendSatelliteAllowResultToReceivers(SATELLITE_RESULT_SUCCESS, bundle,
                         satelliteAllowed);
                 mLatestSatelliteCommunicationAllowed = satelliteAllowed;
-                mLatestSatelliteCommunicationAllowedSetTime = SystemClock.elapsedRealtimeNanos();
+                mLatestSatelliteCommunicationAllowedSetTime = getElapsedRealtimeNanos();
                 persistLatestSatelliteCommunicationAllowedState();
             } catch (Exception ex) {
                 ploge("checkSatelliteAccessRestrictionForLocation: ex=" + ex);
@@ -1267,6 +1399,9 @@
             long lastKnownLocationAge =
                     getElapsedRealtimeNanos() - lastKnownLocation.getElapsedRealtimeNanos();
             if (lastKnownLocationAge <= getLocationFreshDurationNanos()) {
+                plogd("getFreshLastKnownLocation: lat=" + Rlog.pii(TAG,
+                        lastKnownLocation.getLatitude())
+                        + ", long=" + Rlog.pii(TAG, lastKnownLocation.getLongitude()));
                 return lastKnownLocation;
             }
         }
@@ -1436,6 +1571,63 @@
     }
 
     @NonNull
+    private static long getDelayBeforeRetryValidatingPossibleChangeInSatelliteAllowedRegionMillis(
+            @NonNull Context context) {
+        Integer retryDuration = null;
+        try {
+            retryDuration = context.getResources().getInteger(com.android.internal.R.integer
+                    .config_satellite_delay_minutes_before_retry_validating_possible_change_in_allowed_region);
+        } catch (Resources.NotFoundException ex) {
+            loge("getDelayBeforeRetryValidatingPossibleChangeInSatelliteAllowedRegionMillis: got "
+                    + "ex=" + ex);
+        }
+        if (retryDuration == null) {
+            logd("Use default retry duration for possible change satellite allowed region ="
+                    + DEFAULT_DELAY_MINUTES_BEFORE_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION);
+            retryDuration =
+                    DEFAULT_DELAY_MINUTES_BEFORE_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION;
+        }
+        return TimeUnit.MINUTES.toMillis(retryDuration);
+    }
+
+    @NonNull
+    private static int getMaxRetryCountForValidatingPossibleChangeInAllowedRegion(
+            @NonNull Context context) {
+        Integer maxRetrycount = null;
+        try {
+            maxRetrycount = context.getResources().getInteger(com.android.internal.R.integer
+                    .config_satellite_max_retry_count_for_validating_possible_change_in_allowed_region);
+        } catch (Resources.NotFoundException ex) {
+            loge("getMaxRetryCountForValidatingPossibleChangeInAllowedRegion: got ex= " + ex);
+        }
+        if (maxRetrycount == null) {
+            logd("Use default max retry count for possible change satellite allowed region ="
+                    + DEFAULT_MAX_RETRY_COUNT_FOR_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION);
+            maxRetrycount =
+                    DEFAULT_MAX_RETRY_COUNT_FOR_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION;
+        }
+        return maxRetrycount;
+    }
+
+    @NonNull
+    private static long getLocationQueryThrottleIntervalNanos(@NonNull Context context) {
+        Integer throttleInterval = null;
+        try {
+            throttleInterval = context.getResources().getInteger(com.android.internal.R.integer
+                    .config_satellite_location_query_throttle_interval_minutes);
+        } catch (Resources.NotFoundException ex) {
+            loge("getLocationQueryThrottleIntervalNanos: got ex=" + ex);
+        }
+        if (throttleInterval == null) {
+            logd("Use default location query throttle interval ="
+                    + DEFAULT_THROTTLE_INTERVAL_FOR_LOCATION_QUERY_MINUTES);
+            throttleInterval =
+                    DEFAULT_THROTTLE_INTERVAL_FOR_LOCATION_QUERY_MINUTES;
+        }
+        return TimeUnit.MINUTES.toNanos(throttleInterval);
+    }
+
+    @NonNull
     private static String[] readStringArrayFromOverlayConfig(
             @NonNull Context context, @ArrayRes int id) {
         String[] strArray = null;
@@ -1525,6 +1717,17 @@
      * @param command  command to be executed on the main thread
      * @param argument additional parameters required to perform of the operation
      */
+    private void sendDelayedRequestAsync(int command, @NonNull Object argument, long dealyMillis) {
+        Message msg = this.obtainMessage(command, argument);
+        sendMessageDelayed(msg, dealyMillis);
+    }
+
+    /**
+     * Posts the specified command to be executed on the main thread and returns immediately.
+     *
+     * @param command  command to be executed on the main thread
+     * @param argument additional parameters required to perform of the operation
+     */
     private void sendRequestAsync(int command, @NonNull Object argument) {
         Message msg = this.obtainMessage(command, argument);
         msg.sendToTarget();
@@ -1550,11 +1753,6 @@
 
         mSatelliteCommunicationAllowedStateChangedListeners.put(callback.asBinder(), callback);
 
-        if (!mFeatureFlags.geofenceEnhancementForBetterUx()) {
-            plogd("The feature flag geofenceEnhancementForBetterUx is not enabled");
-            return SATELLITE_RESULT_SUCCESS;
-        }
-
         this.post(() -> {
             try {
                 synchronized (mSatelliteCommunicationAllowStateLock) {
@@ -1596,7 +1794,7 @@
      * This API can be used by only CTS to set the cache whether satellite communication is allowed.
      *
      * @param state a state indicates whether satellite access allowed state should be cached and
-     * the allowed state.
+     *              the allowed state.
      * @return {@code true} if the setting is successful, {@code false} otherwise.
      */
     public boolean setIsSatelliteCommunicationAllowedForCurrentLocationCache(String state) {
@@ -1616,7 +1814,7 @@
 
         synchronized (mSatelliteCommunicationAllowStateLock) {
             if ("cache_allowed".equalsIgnoreCase(state)) {
-                mLatestSatelliteCommunicationAllowedSetTime = SystemClock.elapsedRealtimeNanos();
+                mLatestSatelliteCommunicationAllowedSetTime = getElapsedRealtimeNanos();
                 mLatestSatelliteCommunicationAllowed = true;
                 mCurrentSatelliteAllowedState = true;
             } else if ("cache_clear_and_not_allowed".equalsIgnoreCase(state)) {
@@ -1673,6 +1871,19 @@
         mTotalCheckingStartTimeMillis = 0;
     }
 
+    protected boolean isSatelliteAllowedRegionPossiblyChanged() {
+        synchronized (mPossibleChangeInSatelliteAllowedRegionLock) {
+            return mIsSatelliteAllowedRegionPossiblyChanged;
+        }
+    }
+
+    protected void setIsSatelliteAllowedRegionPossiblyChanged(boolean changed) {
+        synchronized (mPossibleChangeInSatelliteAllowedRegionLock) {
+            plogd("setIsSatelliteAllowedRegionPossiblyChanged : " + changed);
+            mIsSatelliteAllowedRegionPossiblyChanged = changed;
+        }
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
index 8eba53b..cbca7b7 100644
--- a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
+++ b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
@@ -22,8 +22,14 @@
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.ALLOWED_STATE_CACHE_VALID_DURATION_NANOS;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.CMD_HANDLE_COUNTRY_CODE_CHANGED;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.CMD_IS_SATELLITE_COMMUNICATION_ALLOWED;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.DEFAULT_DELAY_MINUTES_BEFORE_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.DEFAULT_THROTTLE_INTERVAL_FOR_LOCATION_QUERY_MINUTES;
 import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.EVENT_CONFIG_DATA_UPDATED;
 import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.GOOGLE_US_SAN_SAT_S2_FILE_NAME;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.DEFAULT_MAX_RETRY_COUNT_FOR_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -34,10 +40,12 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -63,6 +71,7 @@
 import android.os.Message;
 import android.os.ResultReceiver;
 import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.satellite.SatelliteManager;
 import android.testing.TestableLooper;
 import android.util.Log;
@@ -111,6 +120,8 @@
     private static final int TEST_LOCATION_FRESH_DURATION_SECONDS = 10;
     private static final long TEST_LOCATION_FRESH_DURATION_NANOS =
             TimeUnit.SECONDS.toNanos(TEST_LOCATION_FRESH_DURATION_SECONDS);
+    private static final long TEST_LOCATION_QUERY_THROTTLE_INTERVAL_NANOS =
+            TimeUnit.MINUTES.toNanos(10);  // DEFAULT_THROTTLE_INTERVAL_FOR_LOCATION_QUERY_MINUTES
     private static final long TIMEOUT = 500;
     private static final List<String> EMPTY_STRING_LIST = new ArrayList<>();
     private static final List<String> LOCATION_PROVIDERS =
@@ -170,6 +181,13 @@
     private ArgumentCaptor<Integer> mConfigUpdateIntCaptor;
     @Captor
     private ArgumentCaptor<Object> mConfigUpdateObjectCaptor;
+    @Captor
+    private ArgumentCaptor<Handler> mCountryDetectorHandlerCaptor;
+    @Captor
+    private ArgumentCaptor<Integer> mCountryDetectorIntCaptor;
+    @Captor
+    private ArgumentCaptor<Object> mCountryDetectorObjCaptor;
+
     private boolean mQueriedSatelliteAllowed = false;
     private int mQueriedSatelliteAllowedResultCode = SATELLITE_RESULT_SUCCESS;
     private Semaphore mSatelliteAllowedSemaphore = new Semaphore(0);
@@ -243,6 +261,17 @@
         when(mMockResources.getInteger(com.android.internal.R.integer
                 .config_oem_enabled_satellite_location_fresh_duration))
                 .thenReturn(TEST_LOCATION_FRESH_DURATION_SECONDS);
+        when(mMockResources.getInteger(com.android.internal.R.integer
+                .config_satellite_delay_minutes_before_retry_validating_possible_change_in_allowed_region))
+                .thenReturn(
+                        DEFAULT_DELAY_MINUTES_BEFORE_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION);
+        when(mMockResources.getInteger(com.android.internal.R.integer
+                .config_satellite_max_retry_count_for_validating_possible_change_in_allowed_region))
+                .thenReturn(
+                        DEFAULT_MAX_RETRY_COUNT_FOR_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION);
+        when(mMockResources.getInteger(com.android.internal.R.integer
+                .config_satellite_location_query_throttle_interval_minutes))
+                .thenReturn(DEFAULT_THROTTLE_INTERVAL_FOR_LOCATION_QUERY_MINUTES);
 
         when(mMockLocationManager.getProviders(true)).thenReturn(LOCATION_PROVIDERS);
         when(mMockLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER))
@@ -266,8 +295,12 @@
                 .putBoolean(anyString(), anyBoolean());
         doReturn(mMockSharedPreferencesEditor).when(mMockSharedPreferencesEditor)
                 .putStringSet(anyString(), any());
+        doReturn(mMockSharedPreferencesEditor).when(mMockSharedPreferencesEditor)
+                .putLong(anyString(), anyLong());
+        doNothing().when(mMockSharedPreferencesEditor).apply();
 
         when(mMockFeatureFlags.satellitePersistentLogging()).thenReturn(true);
+        when(mMockFeatureFlags.geofenceEnhancementForBetterUx()).thenReturn(true);
 
         mSatelliteAccessControllerUT = new TestSatelliteAccessController(mMockContext,
                 mMockFeatureFlags, mLooper, mMockLocationManager, mMockTelecomManager,
@@ -457,6 +490,136 @@
     }
 
     @Test
+    public void testAllowLocationQueryForSatelliteAllowedCheck() {
+        mSatelliteAccessControllerUT.mLatestSatelliteCommunicationAllowedSetTime = 1;
+
+        mSatelliteAccessControllerUT.setIsSatelliteAllowedRegionPossiblyChanged(false);
+        // cash is invalid
+        mSatelliteAccessControllerUT.elapsedRealtimeNanos =
+                ALLOWED_STATE_CACHE_VALID_DURATION_NANOS + 10;
+        assertTrue(mSatelliteAccessControllerUT.allowLocationQueryForSatelliteAllowedCheck());
+
+        // cash is valid
+        mSatelliteAccessControllerUT.elapsedRealtimeNanos =
+                ALLOWED_STATE_CACHE_VALID_DURATION_NANOS - 10;
+        assertFalse(mSatelliteAccessControllerUT.allowLocationQueryForSatelliteAllowedCheck());
+
+        mSatelliteAccessControllerUT.setIsSatelliteAllowedRegionPossiblyChanged(true);
+        // cash is invalid
+        mSatelliteAccessControllerUT.elapsedRealtimeNanos =
+                ALLOWED_STATE_CACHE_VALID_DURATION_NANOS + 10;
+        assertTrue(mSatelliteAccessControllerUT.allowLocationQueryForSatelliteAllowedCheck());
+
+        // cash is valid and throttled
+        mSatelliteAccessControllerUT.elapsedRealtimeNanos =
+                ALLOWED_STATE_CACHE_VALID_DURATION_NANOS - 10;
+
+        // cash is valid and never queried before
+        mSatelliteAccessControllerUT.mLastLocationQueryForPossibleChangeInAllowedRegionTimeNanos = 0;
+        assertTrue(mSatelliteAccessControllerUT.allowLocationQueryForSatelliteAllowedCheck());
+
+        // cash is valid and throttled
+        mSatelliteAccessControllerUT.mLastLocationQueryForPossibleChangeInAllowedRegionTimeNanos =
+                mSatelliteAccessControllerUT.elapsedRealtimeNanos
+                - TEST_LOCATION_QUERY_THROTTLE_INTERVAL_NANOS + 100;
+        assertFalse(mSatelliteAccessControllerUT.allowLocationQueryForSatelliteAllowedCheck());
+
+        // cash is valid and not throttled
+        mSatelliteAccessControllerUT.mLastLocationQueryForPossibleChangeInAllowedRegionTimeNanos =
+                mSatelliteAccessControllerUT.elapsedRealtimeNanos
+                - TEST_LOCATION_QUERY_THROTTLE_INTERVAL_NANOS - 100;
+        assertTrue(mSatelliteAccessControllerUT.allowLocationQueryForSatelliteAllowedCheck());
+    }
+
+    @Test
+    public void testValidatePossibleChangeInSatelliteAllowedRegion() throws Exception {
+        // OEM-enabled satellite is supported
+        when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+        verify(mMockCountryDetector).registerForCountryCodeChanged(
+                mCountryDetectorHandlerCaptor.capture(), mCountryDetectorIntCaptor.capture(),
+                mCountryDetectorObjCaptor.capture());
+
+        assertSame(mCountryDetectorHandlerCaptor.getValue(), mSatelliteAccessControllerUT);
+        assertSame(mCountryDetectorIntCaptor.getValue(), CMD_HANDLE_COUNTRY_CODE_CHANGED);
+        assertNull(mCountryDetectorObjCaptor.getValue());
+
+        // Normal case that invokes
+        // mMockSatelliteOnDeviceAccessController.isSatCommunicationAllowedAtLocation
+        clearInvocations(mMockSatelliteOnDeviceAccessController);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        doReturn(true).when(mMockLocationManager).isLocationEnabled();
+        mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS;
+        sendCommandValidateCountryCodeChangeEvent(mMockContext);
+        verify(mMockSatelliteOnDeviceAccessController,
+                times(1)).isSatCommunicationAllowedAtLocation(
+                any(SatelliteOnDeviceAccessController.LocationToken.class));
+
+        // Case that isCommunicationAllowedCacheValid is true
+        clearInvocations(mMockSatelliteOnDeviceAccessController);
+        mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS + 1;
+        sendCommandValidateCountryCodeChangeEvent(mMockContext);
+        verify(mMockSatelliteOnDeviceAccessController, never()).isSatCommunicationAllowedAtLocation(
+                any(SatelliteOnDeviceAccessController.LocationToken.class));
+
+        // Case that mLatestCacheEnforcedValidateTimeNanos is over
+        // ALLOWED_STATE_CACHE_VALIDATE_INTERVAL_NANOS (1hours)
+        clearInvocations(mMockSatelliteOnDeviceAccessController);
+        mSatelliteAccessControllerUT.elapsedRealtimeNanos =
+                mSatelliteAccessControllerUT.elapsedRealtimeNanos
+                        + TEST_LOCATION_QUERY_THROTTLE_INTERVAL_NANOS + 1;
+        when(mMockLocation0.getElapsedRealtimeNanos())
+                .thenReturn(mSatelliteAccessControllerUT.elapsedRealtimeNanos + 1L);
+        when(mMockLocation1.getElapsedRealtimeNanos())
+                .thenReturn(mSatelliteAccessControllerUT.elapsedRealtimeNanos + 1L);
+        when(mMockLocation0.getLatitude()).thenReturn(2.0);
+        when(mMockLocation0.getLongitude()).thenReturn(2.0);
+        when(mMockLocation1.getLatitude()).thenReturn(3.0);
+        when(mMockLocation1.getLongitude()).thenReturn(3.0);
+        when(mMockSatelliteOnDeviceAccessController.isSatCommunicationAllowedAtLocation(
+                any(SatelliteOnDeviceAccessController.LocationToken.class))).thenReturn(false);
+        sendCommandValidateCountryCodeChangeEvent(mMockContext);
+        verify(mMockSatelliteOnDeviceAccessController,
+                times(1)).isSatCommunicationAllowedAtLocation(
+                any(SatelliteOnDeviceAccessController.LocationToken.class));
+    }
+
+    @Test
+    public void testRetryValidatePossibleChangeInSatelliteAllowedRegion() throws Exception {
+        when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+        verify(mMockCountryDetector).registerForCountryCodeChanged(
+                mCountryDetectorHandlerCaptor.capture(), mCountryDetectorIntCaptor.capture(),
+                mCountryDetectorObjCaptor.capture());
+
+        assertSame(mCountryDetectorHandlerCaptor.getValue(), mSatelliteAccessControllerUT);
+        assertSame(mCountryDetectorIntCaptor.getValue(), CMD_HANDLE_COUNTRY_CODE_CHANGED);
+        assertNull(mCountryDetectorObjCaptor.getValue());
+
+        assertTrue(mSatelliteAccessControllerUT
+                .getRetryCountPossibleChangeInSatelliteAllowedRegion() == 0);
+
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_LOCATION_NOT_AVAILABLE);
+        sendCommandValidateCountryCodeChangeEvent(mMockContext);
+
+        assertTrue(mSatelliteAccessControllerUT
+                .getRetryCountPossibleChangeInSatelliteAllowedRegion() == 1);
+
+        mSatelliteAccessControllerUT.setRetryCountPossibleChangeInSatelliteAllowedRegion(
+                DEFAULT_MAX_RETRY_COUNT_FOR_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION);
+        sendSatelliteCommunicationAllowedEvent();
+        assertTrue(mSatelliteAccessControllerUT
+                .getRetryCountPossibleChangeInSatelliteAllowedRegion() == 0);
+
+        mSatelliteAccessControllerUT.setRetryCountPossibleChangeInSatelliteAllowedRegion(2);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        sendSatelliteCommunicationAllowedEvent();
+        assertTrue(mSatelliteAccessControllerUT
+                .getRetryCountPossibleChangeInSatelliteAllowedRegion() == 0);
+    }
+
+    @Test
     public void testUpdateSatelliteConfigData() throws Exception {
         verify(mMockSatelliteController).registerForConfigUpdateChanged(
                 mConfigUpdateHandlerCaptor.capture(), mConfigUpdateIntCaptor.capture(),
@@ -528,6 +691,18 @@
         verify(mMockCachedAccessRestrictionMap, times(1)).clear();
     }
 
+    private void sendSatelliteCommunicationAllowedEvent() {
+        Pair<Integer, ResultReceiver> requestPair =
+                new Pair<>(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                        mSatelliteAccessControllerUT.getResultReceiverCurrentLocation());
+        Message msg = mSatelliteAccessControllerUT.obtainMessage(
+                CMD_IS_SATELLITE_COMMUNICATION_ALLOWED);
+        msg.obj = requestPair;
+        msg.sendToTarget();
+        mTestableLooper.processAllMessages();
+    }
+
+
     private void sendConfigUpdateChangedEvent(Context context) {
         Message msg = mSatelliteAccessControllerUT.obtainMessage(EVENT_CONFIG_DATA_UPDATED);
         msg.obj = new AsyncResult(context, SATELLITE_RESULT_SUCCESS, null);
@@ -535,6 +710,13 @@
         mTestableLooper.processAllMessages();
     }
 
+    private void sendCommandValidateCountryCodeChangeEvent(Context context) {
+        Message msg = mSatelliteAccessControllerUT.obtainMessage(CMD_HANDLE_COUNTRY_CODE_CHANGED);
+        msg.obj = new AsyncResult(context, SATELLITE_RESULT_SUCCESS, null);
+        msg.sendToTarget();
+        mTestableLooper.processAllMessages();
+    }
+
     private void clearAllInvocations() {
         clearInvocations(mMockSatelliteController);
         clearInvocations(mMockSatelliteOnDeviceAccessController);
@@ -678,5 +860,17 @@
         public boolean isWaitForCurrentLocationTimerStarted() {
             return hasMessages(EVENT_WAIT_FOR_CURRENT_LOCATION_TIMEOUT);
         }
+
+        public int getRetryCountPossibleChangeInSatelliteAllowedRegion() {
+            return mRetryCountForValidatingPossibleChangeInAllowedRegion;
+        }
+
+        public void setRetryCountPossibleChangeInSatelliteAllowedRegion(int retryCount) {
+            mRetryCountForValidatingPossibleChangeInAllowedRegion = retryCount;
+        }
+
+        public ResultReceiver getResultReceiverCurrentLocation() {
+            return mHandlerForSatelliteAllowedResult;
+        }
     }
 }