Merge changes from topic "TelephonySatelliteNotifications" into main

* changes:
  Fix NPE by initializing notification manager
  Added flag check in SatelliteAccessController
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 4fb96a2..188baaa 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -439,7 +439,10 @@
                 mUserManager.getSerialNumbersOfUsers(/* excludeDying= */ true);
         List<UserHandle> users = new ArrayList<>(serialNumbersOfUsers.length);
         for (long serialNumber : serialNumbersOfUsers) {
-            users.add(mUserManager.getUserForSerialNumber(serialNumber));
+            UserHandle userHandle = mUserManager.getUserForSerialNumber(serialNumber);
+            if (userHandle != null) {
+                users.add(userHandle);
+            }
         }
         return users;
     }
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 440c39d..47c00af 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -13227,14 +13227,17 @@
                             return;
                         }
                         if (isAllowed) {
-                            if (mFeatureFlags.carrierRoamingNbIotNtn()
-                                    && !mSatelliteAccessController.getSatelliteDisallowedReasons()
-                                    .isEmpty()) {
-                                result.accept(SATELLITE_RESULT_ACCESS_BARRED);
-                            } else {
-                                mSatelliteController.requestSatelliteEnabled(
+                            ResultReceiver resultReceiver = new ResultReceiver(mMainThreadHandler) {
+                                @Override
+                                protected void onReceiveResult(int resultCode, Bundle resultData) {
+                                    Log.d(LOG_TAG, "updateSystemSelectionChannels resultCode="
+                                            + resultCode);
+                                    mSatelliteController.requestSatelliteEnabled(
                                         enableSatellite, enableDemoMode, isEmergency, callback);
-                            }
+                                }
+                            };
+                            mSatelliteAccessController.updateSystemSelectionChannels(
+                                    resultReceiver);
                         } else {
                             result.accept(SATELLITE_RESULT_ACCESS_BARRED);
                         }
@@ -13767,6 +13770,25 @@
     }
 
     /**
+     * Request to get the currently selected satellite subscription id.
+     *
+     * @param result The result receiver that returns the currently selected satellite subscription
+     *               id if the request is successful or an error code if the request failed.
+     *
+     * @throws SecurityException if the caller doesn't have the required permission.
+     */
+    @Override
+    public void requestSelectedNbIotSatelliteSubscriptionId(@NonNull ResultReceiver result) {
+        enforceSatelliteCommunicationPermission("requestSelectedNbIotSatelliteSubscriptionId");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSatelliteController.requestSelectedNbIotSatelliteSubscriptionId(result);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
      * Inform whether the device is aligned with the satellite in both real and demo mode.
      *
      * @param isAligned {@code true} Device is aligned with the satellite.
diff --git a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
index ef169de..ef2e80f 100644
--- a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
@@ -59,6 +59,7 @@
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
@@ -71,10 +72,12 @@
 import android.provider.DeviceConfig;
 import android.telecom.TelecomManager;
 import android.telephony.AnomalyReporter;
+import android.telephony.CarrierConfigManager;
 import android.telephony.DropBoxManagerLoggerBackend;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
 import android.telephony.satellite.ISatelliteDisallowedReasonsCallback;
@@ -83,7 +86,9 @@
 import android.telephony.satellite.SatelliteAccessConfiguration;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteSubscriberProvisionStatus;
+import android.telephony.satellite.SystemSelectionSpecifier;
 import android.text.TextUtils;
+import android.util.IntArray;
 import android.util.Pair;
 
 import com.android.internal.R;
@@ -100,6 +105,7 @@
 import com.android.internal.telephony.satellite.metrics.AccessControllerMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ConfigUpdaterMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.phone.PhoneGlobals;
 
@@ -162,6 +168,8 @@
     protected static final int EVENT_CONFIG_DATA_UPDATED = 4;
     protected static final int EVENT_COUNTRY_CODE_CHANGED = 5;
     protected static final int EVENT_LOCATION_SETTINGS_ENABLED = 6;
+    protected static final int CMD_UPDATE_SYSTEM_SELECTION_CHANNELS = 7;
+    protected static final int EVENT_LOCATION_SETTINGS_DISABLED = 8;
 
     public static final int DEFAULT_REGIONAL_SATELLITE_CONFIG_ID = 0;
     public static final int UNKNOWN_REGIONAL_SATELLITE_CONFIG_ID = -1;
@@ -246,11 +254,16 @@
     @NonNull
     private final ISatelliteProvisionStateCallback mInternalSatelliteProvisionStateCallback;
     @NonNull
+    private final ResultReceiver mInternalUpdateSystemSelectionChannelsResultReceiver;
+    @NonNull
     protected final Object mLock = new Object();
     @GuardedBy("mLock")
     @NonNull
     private final Set<ResultReceiver> mSatelliteAllowResultReceivers = new HashSet<>();
     @NonNull
+    private final Set<ResultReceiver>
+            mUpdateSystemSelectionChannelsResultReceivers = new HashSet<>();
+    @NonNull
     private List<String> mSatelliteCountryCodes;
     private boolean mIsSatelliteAllowAccessControl;
     @Nullable
@@ -292,6 +305,17 @@
     @NonNull
     private HashMap<Integer, SatelliteAccessConfiguration> mSatelliteAccessConfigMap =
             new HashMap<>();
+    @NonNull private final CarrierConfigManager mCarrierConfigManager;
+    @NonNull private final CarrierConfigManager.CarrierConfigChangeListener
+            mCarrierConfigChangeListener;
+    /**
+     * Key: Sub Id, Value: (key: Regional satellite config Id, value: SatelliteRegionalConfig
+     * contains satellite config IDs and set of earfcns in the corresponding regions).
+     */
+    @GuardedBy("mRegionalSatelliteEarfcnsLock")
+    private Map<Integer, Map<Integer, SatelliteRegionalConfig>>
+            mSatelliteRegionalConfigPerSubMap = new HashMap();
+    @NonNull private final Object mRegionalSatelliteEarfcnsLock = new Object();
 
     /** These are used for CTS test */
     private Path mCtsSatS2FilePath = null;
@@ -375,6 +399,9 @@
                 if (mLocationManager.isLocationEnabled()) {
                     plogd("Location settings is just enabled");
                     sendRequestAsync(EVENT_LOCATION_SETTINGS_ENABLED, null);
+                } else {
+                    plogd("Location settings is just enabled");
+                    sendRequestAsync(EVENT_LOCATION_SETTINGS_DISABLED, null);
                 }
             }
         }
@@ -453,6 +480,8 @@
         mNotifySatelliteAvailabilityEnabled =
                 context.getResources().getBoolean(
                         R.bool.config_satellite_should_notify_availability);
+        initializeSatelliteSystemNotification(context);
+        registerDefaultSmsAppChangedBroadcastReceiver(context);
 
         mInternalSatelliteSupportedStateCallback = new ISatelliteSupportedStateCallback.Stub() {
             @Override
@@ -532,10 +561,27 @@
                 mInternalSatelliteProvisionStateCallback);
         plogd("registerForSatelliteProvisionStateChanged result: " + result);
 
+        mInternalUpdateSystemSelectionChannelsResultReceiver = new ResultReceiver(this) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                plogd("UpdateSystemSelectionChannels.onReceiveResult: resultCode=" + resultCode
+                          + ", resultData=" + resultData);
+                sendUpdateSystemSelectionChannelsResult(resultCode, resultData);
+            }
+        };
+
         // Init the SatelliteOnDeviceAccessController so that the S2 level can be cached
         initSatelliteOnDeviceAccessController();
         registerLocationModeChangedBroadcastReceiver(context);
-        registerDefaultSmsAppChangedBroadcastReceiver(context);
+
+        mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
+        mCarrierConfigChangeListener =
+                (slotIndex, subId, carrierId, specificCarrierId) ->
+                        handleCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId);
+        if (mCarrierConfigManager != null) {
+            mCarrierConfigManager.registerCarrierConfigChangeListener(
+                    new HandlerExecutor(new Handler(looper)), mCarrierConfigChangeListener);
+        }
     }
 
     private void updateCurrentSatelliteAllowedState(boolean isAllowed) {
@@ -591,10 +637,14 @@
                 updateSatelliteConfigData((Context) ar.userObj);
                 break;
             case EVENT_LOCATION_SETTINGS_ENABLED:
+            case EVENT_LOCATION_SETTINGS_DISABLED:
                 // Fall through
             case EVENT_COUNTRY_CODE_CHANGED:
                 handleSatelliteAllowedRegionPossiblyChanged(msg.what);
                 break;
+            case CMD_UPDATE_SYSTEM_SELECTION_CHANNELS:
+                handleCmdUpdateSystemSelectionChannels((ResultReceiver) msg.obj);
+                break;
             default:
                 plogw("SatelliteAccessControllerHandler: unexpected message code: " + msg.what);
                 break;
@@ -734,6 +784,28 @@
         return true;
     }
 
+    /**
+     * Report updated system selection to modem and report the update result.
+     */
+    public void updateSystemSelectionChannels(@NonNull ResultReceiver result) {
+        plogd("updateSystemSelectionChannels");
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("updateSystemSelectionChannels: "
+                    + "carrierRoamingNbIotNtn flag is disabled");
+            result.send(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
+            return;
+        }
+        synchronized (mLock) {
+            if (mRegionalConfigId == null) {
+                plogd("updateSystemSelectionChannels: Invalid Regional config ID."
+                        + " System Selection channels can not be passed down to modem");
+                result.send(SatelliteManager.SATELLITE_RESULT_ACCESS_BARRED, null);
+                return;
+            }
+        }
+        sendRequestAsync(CMD_UPDATE_SYSTEM_SELECTION_CHANNELS, result);
+    }
+
     protected File getTestSatelliteS2File(String fileName) {
         plogd("getTestSatelliteS2File: fileName=" + fileName);
         if (TextUtils.equals(fileName, GOOGLE_US_SAN_SAT_S2_FILE_NAME)) {
@@ -758,15 +830,22 @@
         Path targetDir = ctsFile.toPath();
         Path targetSatS2FilePath = targetDir.resolve(sourceFileName);
         try {
-            InputStream inputStream = phoneGlobals.getAssets().open(sourceFileName);
+            var assetManager = phoneGlobals.getAssets();
+            if (assetManager == null) {
+                loge("copyTestSatS2FileToPhoneDirectory: no assets");
+                return null;
+            }
+            InputStream inputStream = assetManager.open(sourceFileName);
             if (inputStream == null) {
                 loge("copyTestSatS2FileToPhoneDirectory: Resource=" + sourceFileName
                         + " not found");
+                return null;
             } else {
                 Files.copy(inputStream, targetSatS2FilePath, StandardCopyOption.REPLACE_EXISTING);
             }
         } catch (IOException ex) {
             loge("copyTestSatS2FileToPhoneDirectory: ex=" + ex);
+            return null;
         }
         return targetSatS2FilePath;
     }
@@ -1330,6 +1409,11 @@
     }
 
     private void showSatelliteSystemNotification() {
+        if (mNotificationManager == null) {
+            logd("showSatelliteSystemNotification: NotificationManager is null");
+            return;
+        }
+
         if (mSatelliteDisallowedReasons.isEmpty()) {
             if (!hasAlreadyNotified(KEY_AVAILABLE_NOTIFICATION_SHOWN, 0)) {
                 mNotificationManager.notifyAsUser(
@@ -2070,6 +2154,78 @@
         }
     }
 
+    private void handleCmdUpdateSystemSelectionChannels(
+            @NonNull ResultReceiver resultReceiver) {
+        synchronized (mLock) {
+            mUpdateSystemSelectionChannelsResultReceivers.add(resultReceiver);
+            if (mUpdateSystemSelectionChannelsResultReceivers.size() > 1) {
+                plogd("updateSystemSelectionChannels is already being processed");
+                return;
+            }
+            int subId =  mSatelliteController.getSelectedSatelliteSubId();
+            plogd("handleCmdUpdateSystemSelectionChannels: SatellitePhone subId: " + subId);
+            if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                sendUpdateSystemSelectionChannelsResult(
+                        SATELLITE_RESULT_INVALID_TELEPHONY_STATE, null);
+                return;
+            }
+
+            String mccmnc = "";
+            final SubscriptionInfo subInfo = SubscriptionManagerService.getInstance()
+                    .getSubscriptionInfo(subId);
+            if (subInfo != null) {
+                mccmnc = subInfo.getMccString() + subInfo.getMncString();
+            }
+
+            synchronized (mRegionalSatelliteEarfcnsLock) {
+                /* Key: Regional satellite config ID, Value: SatelliteRegionalConfig
+                 * contains satellite config IDs and set of earfcns in the corresponding regions.
+                 */
+                Map<Integer, SatelliteRegionalConfig> satelliteRegionalConfigMap =
+                        mSatelliteRegionalConfigPerSubMap.get(subId);
+                if (satelliteRegionalConfigMap == null || satelliteRegionalConfigMap.isEmpty()) {
+                    plogd("handleCmdUpdateSystemSelectionChannels: config IDs and Earfcns are not"
+                            + " found for subId: "
+                            + subId);
+                    sendUpdateSystemSelectionChannelsResult(
+                            SATELLITE_RESULT_INVALID_TELEPHONY_STATE, null);
+                    return;
+                }
+
+                SatelliteRegionalConfig satelliteRegionalConfig = satelliteRegionalConfigMap.get(
+                        mRegionalConfigId);
+                if (satelliteRegionalConfig == null) {
+                    plogd("handleCmdUpdateSystemSelectionChannels: "
+                            + "Earfcns for satellite config Id: " + mRegionalConfigId
+                            + " not found");
+                    sendUpdateSystemSelectionChannelsResult(
+                            SATELLITE_RESULT_INVALID_TELEPHONY_STATE, null);
+                    return;
+                }
+                IntArray bands = new IntArray();
+                IntArray earfcns = new IntArray();
+                for (Integer value : satelliteRegionalConfig.getEarfcns()) {
+                    earfcns.add(value);
+                }
+
+                mSatelliteController.updateSystemSelectionChannels(
+                        new SystemSelectionSpecifier(mccmnc, bands, earfcns),
+                        mInternalUpdateSystemSelectionChannelsResultReceiver);
+            }
+        }
+    }
+
+    private void sendUpdateSystemSelectionChannelsResult(int resultCode, Bundle resultData) {
+        plogd("sendUpdateSystemSelectionChannelsResult: resultCode=" + resultCode);
+
+        synchronized (mLock) {
+            for (ResultReceiver resultReceiver : mUpdateSystemSelectionChannelsResultReceivers) {
+                resultReceiver.send(resultCode, resultData);
+            }
+            mUpdateSystemSelectionChannelsResultReceivers.clear();
+        }
+    }
+
     private static boolean getSatelliteAccessAllowFromOverlayConfig(@NonNull Context context) {
         Boolean accessAllowed = null;
         try {
@@ -2615,6 +2771,69 @@
                 resetRequired);
     }
 
+    private static final class SatelliteRegionalConfig {
+        /** Regional satellite config IDs */
+        private final int mConfigId;
+
+        /** Set of earfcns in the corresponding regions */
+        private final Set<Integer> mEarfcns;
+
+        SatelliteRegionalConfig(int configId, Set<Integer> earfcns) {
+            this.mConfigId = configId;
+            this.mEarfcns = earfcns;
+        }
+
+        public Set<Integer> getEarfcns() {
+            return mEarfcns;
+        }
+    }
+
+    private void updateSatelliteRegionalConfig(int subId) {
+        plogd("updateSatelliteRegionalConfig: subId: " + subId);
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return;
+        }
+
+        mSatelliteController.updateRegionalSatelliteEarfcns(subId);
+        //key: regional satellite config Id,
+        //value: set of earfcns in the corresponding regions
+        Map<String, Set<Integer>> earfcnsMap = mSatelliteController
+                .getRegionalSatelliteEarfcns(subId);
+        if (earfcnsMap.isEmpty()) {
+            plogd("updateSatelliteRegionalConfig: Earfcns are not found for subId: "
+                    + subId);
+            return;
+        }
+
+        synchronized (mRegionalSatelliteEarfcnsLock) {
+            SatelliteRegionalConfig satelliteRegionalConfig;
+            /* Key: Regional satellite config ID, Value: SatelliteRegionalConfig
+             * contains satellite config IDs and set of earfcns in the corresponding regions.
+             */
+            Map<Integer, SatelliteRegionalConfig> satelliteRegionalConfigMap = new HashMap<>();
+            for (String configId: earfcnsMap.keySet()) {
+                Set<Integer> earfcnsSet = new HashSet<>();
+                for (int earfcn : earfcnsMap.get(configId)) {
+                    earfcnsSet.add(earfcn);
+                }
+                satelliteRegionalConfig = new SatelliteRegionalConfig(Integer.valueOf(configId),
+                        earfcnsSet);
+                satelliteRegionalConfigMap.put(Integer.valueOf(configId), satelliteRegionalConfig);
+            }
+
+            mSatelliteRegionalConfigPerSubMap.put(subId, satelliteRegionalConfigMap);
+        }
+    }
+
+    private void handleCarrierConfigChanged(int slotIndex, int subId,
+            int carrierId, int specificCarrierId) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("handleCarrierConfigChanged: carrierRoamingNbIotNtn flag is disabled");
+            return;
+        }
+        updateSatelliteRegionalConfig(subId);
+    }
+
     private void plogv(@NonNull String log) {
         Rlog.v(TAG, log);
         if (mPersistentLogger != null) {
diff --git a/tests/src/com/android/TelephonyTestBase.java b/tests/src/com/android/TelephonyTestBase.java
index 3e74eb7..94e91d3 100644
--- a/tests/src/com/android/TelephonyTestBase.java
+++ b/tests/src/com/android/TelephonyTestBase.java
@@ -35,6 +35,8 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.data.DataConfigManager;
 import com.android.internal.telephony.data.DataNetworkController;
+import com.android.internal.telephony.metrics.MetricsCollector;
+import com.android.internal.telephony.metrics.PersistAtomsStorage;
 import com.android.phone.PhoneGlobals;
 import com.android.phone.PhoneInterfaceManager;
 
@@ -65,6 +67,7 @@
     @Mock protected PhoneGlobals mPhoneGlobals;
     @Mock protected GsmCdmaPhone mPhone;
     @Mock protected DataNetworkController mDataNetworkController;
+    @Mock private MetricsCollector mMetricsCollector;
 
     private final HashMap<InstanceKey, Object> mOldInstances = new HashMap<>();
     private final LinkedList<InstanceKey> mInstanceKeys = new LinkedList<>();
@@ -92,6 +95,9 @@
         replaceInstance(PhoneFactory.class, "sPhone", null, mPhone);
         replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
         replaceInstance(PhoneGlobals.class, "sMe", null, mPhoneGlobals);
+        replaceInstance(PhoneFactory.class, "sMetricsCollector", null, mMetricsCollector);
+
+        doReturn(Mockito.mock(PersistAtomsStorage.class)).when(mMetricsCollector).getAtomsStorage();
 
         doReturn(mDataNetworkController).when(mPhone).getDataNetworkController();
         doReturn(Collections.emptyList()).when(mDataNetworkController)
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index a684ef5..bf7832a 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -21,13 +21,18 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
@@ -44,7 +49,10 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
@@ -87,6 +95,11 @@
     }
 
     @Override
+    public AssetManager getAssets() {
+        return Mockito.mock(AssetManager.class);
+    }
+
+    @Override
     public Executor getMainExecutor() {
         // Just run on current thread
         return Runnable::run;
@@ -98,6 +111,11 @@
     }
 
     @Override
+    public @NonNull Context createAttributionContext(@Nullable String attributionTag) {
+        return this;
+    }
+
+    @Override
     public String getPackageName() {
         return "com.android.phone.tests";
     }
@@ -226,6 +244,11 @@
         return new Handler(Looper.getMainLooper());
     }
 
+    @Override
+    public Resources.Theme getTheme() {
+        return InstrumentationRegistry.getTargetContext().getTheme();
+    }
+
     /**
      * @return CarrierConfig PersistableBundle for the subscription specified.
      */
@@ -274,6 +297,11 @@
         }
     }
 
+    @Override
+    public void unbindService(ServiceConnection conn) {
+        // Override the base implementation to ensure we don't crash.
+    }
+
     public void grantPermission(String permission) {
         synchronized (mPermissionTable) {
             if (permission == null) return;
diff --git a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
index 5c18cc5..b388954 100644
--- a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
+++ b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
@@ -49,6 +49,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -87,7 +88,6 @@
 import android.os.CancellationSignal;
 import android.os.DropBoxManager;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
@@ -101,12 +101,14 @@
 import android.telephony.satellite.SatelliteAccessConfiguration;
 import android.telephony.satellite.SatelliteInfo;
 import android.telephony.satellite.SatelliteManager;
+import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Log;
 import android.util.Pair;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.InstrumentationRegistry;
 
+import com.android.TelephonyTestBase;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyCountryDetector;
@@ -115,6 +117,7 @@
 import com.android.internal.telephony.satellite.SatelliteConfigParser;
 import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.satellite.SatelliteModemInterface;
+import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 
 import org.junit.After;
 import org.junit.Before;
@@ -123,10 +126,8 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 import java.io.File;
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -141,8 +142,9 @@
 import java.util.function.Consumer;
 
 /** Unit test for {@link SatelliteAccessController} */
-@RunWith(AndroidJUnit4.class)
-public class SatelliteAccessControllerTest {
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class SatelliteAccessControllerTest extends TelephonyTestBase {
     private static final String TAG = "SatelliteAccessControllerTest";
     private static final String[] TEST_SATELLITE_COUNTRY_CODES = {"US", "CA", "UK"};
     private static final String[] TEST_SATELLITE_COUNTRY_CODES_EMPTY = {""};
@@ -176,8 +178,7 @@
     private SatelliteModemInterface mMockSatelliteModemInterface;
     @Mock
     private DropBoxManager mMockDropBoxManager;
-    @Mock
-    private Context mMockContext;
+    private Context mMockContext;  // alias of mContext
     @Mock
     private Phone mMockPhone;
     @Mock
@@ -224,7 +225,6 @@
     private ConcurrentHashMap<IBinder, ISatelliteCommunicationAllowedStateCallback>
             mSatelliteCommunicationAllowedStateCallbackMap;
 
-    private Looper mLooper;
     private TestableLooper mTestableLooper;
     private Phone[] mPhones;
     private TestSatelliteAccessController mSatelliteAccessControllerUT;
@@ -289,17 +289,10 @@
 
     @Before
     public void setUp() throws Exception {
-        logd("setUp");
-        MockitoAnnotations.initMocks(this);
+        super.setUp();
 
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
-        HandlerThread handlerThread = new HandlerThread("SatelliteAccessControllerTest");
-        handlerThread.start();
-        mLooper = handlerThread.getLooper();
-        mTestableLooper = new TestableLooper(mLooper);
+        mMockContext = mContext;
+        mTestableLooper = TestableLooper.get(this);
         when(mMockContext.getSystemServiceName(LocationManager.class)).thenReturn(
                 Context.LOCATION_SERVICE);
         when(mMockContext.getSystemServiceName(TelecomManager.class)).thenReturn(
@@ -312,6 +305,11 @@
                 mMockTelecomManager);
         when(mMockContext.getSystemService(DropBoxManager.class)).thenReturn(
                 mMockDropBoxManager);
+        doAnswer(inv -> {
+            var args = inv.getArguments();
+            return InstrumentationRegistry.getTargetContext()
+                    .getDir((String) args[0], (Integer) args[1]);
+        }).when(mPhoneGlobals).getDir(anyString(), anyInt());
         mPhones = new Phone[]{mMockPhone, mMockPhone2};
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
         replaceInstance(SatelliteController.class, "sInstance", null,
@@ -320,6 +318,8 @@
                 mMockSatelliteModemInterface);
         replaceInstance(TelephonyCountryDetector.class, "sInstance", null,
                 mMockCountryDetector);
+        replaceInstance(ControllerMetricsStats.class, "sInstance", null,
+                mock(ControllerMetricsStats.class));
         when(mMockSatelliteController.getSatellitePhone()).thenReturn(mMockPhone);
         when(mMockPhone.getSubId()).thenReturn(SubscriptionManager.getDefaultSubscriptionId());
 
@@ -361,8 +361,8 @@
                 any(SatelliteOnDeviceAccessController.LocationToken.class)))
                 .thenReturn(DEFAULT_REGIONAL_SATELLITE_CONFIG_ID);
 
-        when(mMockContext.getSharedPreferences(anyString(), anyInt())).thenReturn(
-                mMockSharedPreferences);
+        doReturn(mMockSharedPreferences).when(mMockContext)
+                .getSharedPreferences(anyString(), anyInt());
         when(mMockSharedPreferences.getBoolean(anyString(), anyBoolean())).thenReturn(true);
         when(mMockSharedPreferences.getStringSet(anyString(), any()))
                 .thenReturn(Set.of(TEST_SATELLITE_COUNTRY_CODES));
@@ -392,28 +392,19 @@
                 NotificationManager.class)).thenReturn(Context.NOTIFICATION_SERVICE);
         when(mMockContext.getSystemService(Context.NOTIFICATION_SERVICE))
                 .thenReturn(mMockNotificationManager);
-        when(mMockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo);
+        doReturn(mMockApplicationInfo).when(mMockContext).getApplicationInfo();
         mMockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
         when(mMockPackageManager.getApplicationInfo(anyString(), anyInt()))
                 .thenReturn(mMockApplicationInfo);
         mSatelliteAccessControllerUT = new TestSatelliteAccessController(mMockContext,
-                mMockFeatureFlags, mLooper, mMockLocationManager, mMockTelecomManager,
-                mMockSatelliteOnDeviceAccessController, mMockSatS2File);
+                mMockFeatureFlags, mTestableLooper.getLooper(), mMockLocationManager,
+                mMockTelecomManager, mMockSatelliteOnDeviceAccessController, mMockSatS2File);
         mTestableLooper.processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
-        logd("tearDown");
-        if (mTestableLooper != null) {
-            mTestableLooper.destroy();
-            mTestableLooper = null;
-        }
-
-        if (mLooper != null) {
-            mLooper.quit();
-            mLooper = null;
-        }
+        super.tearDown();
     }
 
     @Test
@@ -1480,6 +1471,7 @@
         doReturn(mockConfig).when(mMockSatelliteController).getSatelliteConfig();
         File testS2File = mSatelliteAccessControllerUT
                 .getTestSatelliteS2File(GOOGLE_US_SAN_SAT_S2_FILE_NAME);
+        assumeTrue("Satellite not supported", testS2File != null && testS2File.exists());
         doReturn(List.of(TEST_SATELLITE_COUNTRY_CODES))
                 .when(mockConfig).getDeviceSatelliteCountryCodes();
         doReturn(true).when(mockConfig).isSatelliteDataForAllowedRegion();
@@ -1800,13 +1792,6 @@
         Log.d(TAG, message);
     }
 
-    private static void replaceInstance(final Class c,
-            final String instanceName, final Object obj, final Object newValue) throws Exception {
-        Field field = c.getDeclaredField(instanceName);
-        field.setAccessible(true);
-        field.set(obj, newValue);
-    }
-
     private static class TestSatelliteAccessController extends SatelliteAccessController {
         public long elapsedRealtimeNanos = 0;
 
diff --git a/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java
index 9f8a733..f41f861 100644
--- a/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java
+++ b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java
@@ -60,9 +60,6 @@
 
 import com.android.TelephonyTestBase;
 import com.android.internal.telephony.ExponentialBackoff;
-import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.metrics.MetricsCollector;
-import com.android.internal.telephony.metrics.PersistAtomsStorage;
 import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.libraries.entitlement.ServiceEntitlementException;
@@ -72,7 +69,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
@@ -105,7 +101,6 @@
     @Mock Network mNetwork;
     @Mock TelephonyManager mTelephonyManager;
     @Mock SubscriptionManagerService mMockSubscriptionManagerService;
-    @Mock private MetricsCollector mMetricsCollector;
     @Mock SatelliteEntitlementApi mSatelliteEntitlementApi;
     @Mock SatelliteEntitlementResult mSatelliteEntitlementResult;
     @Mock SatelliteController mSatelliteController;
@@ -123,8 +118,6 @@
         replaceInstance(SubscriptionManagerService.class, "sInstance", null,
                 mMockSubscriptionManagerService);
         replaceInstance(SatelliteController.class, "sInstance", null, mSatelliteController);
-        replaceInstance(PhoneFactory.class, "sMetricsCollector", null, mMetricsCollector);
-        doReturn(Mockito.mock(PersistAtomsStorage.class)).when(mMetricsCollector).getAtomsStorage();
 
         mTestableLooper = TestableLooper.get(this);
         mHandler = new Handler(mTestableLooper.getLooper());