Merge "Add satellite access controller atom into metrics" into main
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index 4b70d29..48e7b0d 100644
--- a/proto/src/persist_atoms.proto
+++ b/proto/src/persist_atoms.proto
@@ -23,7 +23,7 @@
 
 // Holds atoms to store on persist storage in case of power cycle or process crash.
 // NOTE: using int64 rather than google.protobuf.Timestamp for timestamps simplifies implementation.
-// Next id: 80
+// Next id: 82
 message PersistAtoms {
     /* Aggregated RAT usage during the call. */
     repeated VoiceCallRatUsage voice_call_rat_usage = 1;
@@ -261,6 +261,12 @@
 
     /* Timestamp of last satellite_config_updater pull. */
     optional int64 satellite_config_updater_pull_timestamp_millis = 79;
+
+    /** Snapshot of satellite access controller. */
+    repeated SatelliteAccessController satellite_access_controller = 80;
+
+    /* Timestamp of last satellite access controller pull. */
+    optional int64 satellite_access_controller_pull_timestamp_millis = 81;
 }
 
 // The canonical versions of the following enums live in:
@@ -708,6 +714,9 @@
     optional int32 count_of_demo_mode_incoming_datagram_fail = 23;
     optional int32 count_of_datagram_type_keep_alive_success = 24;
     optional int32 count_of_datagram_type_keep_alive_fail = 25;
+    optional int32 count_of_allowed_satellite_access = 26;
+    optional int32 count_of_disallowed_satellite_access = 27;
+    optional int32 count_of_satellite_access_check_fail = 28;
 }
 
 message SatelliteSession {
@@ -812,3 +821,15 @@
     optional int32 carrier_config_result = 3;
     optional int32 count = 4;
 }
+
+message SatelliteAccessController {
+    optional int32 access_control_type = 1;
+    optional int64 location_query_time_millis = 2;
+    optional int64 on_device_lookup_time_millis = 3;
+    optional int64 total_checking_time_millis = 4;
+    optional bool is_allowed = 5;
+    optional bool is_emergency = 6;
+    optional int32 result_code = 7;
+    repeated string country_codes = 8;
+    optional int32 config_data_source = 9;
+}
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index e6f25bb..a83cd06 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -39,6 +39,7 @@
 import static com.android.internal.telephony.TelephonyStatsLog.PRESENCE_NOTIFY_EVENT;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_ACCESS_CONTROLLER;
 import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_CONFIG_UPDATER;
 import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_CONTROLLER;
 import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_ENTITLEMENT;
@@ -95,6 +96,7 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteAccessController;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
@@ -242,6 +244,7 @@
             registerAtom(CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS);
             registerAtom(SATELLITE_ENTITLEMENT);
             registerAtom(SATELLITE_CONFIG_UPDATER);
+            registerAtom(SATELLITE_ACCESS_CONTROLLER);
             Rlog.d(TAG, "registered");
         } else {
             Rlog.e(TAG, "could not get StatsManager, atoms not registered");
@@ -346,6 +349,8 @@
                 return pullSatelliteEntitlement(data);
             case SATELLITE_CONFIG_UPDATER:
                 return pullSatelliteConfigUpdater(data);
+            case SATELLITE_ACCESS_CONTROLLER:
+                return pullSatelliteAccessController(data);
             default:
                 Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
                 return StatsManager.PULL_SKIP;
@@ -1032,6 +1037,19 @@
         }
     }
 
+    private int pullSatelliteAccessController(List<StatsEvent> data) {
+        SatelliteAccessController[] satelliteAccessControllerAtoms =
+                mStorage.getSatelliteAccessControllerStats(MIN_COOLDOWN_MILLIS);
+        if (satelliteAccessControllerAtoms != null) {
+            Arrays.stream(satelliteAccessControllerAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_ACCESS_CONTROLLER pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
     /** Registers a pulled atom ID {@code atomId}. */
     private void registerAtom(int atomId) {
         mStatsManager.setPullAtomCallback(atomId, /* metadata= */ null,
@@ -1453,7 +1471,10 @@
                 satelliteController.countOfDemoModeIncomingDatagramSuccess,
                 satelliteController.countOfDemoModeIncomingDatagramFail,
                 satelliteController.countOfDatagramTypeKeepAliveSuccess,
-                satelliteController.countOfDatagramTypeKeepAliveFail);
+                satelliteController.countOfDatagramTypeKeepAliveFail,
+                satelliteController.countOfAllowedSatelliteAccess,
+                satelliteController.countOfDisallowedSatelliteAccess,
+                satelliteController.countOfSatelliteAccessCheckFail);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteSession satelliteSession) {
@@ -1578,6 +1599,20 @@
                 stats.count);
     }
 
+    private static StatsEvent buildStatsEvent(SatelliteAccessController stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                SATELLITE_ACCESS_CONTROLLER,
+                stats.accessControlType,
+                stats.locationQueryTimeMillis,
+                stats.onDeviceLookupTimeMillis,
+                stats.totalCheckingTimeMillis,
+                stats.isAllowed,
+                stats.isEmergency,
+                stats.resultCode,
+                stats.countryCodes,
+                stats.configDataSource);
+    }
+
     /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
     static Phone[] getPhonesIfAny() {
         try {
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index 975750e..12dab7a 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -51,6 +51,7 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteAccessController;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
@@ -766,6 +767,9 @@
                 += stats.countOfDatagramTypeKeepAliveSuccess;
         atom.countOfDatagramTypeKeepAliveFail
                 += stats.countOfDatagramTypeKeepAliveFail;
+        atom.countOfAllowedSatelliteAccess += stats.countOfAllowedSatelliteAccess;
+        atom.countOfDisallowedSatelliteAccess += stats.countOfDisallowedSatelliteAccess;
+        atom.countOfSatelliteAccessCheckFail += stats.countOfSatelliteAccessCheckFail;
 
         mAtoms.satelliteController = atomArray;
         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
@@ -894,6 +898,14 @@
         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
     }
 
+    /** Adds a new {@link SatelliteAccessController} to the storage. */
+    public synchronized void addSatelliteAccessControllerStats(SatelliteAccessController stats) {
+        mAtoms.satelliteAccessController =
+                insertAtRandomPlace(mAtoms.satelliteAccessController, stats,
+                        mMaxNumSatelliteStats);
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
     /**
      * Returns and clears the voice call sessions if last pulled longer than {@code
      * minIntervalMillis} ago, otherwise returns {@code null}.
@@ -1542,7 +1554,7 @@
             long minIntervalMillis) {
         if (getWallTimeMillis() - mAtoms.satelliteSosMessageRecommenderPullTimestampMillis
                 > minIntervalMillis) {
-            mAtoms.satelliteProvisionPullTimestampMillis = getWallTimeMillis();
+            mAtoms.satelliteSosMessageRecommenderPullTimestampMillis = getWallTimeMillis();
             SatelliteSosMessageRecommender[] statsArray = mAtoms.satelliteSosMessageRecommender;
             mAtoms.satelliteSosMessageRecommender = new SatelliteSosMessageRecommender[0];
             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
@@ -1648,6 +1660,25 @@
         }
     }
 
+    /**
+     * Returns and clears the {@link SatelliteAccessController} stats if last pulled longer
+     * than {@code minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteAccessController[] getSatelliteAccessControllerStats(
+            long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteAccessControllerPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteAccessControllerPullTimestampMillis = getWallTimeMillis();
+            SatelliteAccessController[] statsArray = mAtoms.satelliteAccessController;
+            mAtoms.satelliteAccessController = new SatelliteAccessController[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
     /** Saves {@link PersistAtoms} to a file in private storage immediately. */
     public synchronized void flushAtoms() {
         saveAtomsToFile(0);
@@ -1814,6 +1845,9 @@
                     SatelliteEntitlement.class, mMaxNumSatelliteStats);
             atoms.satelliteConfigUpdater = sanitizeAtoms(atoms.satelliteConfigUpdater,
                     SatelliteConfigUpdater.class, mMaxNumSatelliteStats);
+            atoms.satelliteAccessController = sanitizeAtoms(
+                    atoms.satelliteAccessController, SatelliteAccessController.class,
+                    mMaxNumSatelliteStats);
 
             // out of caution, sanitize also the timestamps
             atoms.voiceCallRatUsagePullTimestampMillis =
@@ -1886,6 +1920,8 @@
                     sanitizeTimestamp(atoms.satelliteEntitlementPullTimestampMillis);
             atoms.satelliteConfigUpdaterPullTimestampMillis =
                     sanitizeTimestamp(atoms.satelliteConfigUpdaterPullTimestampMillis);
+            atoms.satelliteAccessControllerPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteAccessControllerPullTimestampMillis);
             return atoms;
         } catch (NoSuchFileException e) {
             Rlog.d(TAG, "PersistAtoms file not found");
@@ -2629,6 +2665,7 @@
         atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = currentTime;
         atoms.satelliteEntitlementPullTimestampMillis = currentTime;
         atoms.satelliteConfigUpdaterPullTimestampMillis = currentTime;
+        atoms.satelliteAccessControllerPullTimestampMillis = currentTime;
 
         Rlog.d(TAG, "created new PersistAtoms");
         return atoms;
diff --git a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
index 978e9d3..c2b2753 100644
--- a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
@@ -24,6 +24,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteAccessController;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
@@ -35,6 +36,8 @@
 import com.android.internal.telephony.satellite.SatelliteConstants;
 import com.android.telephony.Rlog;
 
+import java.util.Arrays;
+
 /** Tracks Satellite metrics for each phone */
 public class SatelliteStats {
     private static final String TAG = SatelliteStats.class.getSimpleName();
@@ -85,6 +88,9 @@
         private final int mCountOfDemoModeIncomingDatagramFail;
         private final int mCountOfDatagramTypeKeepAliveSuccess;
         private final int mCountOfDatagramTypeKeepAliveFail;
+        private final int mCountOfAllowedSatelliteAccess;
+        private final int mCountOfDisallowedSatelliteAccess;
+        private final int mCountOfSatelliteAccessCheckFail;
 
         private SatelliteControllerParams(Builder builder) {
             this.mCountOfSatelliteServiceEnablementsSuccess =
@@ -124,6 +130,12 @@
                     builder.mCountOfDatagramTypeKeepAliveSuccess;
             this.mCountOfDatagramTypeKeepAliveFail =
                     builder.mCountOfDatagramTypeKeepAliveFail;
+            this.mCountOfAllowedSatelliteAccess =
+                    builder.mCountOfAllowedSatelliteAccess;
+            this.mCountOfDisallowedSatelliteAccess =
+                    builder.mCountOfDisallowedSatelliteAccess;
+            this.mCountOfSatelliteAccessCheckFail =
+                    builder.mCountOfSatelliteAccessCheckFail;
         }
 
         public int getCountOfSatelliteServiceEnablementsSuccess() {
@@ -226,6 +238,18 @@
             return mCountOfDatagramTypeKeepAliveFail;
         }
 
+        public int getCountOfAllowedSatelliteAccess() {
+            return mCountOfAllowedSatelliteAccess;
+        }
+
+        public int getCountOfDisallowedSatelliteAccess() {
+            return mCountOfDisallowedSatelliteAccess;
+        }
+
+        public int getCountOfSatelliteAccessCheckFail() {
+            return mCountOfSatelliteAccessCheckFail;
+        }
+
         /**
          * A builder class to create {@link SatelliteControllerParams} data structure class
          */
@@ -255,6 +279,9 @@
             private int mCountOfDemoModeIncomingDatagramFail = 0;
             private int mCountOfDatagramTypeKeepAliveSuccess = 0;
             private int mCountOfDatagramTypeKeepAliveFail = 0;
+            private int mCountOfAllowedSatelliteAccess = 0;
+            private int mCountOfDisallowedSatelliteAccess = 0;
+            private int mCountOfSatelliteAccessCheckFail = 0;
 
             /**
              * Sets countOfSatelliteServiceEnablementsSuccess value of {@link SatelliteController}
@@ -503,6 +530,37 @@
             }
 
             /**
+             * Sets countOfAllowedSatelliteAccess value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfAllowedSatelliteAccess(
+                    int countOfAllowedSatelliteAccess) {
+                this.mCountOfAllowedSatelliteAccess =
+                        countOfAllowedSatelliteAccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfDisallowedSatelliteAccess value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfDisallowedSatelliteAccess(
+                    int countOfDisallowedSatelliteAccess) {
+                this.mCountOfDisallowedSatelliteAccess = countOfDisallowedSatelliteAccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfSatelliteAccessCheckFail value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfSatelliteAccessCheckFail(
+                    int countOfSatelliteAccessCheckFail) {
+                this.mCountOfSatelliteAccessCheckFail = countOfSatelliteAccessCheckFail;
+                return this;
+            }
+
+            /**
              * Returns ControllerParams, which contains whole component of
              * {@link SatelliteController} atom
              */
@@ -548,6 +606,9 @@
                     + mCountOfDatagramTypeKeepAliveSuccess
                     + ", countOfDatagramTypeKeepAliveFail="
                     + mCountOfDatagramTypeKeepAliveFail
+                    + ", countOfAllowedSatelliteAccess=" + mCountOfAllowedSatelliteAccess
+                    + ", countOfDisallowedSatelliteAccess=" + mCountOfDisallowedSatelliteAccess
+                    + ", countOfSatelliteAccessCheckFail=" + mCountOfSatelliteAccessCheckFail
                     + ")";
         }
     }
@@ -1915,6 +1976,169 @@
         }
     }
 
+    /**
+     * A data class to contain whole component of {@link SatelliteAccessControllerParams} atom.
+     * Refer to {@link #onSatelliteAccessControllerMetrics(SatelliteAccessControllerParams)}.
+     */
+    public class SatelliteAccessControllerParams {
+        private final @SatelliteConstants.AccessControlType int mAccessControlType;
+        private final long mLocationQueryTimeMillis;
+        private final long mOnDeviceLookupTimeMillis;
+        private final long mTotalCheckingTimeMillis;
+        private final boolean mIsAllowed;
+        private final boolean mIsEmergency;
+        private final @SatelliteManager.SatelliteResult int mResultCode;
+        private final String[] mCountryCodes;
+        private final @SatelliteConstants.ConfigDataSource int mConfigDataSource;
+
+        private SatelliteAccessControllerParams(Builder builder) {
+            this.mAccessControlType = builder.mAccessControlType;
+            this.mLocationQueryTimeMillis = builder.mLocationQueryTimeMillis;
+            this.mOnDeviceLookupTimeMillis = builder.mOnDeviceLookupTimeMillis;
+            this.mTotalCheckingTimeMillis = builder.mTotalCheckingTimeMillis;
+            this.mIsAllowed = builder.mIsAllowed;
+            this.mIsEmergency = builder.mIsEmergency;
+            this.mResultCode = builder.mResultCode;
+            this.mCountryCodes = builder.mCountryCodes;
+            this.mConfigDataSource = builder.mConfigDataSource;
+        }
+
+        public @SatelliteConstants.AccessControlType int getAccessControlType() {
+            return mAccessControlType;
+        }
+
+        public long getLocationQueryTime() {
+            return mLocationQueryTimeMillis;
+        }
+
+        public long getOnDeviceLookupTime() {
+            return mOnDeviceLookupTimeMillis;
+        }
+
+        public long getTotalCheckingTime() {
+            return mTotalCheckingTimeMillis;
+        }
+
+        public boolean getIsAllowed() {
+            return mIsAllowed;
+        }
+
+        public boolean getIsEmergency() {
+            return mIsEmergency;
+        }
+
+        public @SatelliteManager.SatelliteResult int getResultCode() {
+            return mResultCode;
+        }
+
+        public String[] getCountryCodes() {
+            return mCountryCodes;
+        }
+
+        public @SatelliteConstants.ConfigDataSource int getConfigDataSource() {
+            return mConfigDataSource;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteAccessControllerParams} data structure class
+         */
+        public static class Builder {
+            private @SatelliteConstants.AccessControlType int mAccessControlType;
+            private long mLocationQueryTimeMillis;
+            private long mOnDeviceLookupTimeMillis;
+            private long mTotalCheckingTimeMillis;
+            private boolean mIsAllowed;
+            private boolean mIsEmergency;
+            private @SatelliteManager.SatelliteResult int mResultCode;
+            private String[] mCountryCodes;
+            private @SatelliteConstants.ConfigDataSource int mConfigDataSource;
+
+            /**
+             * Sets AccessControlType value of {@link #SatelliteAccessController}
+             * atom then returns Builder class
+             */
+            public Builder setAccessControlType(
+                    @SatelliteConstants.AccessControlType int accessControlType) {
+                this.mAccessControlType = accessControlType;
+                return this;
+            }
+
+            /** Sets the location query time for current satellite enablement. */
+            public Builder setLocationQueryTime(long locationQueryTimeMillis) {
+                this.mLocationQueryTimeMillis = locationQueryTimeMillis;
+                return this;
+            }
+
+            /** Sets the on device lookup time for current satellite enablement. */
+            public Builder setOnDeviceLookupTime(long onDeviceLookupTimeMillis) {
+                this.mOnDeviceLookupTimeMillis = onDeviceLookupTimeMillis;
+                return this;
+            }
+
+            /** Sets the total checking time for current satellite enablement. */
+            public Builder setTotalCheckingTime(long totalCheckingTimeMillis) {
+                this.mTotalCheckingTimeMillis = totalCheckingTimeMillis;
+                return this;
+            }
+
+            /** Sets whether the satellite communication is allowed from current location. */
+            public Builder setIsAllowed(boolean isAllowed) {
+                this.mIsAllowed = isAllowed;
+                return this;
+            }
+
+            /** Sets whether the current satellite enablement is for emergency or not. */
+            public Builder setIsEmergency(boolean isEmergency) {
+                this.mIsEmergency = isEmergency;
+                return this;
+            }
+
+            /** Sets the result code for checking whether satellite service is allowed from current
+             location. */
+            public Builder setResult(int result) {
+                this.mResultCode = result;
+                return this;
+            }
+
+            /** Sets the country code for current location while attempting satellite enablement. */
+            public Builder setCountryCodes(String[] countryCodes) {
+                this.mCountryCodes = Arrays.stream(countryCodes).toArray(String[]::new);
+                return this;
+            }
+
+            /** Sets the config data source for checking whether satellite service is allowed from
+             current location. */
+            public Builder setConfigDatasource(int configDatasource) {
+                this.mConfigDataSource = configDatasource;
+                return this;
+            }
+
+            /**
+             * Returns AccessControllerParams, which contains whole component of
+             * {@link #SatelliteAccessController} atom
+             */
+            public SatelliteAccessControllerParams build() {
+                return new SatelliteStats()
+                        .new SatelliteAccessControllerParams(this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "AccessControllerParams("
+                    + ", AccessControlType=" + mAccessControlType
+                    + ", LocationQueryTime=" + mLocationQueryTimeMillis
+                    + ", OnDeviceLookupTime=" + mOnDeviceLookupTimeMillis
+                    + ", TotalCheckingTime=" + mTotalCheckingTimeMillis
+                    + ", IsAllowed=" + mIsAllowed
+                    + ", IsEmergency=" + mIsEmergency
+                    + ", ResultCode=" + mResultCode
+                    + ", CountryCodes=" + Arrays.toString(mCountryCodes)
+                    + ", ConfigDataSource=" + mConfigDataSource
+                    + ")";
+        }
+    }
+
     /**  Create a new atom or update an existing atom for SatelliteController metrics */
     public synchronized void onSatelliteControllerMetrics(SatelliteControllerParams param) {
         SatelliteController proto = new SatelliteController();
@@ -2079,4 +2303,20 @@
         proto.count = param.getCount();
         mAtomsStorage.addSatelliteConfigUpdaterStats(proto);
     }
+
+    /**  Create a new atom or update an existing atom for SatelliteAccessController metrics */
+    public synchronized void onSatelliteAccessControllerMetrics(
+            SatelliteAccessControllerParams param) {
+        SatelliteAccessController proto = new SatelliteAccessController();
+        proto.accessControlType = param.getAccessControlType();
+        proto.locationQueryTimeMillis = param.getLocationQueryTime();
+        proto.onDeviceLookupTimeMillis = param.getOnDeviceLookupTime();
+        proto.totalCheckingTimeMillis = param.getTotalCheckingTime();
+        proto.isAllowed = param.getIsAllowed();
+        proto.isEmergency = param.getIsEmergency();
+        proto.resultCode = param.getResultCode();
+        proto.countryCodes = param.getCountryCodes();
+        proto.configDataSource = param.getConfigDataSource();
+        mAtomsStorage.addSatelliteAccessControllerStats(proto);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java b/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
index f88069f..384dfa5 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
@@ -83,4 +83,25 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ConfigUpdateResult {}
+
+    // Access control type is unknown
+    public static final int ACCESS_CONTROL_TYPE_UNKNOWN = 0;
+    // Network country code is used for satellite access decision
+    public static final int ACCESS_CONTROL_TYPE_NETWORK_COUNTRY_CODE = 1;
+    // Device's current location is used for satellite access decision
+    public static final int ACCESS_CONTROL_TYPE_CURRENT_LOCATION = 2;
+    // Device's last known location is used for satellite access decision
+    public static final int ACCESS_CONTROL_TYPE_LAST_KNOWN_LOCATION = 3;
+    // Cached country codes are used for satellite access decision
+    public static final int ACCESS_CONTROL_TYPE_CACHED_COUNTRY_CODE = 4;
+
+    @IntDef(prefix = {"ACCESS_CONTROL_TYPE_"}, value = {
+            ACCESS_CONTROL_TYPE_UNKNOWN,
+            ACCESS_CONTROL_TYPE_NETWORK_COUNTRY_CODE,
+            ACCESS_CONTROL_TYPE_CURRENT_LOCATION,
+            ACCESS_CONTROL_TYPE_LAST_KNOWN_LOCATION,
+            ACCESS_CONTROL_TYPE_CACHED_COUNTRY_CODE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AccessControlType {}
 }
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
new file mode 100644
index 0000000..13ba709
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.telephony.satellite.metrics;
+
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+
+import static com.android.internal.telephony.satellite.SatelliteConstants.CONFIG_DATA_SOURCE_UNKNOWN;
+
+import android.annotation.NonNull;
+import android.telephony.satellite.SatelliteManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.SatelliteConstants;
+
+import java.util.Arrays;
+import java.util.List;
+public class AccessControllerMetricsStats {
+    private static final String TAG = AccessControllerMetricsStats.class.getSimpleName();
+    private static AccessControllerMetricsStats sInstance = null;
+
+    private @SatelliteConstants.AccessControlType int mAccessControlType;
+    private long mLocationQueryTimeMillis;
+    private long mOnDeviceLookupTimeMillis;
+    private long mTotalCheckingTimeMillis;
+    private Boolean mIsAllowed;
+    private Boolean mIsEmergency;
+    private @SatelliteManager.SatelliteResult int mResultCode;
+    private String[] mCountryCodes;
+    private @SatelliteConstants.ConfigDataSource int mConfigDataSource;
+    private AccessControllerMetricsStats() {
+        initializeAccessControllerMetricsParam();
+    }
+
+    /**
+     * Returns the Singleton instance of AccessControllerMetricsStats class.
+     * If an instance of the Singleton class has not been created,
+     * it creates a new instance and returns it. Otherwise, it returns
+     * the existing instance.
+     * @return the Singleton instance of AccessControllerMetricsStats.
+     */
+    public static AccessControllerMetricsStats getInstance() {
+        if (sInstance == null) {
+            loge("create new AccessControllerMetricsStats.");
+            sInstance = new AccessControllerMetricsStats();
+        }
+        return sInstance;
+    }
+    private void initializeAccessControllerMetricsParam() {
+        mAccessControlType = SatelliteConstants.ACCESS_CONTROL_TYPE_UNKNOWN;
+        mLocationQueryTimeMillis = 0;
+        mOnDeviceLookupTimeMillis = 0;
+        mTotalCheckingTimeMillis = 0;
+        mIsAllowed = null;
+        mIsEmergency = null;
+        mResultCode = SATELLITE_RESULT_SUCCESS;
+        mCountryCodes = new String[0];
+        mConfigDataSource = CONFIG_DATA_SOURCE_UNKNOWN;
+    }
+    /**
+     * Sets the Access Control Type for current satellite enablement.
+     * @param accessControlType access control type of location query is attempted.
+     */
+    public AccessControllerMetricsStats setAccessControlType(
+            @SatelliteConstants.AccessControlType int accessControlType) {
+        mAccessControlType = accessControlType;
+        logd("setAccessControlType: access control type = " + mAccessControlType);
+        return this;
+    }
+    /**
+     * Sets the location query time for current satellite enablement.
+     * @param queryStartTime the time location query is attempted.
+     */
+    public AccessControllerMetricsStats setLocationQueryTime(long queryStartTime) {
+        mLocationQueryTimeMillis =
+                (queryStartTime > 0) ? (getCurrentTime() - queryStartTime) : 0;
+        logd("setLocationQueryTimeMillis: location query time = " + mLocationQueryTimeMillis);
+        return this;
+    }
+    /**
+     * Sets the on device lookup time for current satellite enablement.
+     * @param onDeviceLookupStartTime the time on device lookup is attempted.
+     */
+    public AccessControllerMetricsStats setOnDeviceLookupTime(long onDeviceLookupStartTime) {
+        mOnDeviceLookupTimeMillis =
+                (onDeviceLookupStartTime > 0) ? (getCurrentTime() - onDeviceLookupStartTime) : 0;
+        logd("setLocationQueryTime: on device lookup time = " + mOnDeviceLookupTimeMillis);
+        return this;
+    }
+    /**
+     * Sets the total checking time for current satellite enablement.
+     * @param queryStartTime the time location query is attempted.
+     */
+    public AccessControllerMetricsStats setTotalCheckingTime(long queryStartTime) {
+        mTotalCheckingTimeMillis =
+                (queryStartTime > 0) ? (getCurrentTime() - queryStartTime) : 0;
+        logd("setTotalCheckingTime: location query time = " + mTotalCheckingTimeMillis);
+        return this;
+    }
+    /**
+     * Sets whether the satellite communication is allowed from current location.
+     * @param isAllowed {@code true} if satellite communication is allowed from current location
+     *        {@code false} otherwise.
+     */
+    public AccessControllerMetricsStats setIsAllowed(boolean isAllowed) {
+        mIsAllowed = isAllowed;
+        logd("setIsAllowed: allowed=" + mIsAllowed);
+        return this;
+    }
+    /**
+     * Sets whether the current satellite enablement is for emergency or not.
+     * @param isEmergency {@code true} if current satellite enablement is for emergency SOS message
+     *        {@code false} otherwise.
+     */
+    public AccessControllerMetricsStats setIsEmergency(boolean isEmergency) {
+        mIsEmergency = isEmergency;
+        logd("setIsEmergency: emergency =" + mIsEmergency);
+        return this;
+    }
+    /**
+     * Sets the result code for checking whether satellite service is allowed from current
+     * location.
+     * @param result result code for checking process.
+     */
+    public AccessControllerMetricsStats setResult(@SatelliteManager.SatelliteResult int result) {
+        mResultCode = result;
+        logd("setResult: result = " + mResultCode);
+        return this;
+    }
+    /**
+     * Sets the country code for current location while attempting satellite enablement.
+     * @param countryCodes Country code the user is located in
+     */
+    public AccessControllerMetricsStats setCountryCodes(List<String> countryCodes) {
+        mCountryCodes = countryCodes.stream().toArray(String[]::new);
+        logd("setCountryCodes: country code is " + Arrays.toString(mCountryCodes));
+        return this;
+    }
+    /**
+     * Sets the config data source for checking whether satellite service is allowed from current
+     * location.
+     * @param configDatasource configuration data source.
+     */
+    public AccessControllerMetricsStats setConfigDataSource(
+            @SatelliteConstants.ConfigDataSource int configDatasource) {
+        mConfigDataSource = configDatasource;
+        logd("setConfigDataSource: config data source = " + mConfigDataSource);
+        return this;
+    }
+    /** Report the access controller metrics atoms to PersistAtomsStorage in telephony. */
+    public void reportAccessControllerMetrics() {
+        SatelliteStats.SatelliteAccessControllerParams accessControllerParams =
+                new SatelliteStats.SatelliteAccessControllerParams.Builder()
+                        .setAccessControlType(mAccessControlType)
+                        .setLocationQueryTime(mLocationQueryTimeMillis)
+                        .setOnDeviceLookupTime(mOnDeviceLookupTimeMillis)
+                        .setTotalCheckingTime(mTotalCheckingTimeMillis)
+                        .setIsAllowed(mIsAllowed)
+                        .setIsEmergency(mIsEmergency)
+                        .setResult(mResultCode)
+                        .setCountryCodes(mCountryCodes)
+                        .setConfigDatasource(mConfigDataSource)
+                        .build();
+        logd("reportAccessControllerMetrics: " + accessControllerParams.toString());
+        SatelliteStats.getInstance().onSatelliteAccessControllerMetrics(accessControllerParams);
+        initializeAccessControllerMetricsParam();
+    }
+    @VisibleForTesting
+    public long getCurrentTime() {
+        return System.currentTimeMillis();
+    }
+    private static void logd(@NonNull String log) {
+        Log.d(TAG, log);
+    }
+    private static void loge(@NonNull String log) {
+        Log.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
index fdc7c5c..7dff2e6 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
@@ -236,6 +236,36 @@
         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
     }
 
+    /**
+     * Report a counter when checking result whether satellite communication is allowed or not for
+     * current location.
+     */
+    public void reportAllowedSatelliteAccessCount(boolean isAllowed) {
+        SatelliteStats.SatelliteControllerParams.Builder builder;
+        if (isAllowed) {
+            builder = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfAllowedSatelliteAccess(ADD_COUNT);
+        } else {
+            builder = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfDisallowedSatelliteAccess(ADD_COUNT);
+        }
+        SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
+        logd("reportAllowedSatelliteAccessCount:" + controllerParam);
+        mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+    }
+
+    /**
+     * Report a counter when checking whether satellite communication for current location is
+     * allowed has failed.
+     */
+    public void reportFailedSatelliteAccessCheckCount() {
+        SatelliteStats.SatelliteControllerParams controllerParam =
+                new SatelliteStats.SatelliteControllerParams.Builder()
+                        .setCountOfSatelliteAccessCheckFail(ADD_COUNT).build();
+        logd("reportFailedSatelliteAccessCheckCount:" + controllerParam);
+        mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+    }
+
     /** Return the total service up time for satellite service */
     @VisibleForTesting
     public int captureTotalServiceUpTimeSec() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
index 0f37a57..8ee3a37 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -20,6 +20,7 @@
 import static android.telephony.SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_GPRS;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_GSM;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 
@@ -44,6 +45,10 @@
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
+import static com.android.internal.telephony.satellite.SatelliteConstants.ACCESS_CONTROL_TYPE_CURRENT_LOCATION;
+import static com.android.internal.telephony.satellite.SatelliteConstants.ACCESS_CONTROL_TYPE_NETWORK_COUNTRY_CODE;
+import static com.android.internal.telephony.satellite.SatelliteConstants.CONFIG_DATA_SOURCE_CONFIG_UPDATER;
+import static com.android.internal.telephony.satellite.SatelliteConstants.CONFIG_DATA_SOURCE_DEVICE_CONFIG;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -96,6 +101,7 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteAccessController;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
@@ -129,6 +135,7 @@
 import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.Queue;
+import java.util.concurrent.TimeUnit;
 
 public class PersistAtomsStorageTest extends TelephonyTest {
     private static final String TEST_FILE = "PersistAtomsStorageTest.pb";
@@ -316,6 +323,10 @@
     private SatelliteConfigUpdater mSatelliteConfigUpdater2;
     private SatelliteConfigUpdater[] mSatelliteConfigUpdaters;
 
+    private SatelliteAccessController mSatelliteAccessController1;
+    private SatelliteAccessController mSatelliteAccessController2;
+    private SatelliteAccessController[] mSatelliteAccessControllers;
+
     private void makeTestData() {
         // MO call with SRVCC (LTE to UMTS)
         mCall1Proto = new VoiceCallSession();
@@ -1392,6 +1403,32 @@
 
         mSatelliteConfigUpdaters = new SatelliteConfigUpdater[] {mSatelliteConfigUpdater1,
                 mSatelliteConfigUpdater2};
+
+        mSatelliteAccessController1 = new SatelliteAccessController();
+        mSatelliteAccessController1.accessControlType = ACCESS_CONTROL_TYPE_NETWORK_COUNTRY_CODE;
+        mSatelliteAccessController1.locationQueryTimeMillis = TimeUnit.SECONDS.toMillis(1);
+        mSatelliteAccessController1.onDeviceLookupTimeMillis = TimeUnit.SECONDS.toMillis(2);
+        mSatelliteAccessController1.totalCheckingTimeMillis = TimeUnit.SECONDS.toMillis(3);
+        mSatelliteAccessController1.isAllowed = true;
+        mSatelliteAccessController1.isEmergency = false;
+        mSatelliteAccessController1.resultCode = SATELLITE_RESULT_SUCCESS;
+        mSatelliteAccessController1.countryCodes = new String[]{"AB", "CD"};
+        mSatelliteAccessController1.configDataSource = CONFIG_DATA_SOURCE_DEVICE_CONFIG;
+
+        mSatelliteAccessController2 = new SatelliteAccessController();
+        mSatelliteAccessController1.accessControlType = ACCESS_CONTROL_TYPE_CURRENT_LOCATION;
+        mSatelliteAccessController1.locationQueryTimeMillis = TimeUnit.SECONDS.toMillis(4);
+        mSatelliteAccessController2.onDeviceLookupTimeMillis = TimeUnit.SECONDS.toMillis(5);
+        mSatelliteAccessController2.totalCheckingTimeMillis = TimeUnit.SECONDS.toMillis(6);
+        mSatelliteAccessController2.isAllowed = false;
+        mSatelliteAccessController2.isEmergency = true;
+        mSatelliteAccessController2.resultCode = SATELLITE_RESULT_SUCCESS;
+        mSatelliteAccessController2.countryCodes = new String[]{"EF", "GH"};
+        mSatelliteAccessController2.configDataSource = CONFIG_DATA_SOURCE_CONFIG_UPDATER;
+
+        mSatelliteAccessControllers = new SatelliteAccessController[]{
+                mSatelliteAccessController1, mSatelliteAccessController2
+        };
     }
 
     private void generateTestDataNetworkValidationsData() {
@@ -1630,6 +1667,9 @@
         mSatelliteConfigUpdater1 = null;
         mSatelliteConfigUpdater2 = null;
         mSatelliteConfigUpdaters = null;
+        mSatelliteAccessController1 = null;
+        mSatelliteAccessController2 = null;
+        mSatelliteAccessControllers = null;
         super.tearDown();
     }
 
@@ -5184,6 +5224,63 @@
     }
 
     @Test
+    public void addSatelliteAccessControllerStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteAccessControllerStats(
+                mSatelliteAccessController1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteAccessController[] output =
+                mPersistAtomsStorage.getSatelliteAccessControllerStats(0L);
+        assertProtoArrayEquals(
+                new SatelliteAccessController[] {mSatelliteAccessController1}, output);
+    }
+
+    @Test
+    public void addSatelliteAccessControllerStats_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store atoms up to maximum number + 1
+        int maxCount = 15 + 1;
+        for (int i = 0; i < maxCount; i++) {
+            mPersistAtomsStorage
+                    .addSatelliteAccessControllerStats(//mSatelliteAccessController1);
+                            copyOf(mSatelliteAccessController1));
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage
+                .addSatelliteAccessControllerStats(mSatelliteAccessController2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        SatelliteAccessController[] result =
+                mPersistAtomsStorage.getSatelliteAccessControllerStats(0L);
+        // First atom should have count 14, the other should have 1
+        assertHasStatsAndCount(result, mSatelliteAccessController1, 14);
+        assertHasStatsAndCount(result, mSatelliteAccessController2, 1);
+    }
+
+    @Test
+    public void getSatelliteAccessControllerStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteAccessController[] output =
+                mPersistAtomsStorage.getSatelliteAccessControllerStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
     @SmallTest
     public void addDataNetworkValidation_newEntry() throws Exception {
         createEmptyTestFile();
@@ -5349,6 +5446,7 @@
         atoms.satelliteEntitlementPullTimestampMillis = lastPullTimeMillis;
         atoms.satelliteConfigUpdater = mSatelliteConfigUpdaters;
         atoms.satelliteConfigUpdaterPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteAccessControllerPullTimestampMillis = lastPullTimeMillis;
         FileOutputStream stream = new FileOutputStream(mTestFile);
         stream.write(PersistAtoms.toByteArray(atoms));
         stream.close();
@@ -5535,6 +5633,11 @@
         return SatelliteConfigUpdater.parseFrom(MessageNano.toByteArray(source));
     }
 
+    private static SatelliteAccessController copyOf(SatelliteAccessController source)
+            throws Exception {
+        return SatelliteAccessController.parseFrom(MessageNano.toByteArray(source));
+    }
+
     private void assertAllPullTimestampEquals(long timestamp) {
         assertEquals(
                 timestamp,
@@ -5827,6 +5930,28 @@
         assertEquals(expectedCount, actualCount);
     }
 
+    private static void assertHasStatsAndCount(
+            SatelliteAccessController[] tested,
+            @Nullable SatelliteAccessController expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (SatelliteAccessController stats : tested) {
+            if (stats.accessControlType
+                    == expectedStats.accessControlType
+                    && stats.locationQueryTimeMillis == expectedStats.locationQueryTimeMillis
+                    && stats.onDeviceLookupTimeMillis == expectedStats.onDeviceLookupTimeMillis
+                    && stats.totalCheckingTimeMillis == expectedStats.totalCheckingTimeMillis
+                    && stats.isAllowed == expectedStats.isAllowed
+                    && stats.isEmergency == expectedStats.isEmergency
+                    && stats.resultCode == expectedStats.resultCode
+                    && Arrays.equals(stats.countryCodes, expectedStats.countryCodes)
+                    && stats.configDataSource == expectedStats.configDataSource) {
+                actualCount++;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
     private static void assertHasStatsAndCountDuration(
             RcsAcsProvisioningStats[] statses,
             @Nullable RcsAcsProvisioningStats expectedStats, int count, long duration) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
index daa47c1..cda96ef 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
@@ -17,6 +17,10 @@
 package com.android.internal.telephony.metrics;
 
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_GOOD;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+
+import static com.android.internal.telephony.satellite.SatelliteConstants.CONFIG_DATA_SOURCE_DEVICE_CONFIG;
+import static com.android.internal.telephony.satellite.SatelliteConstants.ACCESS_CONTROL_TYPE_CACHED_COUNTRY_CODE;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.verify;
@@ -28,6 +32,7 @@
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteAccessController;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
@@ -42,6 +47,8 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import java.util.concurrent.TimeUnit;
+
 public class SatelliteStatsTest extends TelephonyTest {
     private static final String TAG = SatelliteStatsTest.class.getSimpleName();
 
@@ -436,4 +443,38 @@
 
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
+
+
+    @Test
+    public void onSatelliteAccessControllerMetrics_withAtoms() {
+        SatelliteStats.SatelliteAccessControllerParams param =
+                new SatelliteStats.SatelliteAccessControllerParams.Builder()
+                        .setAccessControlType(ACCESS_CONTROL_TYPE_CACHED_COUNTRY_CODE)
+                        .setLocationQueryTime(TimeUnit.SECONDS.toMillis(1))
+                        .setOnDeviceLookupTime(TimeUnit.SECONDS.toMillis(2))
+                        .setTotalCheckingTime(TimeUnit.SECONDS.toMillis(3))
+                        .setIsAllowed(true)
+                        .setIsEmergency(false)
+                        .setResult(SATELLITE_RESULT_SUCCESS)
+                        .setCountryCodes(new String[]{"AB", "CD"})
+                        .setConfigDatasource(CONFIG_DATA_SOURCE_DEVICE_CONFIG)
+                        .build();
+
+        mSatelliteStats.onSatelliteAccessControllerMetrics(param);
+
+        ArgumentCaptor<SatelliteAccessController> captor = ArgumentCaptor.forClass(
+                SatelliteAccessController.class);
+        verify(mPersistAtomsStorage).addSatelliteAccessControllerStats(captor.capture());
+        SatelliteAccessController stats = captor.getValue();
+        assertEquals(param.getAccessControlType(), stats.accessControlType);
+        assertEquals(param.getLocationQueryTime(), stats.locationQueryTimeMillis);
+        assertEquals(param.getOnDeviceLookupTime(), stats.onDeviceLookupTimeMillis);
+        assertEquals(param.getTotalCheckingTime(), stats.totalCheckingTimeMillis);
+        assertEquals(param.getIsAllowed(), stats.isAllowed);
+        assertEquals(param.getIsEmergency(), stats.isEmergency);
+        assertEquals(param.getResultCode(), stats.resultCode);
+        assertEquals(param.getCountryCodes(), stats.countryCodes);
+        assertEquals(param.getConfigDataSource(), stats.configDataSource);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
 }