Merge "Request again when the latest signal strength report request and report state of modem are diferent" into 24D1-dev
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index 3847fcd..d116159 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: 70
+// Next id: 80
 message PersistAtoms {
     /* Aggregated RAT usage during the call. */
     repeated VoiceCallRatUsage voice_call_rat_usage = 1;
@@ -231,6 +231,30 @@
 
     /* Timestamp of last satellite_sos_message_recommender pull. */
     optional int64 satellite_sos_message_recommender_pull_timestamp_millis = 69;
+
+    /* Snapshot of carrier roaming satellite session. */
+    repeated CarrierRoamingSatelliteSession carrier_roaming_satellite_session = 72;
+
+    /* Timestamp of last carrier_roaming_satellite_session pull. */
+    optional int64 carrier_roaming_satellite_session_pull_timestamp_millis = 73;
+
+    /* Snapshot of carrier roaming satellite controller stats. */
+    repeated CarrierRoamingSatelliteControllerStats carrier_roaming_satellite_controller_stats = 74;
+
+    /* Timestamp of last carrier_roaming_satellite_controller_stats pull. */
+    optional int64 carrier_roaming_satellite_controller_stats_pull_timestamp_millis = 75;
+
+    /* Snapshot of satellite entitlement. */
+    repeated SatelliteEntitlement satellite_entitlement = 76;
+
+    /* Timestamp of last satellite_entitlement pull. */
+    optional int64 satellite_entitlement_pull_timestamp_millis = 77;
+
+    /* Snapshot of satellite config updater. */
+    repeated SatelliteConfigUpdater satellite_config_updater = 78;
+
+    /* Timestamp of last satellite_config_updater pull. */
+    optional int64 satellite_config_updater_pull_timestamp_millis = 79;
 }
 
 // The canonical versions of the following enums live in:
@@ -717,3 +741,47 @@
     optional int32 recommending_handover_type = 7;
     optional bool is_satellite_allowed_in_current_location = 8;
 }
+
+message CarrierRoamingSatelliteSession {
+    optional int32 carrier_id = 1;
+    optional bool is_ntn_roaming_in_home_country = 2;
+    optional int32 total_satellite_mode_time_sec = 3;
+    optional int32 number_of_satellite_connections = 4;
+    optional int32 avg_duration_of_satellite_connection_sec = 5;
+    optional int32 satellite_connection_gap_min_sec = 6;
+    optional int32 satellite_connection_gap_avg_sec = 7;
+    optional int32 satellite_connection_gap_max_sec = 8;
+    optional int32 rsrp_avg = 9;
+    optional int32 rsrp_median = 10;
+    optional int32 rssnr_avg = 11;
+    optional int32 rssnr_median = 12;
+    optional int32 count_of_incoming_sms = 13;
+    optional int32 count_of_outgoing_sms = 14;
+    optional int32 count_of_incoming_mms = 15;
+    optional int32 count_of_outgoing_mms = 16;
+}
+
+message CarrierRoamingSatelliteControllerStats {
+    optional int32 config_data_source = 1;
+    optional int32 count_of_entitlement_status_query_request = 2;
+    optional int32 count_of_satellite_config_update_request = 3;
+    optional int32 count_of_satellite_notification_displayed = 4;
+    optional int32 satellite_session_gap_min_sec = 5;
+    optional int32 satellite_session_gap_avg_sec = 6;
+    optional int32 satellite_session_gap_max_sec = 7;
+}
+
+message SatelliteEntitlement {
+    optional int32 carrier_id = 1;
+    optional int32 result = 2;
+    optional int32 entitlement_status = 3;
+    optional bool is_retry = 4;
+    optional int32 count = 5;
+}
+
+message SatelliteConfigUpdater {
+    optional int32 config_version = 1;
+    optional int32 oem_config_result = 2;
+    optional int32 carrier_config_result = 3;
+    optional int32 count = 4;
+}
diff --git a/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java
index 85d5a35..85413f5 100644
--- a/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java
+++ b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java
@@ -27,6 +27,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.satellite.SatelliteConfig;
 import com.android.internal.telephony.satellite.SatelliteConfigParser;
+import com.android.internal.telephony.satellite.SatelliteConstants;
+import com.android.internal.telephony.satellite.metrics.ConfigUpdaterMetricsStats;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.server.updates.ConfigUpdateInstallReceiver;
 
@@ -57,6 +59,7 @@
     private final Object mConfigParserLock = new Object();
     @GuardedBy("mConfigParserLock")
     private ConfigParser mConfigParser;
+    @NonNull private final ConfigUpdaterMetricsStats mConfigUpdaterMetricsStats;
 
 
     public static TelephonyConfigUpdateInstallReceiver sReceiverAdaptorInstance =
@@ -72,6 +75,7 @@
 
     public TelephonyConfigUpdateInstallReceiver() {
         super(UPDATE_DIR, NEW_CONFIG_CONTENT_PATH, UPDATE_METADATA_PATH, VERSION);
+        mConfigUpdaterMetricsStats = ConfigUpdaterMetricsStats.getOrCreateInstance();
     }
 
     /**
@@ -97,6 +101,8 @@
         SatelliteConfig satelliteConfig = (SatelliteConfig) parser.getConfig();
         if (satelliteConfig == null) {
             Log.e(TAG, "satelliteConfig is null");
+            mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                    SatelliteConstants.CONFIG_UPDATE_RESULT_NO_SATELLITE_DATA);
             return false;
         }
 
@@ -109,12 +115,16 @@
             for (String plmn : plmns) {
                 if (!TelephonyUtils.isValidPlmn(plmn)) {
                     Log.e(TAG, "found invalid plmn : " + plmn);
+                    mConfigUpdaterMetricsStats.reportCarrierConfigError(
+                            SatelliteConstants.CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_PLMN);
                     return false;
                 }
                 Set<Integer> serviceSet = plmnsServices.get(plmn);
                 for (int service : serviceSet) {
                     if (!TelephonyUtils.isValidService(service)) {
                         Log.e(TAG, "found invalid service : " + service);
+                        mConfigUpdaterMetricsStats.reportCarrierConfigError(SatelliteConstants
+                                .CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_SUPPORTED_SERVICES);
                         return false;
                     }
                 }
@@ -149,8 +159,11 @@
                 int previousVersion = getInstance().mConfigParser.mVersion;
                 Log.d(TAG, "previous version is " + previousVersion + " | updated version is "
                         + updatedVersion);
+                mConfigUpdaterMetricsStats.setConfigVersion(updatedVersion);
                 if (updatedVersion <= previousVersion) {
                     Log.e(TAG, "updatedVersion is smaller than previousVersion");
+                    mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                            SatelliteConstants.CONFIG_UPDATE_RESULT_INVALID_VERSION);
                     return;
                 }
             }
@@ -167,6 +180,8 @@
 
         if (!copySourceFileToTargetFile(NEW_CONFIG_CONTENT_PATH, VALID_CONFIG_CONTENT_PATH)) {
             Log.e(TAG, "fail to copy to the valid satellite carrier config data");
+            mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                    SatelliteConstants.CONFIG_UPDATE_RESULT_IO_ERROR);
         }
     }
 
@@ -231,6 +246,8 @@
     public ConfigParser getNewConfigParser(String domain, @Nullable byte[] data) {
         if (data == null) {
             Log.d(TAG, "content data is null");
+            mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                    SatelliteConstants.CONFIG_UPDATE_RESULT_NO_DATA);
             return null;
         }
         switch (domain) {
@@ -238,6 +255,8 @@
                 return new SatelliteConfigParser(data);
             default:
                 Log.e(TAG, "DOMAIN should be specified");
+                mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                        SatelliteConstants.CONFIG_UPDATE_RESULT_INVALID_DOMAIN);
                 return null;
         }
     }
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index bd98403..7ca504b 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -124,6 +124,7 @@
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -1001,23 +1002,36 @@
                 && transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
         mDataAllowedReason = dataAllowedReason;
         dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
-        mAttachedNetworkRequestList.addAll(networkRequestList);
         for (int transportType : mAccessNetworksManager.getAvailableTransports()) {
             mCid.put(transportType, INVALID_CID);
         }
         mTelephonyDisplayInfo = mPhone.getDisplayInfoController().getTelephonyDisplayInfo();
         mTcpBufferSizes = mDataConfigManager.getTcpConfigString(mTelephonyDisplayInfo);
 
-        for (TelephonyNetworkRequest networkRequest : networkRequestList) {
-            networkRequest.setAttachedNetwork(DataNetwork.this);
-            networkRequest.setState(TelephonyNetworkRequest.REQUEST_STATE_SATISFIED);
-        }
-
+        // network capabilities infer connectivity transport and MMTEL from the requested
+        // capabilities.
+        // TODO: Ideally we shouldn't infer network capabilities base on the requested capabilities,
+        // but currently there are 2 hacks associated with getForcedCellularTransportCapabilities
+        // and IMS service requesting IMS|MMTEL that need to support. When we stop supporting these
+        // cases, we shouldn't consider the requests when determining the network capabilities.
+        mAttachedNetworkRequestList.addAll(networkRequestList);
         // Update the capabilities in the constructor is to make sure the data network has initial
         // capability immediately after created. Doing this connecting state creates the window that
         // DataNetworkController might check if existing data network's capability can satisfy the
         // next network request within this window.
         updateNetworkCapabilities();
+
+        // Remove the requests that can't use the initial capabilities
+        ListIterator<TelephonyNetworkRequest> iter = mAttachedNetworkRequestList.listIterator();
+        while (iter.hasNext()) {
+            TelephonyNetworkRequest request = iter.next();
+            if (request.canBeSatisfiedBy(mNetworkCapabilities)) {
+                request.setAttachedNetwork(DataNetwork.this);
+                request.setState(TelephonyNetworkRequest.REQUEST_STATE_SATISFIED);
+            } else {
+                iter.remove();
+            }
+        }
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 12c94df..5a688ca 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -2122,6 +2122,13 @@
      */
     private boolean canConnectivityTransportSatisfyNetworkRequest(
             @NonNull TelephonyNetworkRequest networkRequest, @TransportType int transport) {
+        // Check if this is a IWLAN network request.
+        if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+            // If the request would result in bringing up network on IWLAN, then no
+            // need to check if the device is using satellite network.
+            return true;
+        }
+
         // When the device is on satellite, only restricted network request can request network.
         if (mServiceState.isUsingNonTerrestrialNetwork()
                 && networkRequest.hasCapability(
@@ -2136,14 +2143,6 @@
             return true;
         }
 
-        // Check if this is a IWLAN network request.
-        if (networkRequest.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
-                && transport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
-            // If the cellular request would result in bringing up network on IWLAN, then no
-            // need to check if the device is using satellite network.
-            return true;
-        }
-
         // As a short term solution, allowing some networks to be always marked as cellular
         // transport if certain capabilities are in the network request.
         if (networkRequest.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) && Arrays.stream(
@@ -3061,8 +3060,18 @@
             List<NetworkRequestList> groupRequestLists = getGroupedUnsatisfiedNetworkRequests();
             dataSetupRetryEntry.networkRequestList.stream()
                     .filter(request -> groupRequestLists.stream()
-                            .anyMatch(groupRequestList -> groupRequestList
-                                    .get(request.getCapabilities()) != null))
+                            .anyMatch(groupRequestList -> {
+                                // The unsatisfied request has all the requested capabilities.
+                                if (groupRequestList.get(request.getCapabilities()) == null) {
+                                    return false;
+                                }
+                                TelephonyNetworkRequest leading = groupRequestList.getFirst();
+                                // The unsatisfied request covers all the requested transports.
+                                return leading.getTransportTypes().length == 0
+                                        || request.getTransportTypes().length == 0
+                                        || Arrays.stream(request.getTransportTypes())
+                                        .allMatch(leading::hasTransport);
+                            }))
                     .forEach(requestList::add);
         }
         if (requestList.isEmpty()) {
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index 0031c7e..f174b25 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -17,6 +17,8 @@
 package com.android.internal.telephony.metrics;
 
 import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ID_TABLE_VERSION;
+import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS;
+import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ROAMING_SATELLITE_SESSION;
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH;
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE;
 import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION;
@@ -36,7 +38,9 @@
 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_CONFIG_UPDATER;
 import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_CONTROLLER;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_ENTITLEMENT;
 import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_INCOMING_DATAGRAM;
 import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_OUTGOING_DATAGRAM;
 import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_PROVISION;
@@ -69,6 +73,8 @@
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
@@ -87,7 +93,9 @@
 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.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
@@ -177,14 +185,15 @@
 
     public MetricsCollector(Context context, @NonNull FeatureFlags featureFlags) {
         this(context, new PersistAtomsStorage(context),
-                new DeviceStateHelper(context), new VonrHelper(featureFlags), featureFlags);
+                new DeviceStateHelper(context), new VonrHelper(featureFlags),
+                new DefaultNetworkMonitor(context, featureFlags), featureFlags);
     }
 
     /** Allows dependency injection. Used during unit tests. */
     @VisibleForTesting
-    public MetricsCollector(
-            Context context, PersistAtomsStorage storage, DeviceStateHelper deviceStateHelper,
-                    VonrHelper vonrHelper, @NonNull FeatureFlags featureFlags) {
+    public MetricsCollector(Context context, PersistAtomsStorage storage,
+            DeviceStateHelper deviceStateHelper, VonrHelper vonrHelper,
+            DefaultNetworkMonitor defaultNetworkMonitor, @NonNull FeatureFlags featureFlags) {
         mStorage = storage;
         mDeviceStateHelper = deviceStateHelper;
         mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER);
@@ -227,13 +236,17 @@
             registerAtom(SATELLITE_OUTGOING_DATAGRAM);
             registerAtom(SATELLITE_PROVISION);
             registerAtom(SATELLITE_SOS_MESSAGE_RECOMMENDER);
+            registerAtom(CARRIER_ROAMING_SATELLITE_SESSION);
+            registerAtom(CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS);
+            registerAtom(SATELLITE_ENTITLEMENT);
+            registerAtom(SATELLITE_CONFIG_UPDATER);
             Rlog.d(TAG, "registered");
         } else {
             Rlog.e(TAG, "could not get StatsManager, atoms not registered");
         }
 
         mAirplaneModeStats = new AirplaneModeStats(context);
-        mDefaultNetworkMonitor = new DefaultNetworkMonitor(context, featureFlags);
+        mDefaultNetworkMonitor = defaultNetworkMonitor;
     }
 
     /**
@@ -318,6 +331,14 @@
                 return pullSatelliteProvision(data);
             case SATELLITE_SOS_MESSAGE_RECOMMENDER:
                 return pullSatelliteSosMessageRecommender(data);
+            case CARRIER_ROAMING_SATELLITE_SESSION:
+                return pullCarrierRoamingSatelliteSession(data);
+            case CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS:
+                return pullCarrierRoamingSatelliteControllerStats(data);
+            case SATELLITE_ENTITLEMENT:
+                return pullSatelliteEntitlement(data);
+            case SATELLITE_CONFIG_UPDATER:
+                return pullSatelliteConfigUpdater(data);
             default:
                 Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
                 return StatsManager.PULL_SKIP;
@@ -937,6 +958,59 @@
         }
     }
 
+    private int pullCarrierRoamingSatelliteSession(List<StatsEvent> data) {
+        CarrierRoamingSatelliteSession[] carrierRoamingSatelliteSessionAtoms =
+                mStorage.getCarrierRoamingSatelliteSessionStats(MIN_COOLDOWN_MILLIS);
+        if (carrierRoamingSatelliteSessionAtoms != null) {
+            Arrays.stream(carrierRoamingSatelliteSessionAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "CARRIER_ROAMING_SATELLITE_SESSION pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullCarrierRoamingSatelliteControllerStats(List<StatsEvent> data) {
+        CarrierRoamingSatelliteControllerStats[] carrierRoamingSatelliteControllerStatsAtoms =
+                mStorage.getCarrierRoamingSatelliteControllerStats(MIN_COOLDOWN_MILLIS);
+        if (carrierRoamingSatelliteControllerStatsAtoms != null) {
+            Arrays.stream(carrierRoamingSatelliteControllerStatsAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS "
+                    + "pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullSatelliteEntitlement(List<StatsEvent> data) {
+        SatelliteEntitlement[] satelliteEntitlementAtoms =
+                mStorage.getSatelliteEntitlementStats(MIN_COOLDOWN_MILLIS);
+        if (satelliteEntitlementAtoms != null) {
+            Arrays.stream(satelliteEntitlementAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_ENTITLEMENT pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullSatelliteConfigUpdater(List<StatsEvent> data) {
+        SatelliteConfigUpdater[] satelliteConfigUpdaterAtoms =
+                mStorage.getSatelliteConfigUpdaterStats(MIN_COOLDOWN_MILLIS);
+        if (satelliteConfigUpdaterAtoms != null) {
+            Arrays.stream(satelliteConfigUpdaterAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_CONFIG_UPDATER 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,
@@ -1415,6 +1489,57 @@
                 stats.isSatelliteAllowedInCurrentLocation);
     }
 
+    private static StatsEvent buildStatsEvent(CarrierRoamingSatelliteSession stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                CARRIER_ROAMING_SATELLITE_SESSION,
+                stats.carrierId,
+                stats.isNtnRoamingInHomeCountry,
+                stats.totalSatelliteModeTimeSec,
+                stats.numberOfSatelliteConnections,
+                stats.avgDurationOfSatelliteConnectionSec,
+                stats.satelliteConnectionGapMinSec,
+                stats.satelliteConnectionGapAvgSec,
+                stats.satelliteConnectionGapMaxSec,
+                stats.rsrpAvg,
+                stats.rsrpMedian,
+                stats.rssnrAvg,
+                stats.rssnrMedian,
+                stats.countOfIncomingSms,
+                stats.countOfOutgoingSms,
+                stats.countOfIncomingMms,
+                stats.countOfOutgoingMms);
+    }
+
+    private static StatsEvent buildStatsEvent(CarrierRoamingSatelliteControllerStats stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS,
+                stats.configDataSource,
+                stats.countOfEntitlementStatusQueryRequest,
+                stats.countOfSatelliteConfigUpdateRequest,
+                stats.countOfSatelliteNotificationDisplayed,
+                stats.satelliteSessionGapMinSec,
+                stats.satelliteSessionGapAvgSec,
+                stats.satelliteSessionGapMaxSec);
+    }
+
+    private static StatsEvent buildStatsEvent(SatelliteEntitlement stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                SATELLITE_ENTITLEMENT,
+                stats.carrierId,
+                stats.result,
+                stats.entitlementStatus,
+                stats.isRetry,
+                stats.count);
+    }
+
+    private static StatsEvent buildStatsEvent(SatelliteConfigUpdater stats) {
+        return TelephonyStatsLog.buildStatsEvent(SATELLITE_CONFIG_UPDATER,
+                stats.configVersion,
+                stats.oemConfigResult,
+                stats.carrierConfigResult,
+                stats.count);
+    }
+
     /** 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 8afdc36..68cca3c 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -30,6 +30,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierIdMismatch;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
@@ -48,7 +50,9 @@
 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.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
@@ -172,6 +176,7 @@
     /** Maximum number of Satellite relevant stats to store between pulls. */
     private final int mMaxNumSatelliteStats;
     private final int mMaxNumSatelliteControllerStats = 1;
+    private final int mMaxNumCarrierRoamingSatelliteSessionStats = 1;
 
     /** Stores persist atoms and persist states of the puller. */
     @VisibleForTesting protected PersistAtoms mAtoms;
@@ -807,6 +812,63 @@
         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
     }
 
+    /** Adds a new {@link CarrierRoamingSatelliteSession} to the storage. */
+    public synchronized void addCarrierRoamingSatelliteSessionStats(
+            CarrierRoamingSatelliteSession stats) {
+        mAtoms.carrierRoamingSatelliteSession = insertAtRandomPlace(
+                mAtoms.carrierRoamingSatelliteSession, stats,
+                mMaxNumCarrierRoamingSatelliteSessionStats);
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link CarrierRoamingSatelliteControllerStats} to the storage. */
+    public synchronized void addCarrierRoamingSatelliteControllerStats(
+            CarrierRoamingSatelliteControllerStats stats) {
+        // CarrierRoamingSatelliteController is a single data point
+        CarrierRoamingSatelliteControllerStats[] atomArray =
+                mAtoms.carrierRoamingSatelliteControllerStats;
+        if (atomArray == null || atomArray.length == 0) {
+            atomArray = new CarrierRoamingSatelliteControllerStats[] {new
+                    CarrierRoamingSatelliteControllerStats()};
+        }
+
+        CarrierRoamingSatelliteControllerStats atom = atomArray[0];
+        atom.configDataSource = stats.configDataSource;
+        atom.countOfEntitlementStatusQueryRequest += stats.countOfEntitlementStatusQueryRequest;
+        atom.countOfSatelliteConfigUpdateRequest += stats.countOfSatelliteConfigUpdateRequest;
+        atom.countOfSatelliteNotificationDisplayed += stats.countOfSatelliteNotificationDisplayed;
+        atom.satelliteSessionGapMinSec = stats.satelliteSessionGapMinSec;
+        atom.satelliteSessionGapAvgSec = stats.satelliteSessionGapAvgSec;
+        atom.satelliteSessionGapMaxSec = stats.satelliteSessionGapMaxSec;
+
+        mAtoms.carrierRoamingSatelliteControllerStats = atomArray;
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link SatelliteEntitlement} to the storage. */
+    public synchronized void addSatelliteEntitlementStats(SatelliteEntitlement stats) {
+        SatelliteEntitlement existingStats = find(stats);
+        if (existingStats != null) {
+            existingStats.count += 1;
+        } else {
+            mAtoms.satelliteEntitlement = insertAtRandomPlace(mAtoms.satelliteEntitlement,
+                    stats, mMaxNumSatelliteStats);
+        }
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link SatelliteConfigUpdater} to the storage. */
+    public synchronized void addSatelliteConfigUpdaterStats(SatelliteConfigUpdater stats) {
+        SatelliteConfigUpdater existingStats = find(stats);
+        if (existingStats != null) {
+            existingStats.count += 1;
+        } else {
+            mAtoms.satelliteConfigUpdater = insertAtRandomPlace(mAtoms.satelliteConfigUpdater,
+                    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}.
@@ -1465,6 +1527,84 @@
         }
     }
 
+    /**
+     * Returns and clears the {@link CarrierRoamingSatelliteSession} stats if last pulled
+     * longer than {@code minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized CarrierRoamingSatelliteSession[] getCarrierRoamingSatelliteSessionStats(
+            long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.carrierRoamingSatelliteSessionPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.carrierRoamingSatelliteSessionPullTimestampMillis = getWallTimeMillis();
+            CarrierRoamingSatelliteSession[] statsArray = mAtoms.carrierRoamingSatelliteSession;
+            mAtoms.carrierRoamingSatelliteSession = new CarrierRoamingSatelliteSession[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link CarrierRoamingSatelliteControllerStats} stats if last pulled
+     * longer than {@code minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized CarrierRoamingSatelliteControllerStats[]
+            getCarrierRoamingSatelliteControllerStats(long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = getWallTimeMillis();
+            CarrierRoamingSatelliteControllerStats[] statsArray =
+                    mAtoms.carrierRoamingSatelliteControllerStats;
+            mAtoms.carrierRoamingSatelliteControllerStats =
+                    new CarrierRoamingSatelliteControllerStats[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link SatelliteEntitlement} stats if last pulled longer than {@code
+     * minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteEntitlement[] getSatelliteEntitlementStats(
+            long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteEntitlementPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteEntitlementPullTimestampMillis = getWallTimeMillis();
+            SatelliteEntitlement[] statsArray = mAtoms.satelliteEntitlement;
+            mAtoms.satelliteEntitlement = new SatelliteEntitlement[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link SatelliteConfigUpdater} stats if last pulled longer than {@code
+     * minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteConfigUpdater[] getSatelliteConfigUpdaterStats(
+            long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteConfigUpdaterPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteConfigUpdaterPullTimestampMillis = getWallTimeMillis();
+            SatelliteConfigUpdater[] statsArray = mAtoms.satelliteConfigUpdater;
+            mAtoms.satelliteConfigUpdater = new SatelliteConfigUpdater[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);
@@ -1615,6 +1755,16 @@
             atoms.satelliteSosMessageRecommender = sanitizeAtoms(
                     atoms.satelliteSosMessageRecommender, SatelliteSosMessageRecommender.class,
                     mMaxNumSatelliteStats);
+            atoms.carrierRoamingSatelliteSession = sanitizeAtoms(
+                    atoms.carrierRoamingSatelliteSession, CarrierRoamingSatelliteSession.class,
+                    mMaxNumSatelliteStats);
+            atoms.carrierRoamingSatelliteControllerStats = sanitizeAtoms(
+                    atoms.carrierRoamingSatelliteControllerStats,
+                    CarrierRoamingSatelliteControllerStats.class, mMaxNumSatelliteControllerStats);
+            atoms.satelliteEntitlement = sanitizeAtoms(atoms.satelliteEntitlement,
+                    SatelliteEntitlement.class, mMaxNumSatelliteStats);
+            atoms.satelliteConfigUpdater = sanitizeAtoms(atoms.satelliteConfigUpdater,
+                    SatelliteConfigUpdater.class, mMaxNumSatelliteStats);
 
             // out of caution, sanitize also the timestamps
             atoms.voiceCallRatUsagePullTimestampMillis =
@@ -1677,6 +1827,14 @@
                     sanitizeTimestamp(atoms.satelliteProvisionPullTimestampMillis);
             atoms.satelliteSosMessageRecommenderPullTimestampMillis =
                     sanitizeTimestamp(atoms.satelliteSosMessageRecommenderPullTimestampMillis);
+            atoms.carrierRoamingSatelliteSessionPullTimestampMillis = sanitizeTimestamp(
+                    atoms.carrierRoamingSatelliteSessionPullTimestampMillis);
+            atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = sanitizeTimestamp(
+                    atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis);
+            atoms.satelliteEntitlementPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteEntitlementPullTimestampMillis);
+            atoms.satelliteConfigUpdaterPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteConfigUpdaterPullTimestampMillis);
             return atoms;
         } catch (NoSuchFileException e) {
             Rlog.d(TAG, "PersistAtoms file not found");
@@ -2113,6 +2271,36 @@
     }
 
     /**
+     * Returns SatelliteEntitlement atom that has same values or {@code null} if it does not exist.
+     */
+    private @Nullable SatelliteEntitlement find(SatelliteEntitlement key) {
+        for (SatelliteEntitlement stats : mAtoms.satelliteEntitlement) {
+            if (stats.carrierId == key.carrierId
+                    && stats.result == key.result
+                    && stats.entitlementStatus == key.entitlementStatus
+                    && stats.isRetry == key.isRetry) {
+                return stats;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns SatelliteConfigUpdater atom that has same values
+     * or {@code null} if it does not exist.
+     */
+    private @Nullable SatelliteConfigUpdater find(SatelliteConfigUpdater key) {
+        for (SatelliteConfigUpdater stats : mAtoms.satelliteConfigUpdater) {
+            if (stats.configVersion == key.configVersion
+                    && stats.oemConfigResult == key.oemConfigResult
+                    && stats.carrierConfigResult == key.carrierConfigResult) {
+                return stats;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Inserts a new element in a random position in an array with a maximum size.
      *
      * <p>If the array is full, merge with existing item if possible or replace one item randomly.
@@ -2368,6 +2556,10 @@
         atoms.satelliteOutgoingDatagramPullTimestampMillis = currentTime;
         atoms.satelliteProvisionPullTimestampMillis = currentTime;
         atoms.satelliteSosMessageRecommenderPullTimestampMillis = currentTime;
+        atoms.carrierRoamingSatelliteSessionPullTimestampMillis = currentTime;
+        atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = currentTime;
+        atoms.satelliteEntitlementPullTimestampMillis = currentTime;
+        atoms.satelliteConfigUpdaterPullTimestampMillis = 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 e097c62..9bb77d2 100644
--- a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
@@ -22,14 +22,22 @@
 import android.telephony.satellite.SatelliteManager;
 
 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.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender;
+import com.android.internal.telephony.satellite.SatelliteConstants;
 import com.android.telephony.Rlog;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** Tracks Satellite metrics for each phone */
 public class SatelliteStats {
     private static final String TAG = SatelliteStats.class.getSimpleName();
@@ -1225,6 +1233,691 @@
         }
     }
 
+    /**
+     * A data class to contain whole component of {@link CarrierRoamingSatelliteSession} atom.
+     * Refer to {@link #onCarrierRoamingSatelliteSessionMetrics(
+     * CarrierRoamingSatelliteSessionParams)}.
+     */
+    public class CarrierRoamingSatelliteSessionParams {
+        private final int mCarrierId;
+        private final boolean mIsNtnRoamingInHomeCountry;
+        private final int mTotalSatelliteModeTimeSec;
+        private final int mNumberOfSatelliteConnections;
+        private final int mAvgDurationOfSatelliteConnectionSec;
+        private final int mSatelliteConnectionGapMinSec;
+        private final int mSatelliteConnectionGapAvgSec;
+        private final int mSatelliteConnectionGapMaxSec;
+        private final int mRsrpAvg;
+        private final int mRsrpMedian;
+        private final int mRssnrAvg;
+        private final int mRssnrMedian;
+        private final int mCountOfIncomingSms;
+        private final int mCountOfOutgoingSms;
+        private final int mCountOfIncomingMms;
+        private final int mCountOfOutgoingMms;
+
+        private CarrierRoamingSatelliteSessionParams(Builder builder) {
+            this.mCarrierId = builder.mCarrierId;
+            this.mIsNtnRoamingInHomeCountry = builder.mIsNtnRoamingInHomeCountry;
+            this.mTotalSatelliteModeTimeSec = builder.mTotalSatelliteModeTimeSec;
+            this.mNumberOfSatelliteConnections = builder.mNumberOfSatelliteConnections;
+            this.mAvgDurationOfSatelliteConnectionSec =
+                    builder.mAvgDurationOfSatelliteConnectionSec;
+            this.mSatelliteConnectionGapMinSec = builder.mSatelliteConnectionGapMinSec;
+            this.mSatelliteConnectionGapAvgSec = builder.mSatelliteConnectionGapAvgSec;
+            this.mSatelliteConnectionGapMaxSec = builder.mSatelliteConnectionGapMaxSec;
+            this.mRsrpAvg = builder.mRsrpAvg;
+            this.mRsrpMedian = builder.mRsrpMedian;
+            this.mRssnrAvg = builder.mRssnrAvg;
+            this.mRssnrMedian = builder.mRssnrMedian;
+            this.mCountOfIncomingSms = builder.mCountOfIncomingSms;
+            this.mCountOfOutgoingSms = builder.mCountOfOutgoingSms;
+            this.mCountOfIncomingMms = builder.mCountOfIncomingMms;
+            this.mCountOfOutgoingMms = builder.mCountOfOutgoingMms;
+        }
+
+        public int getCarrierId() {
+            return mCarrierId;
+        }
+
+        public boolean getIsNtnRoamingInHomeCountry() {
+            return mIsNtnRoamingInHomeCountry;
+        }
+
+        public int getTotalSatelliteModeTimeSec() {
+            return mTotalSatelliteModeTimeSec;
+        }
+
+        public int getNumberOfSatelliteConnections() {
+            return mNumberOfSatelliteConnections;
+        }
+
+        public int getAvgDurationOfSatelliteConnectionSec() {
+            return mAvgDurationOfSatelliteConnectionSec;
+        }
+
+        public int getSatelliteConnectionGapMinSec() {
+            return mSatelliteConnectionGapMinSec;
+        }
+
+        public int getSatelliteConnectionGapAvgSec() {
+            return mSatelliteConnectionGapAvgSec;
+        }
+
+        public int getSatelliteConnectionGapMaxSec() {
+            return mSatelliteConnectionGapMaxSec;
+        }
+
+        public int getRsrpAvg() {
+            return mRsrpAvg;
+        }
+
+        public int getRsrpMedian() {
+            return mRsrpMedian;
+        }
+
+        public int getRssnrAvg() {
+            return mRssnrAvg;
+        }
+
+        public int getRssnrMedian() {
+            return mRssnrMedian;
+        }
+
+        public int getCountOfIncomingSms() {
+            return mCountOfIncomingSms;
+        }
+
+        public int getCountOfOutgoingSms() {
+            return mCountOfOutgoingSms;
+        }
+
+        public int getCountOfIncomingMms() {
+            return mCountOfIncomingMms;
+        }
+
+        public int getCountOfOutgoingMms() {
+            return mCountOfOutgoingMms;
+        }
+
+        /**
+         * A builder class to create {@link CarrierRoamingSatelliteSessionParams} data structure
+         * class
+         */
+        public static class Builder {
+            private int mCarrierId = -1;
+            private boolean mIsNtnRoamingInHomeCountry = false;
+            private int mTotalSatelliteModeTimeSec = 0;
+            private int mNumberOfSatelliteConnections = 0;
+            private int mAvgDurationOfSatelliteConnectionSec = 0;
+            private int mSatelliteConnectionGapMinSec = 0;
+            private int mSatelliteConnectionGapAvgSec = 0;
+            private int mSatelliteConnectionGapMaxSec = 0;
+            private int mRsrpAvg = 0;
+            private int mRsrpMedian = 0;
+            private int mRssnrAvg = 0;
+            private int mRssnrMedian = 0;
+            private int mCountOfIncomingSms = 0;
+            private int mCountOfOutgoingSms = 0;
+            private int mCountOfIncomingMms = 0;
+            private int mCountOfOutgoingMms = 0;
+
+            /**
+             * Sets carrierId value of {@link CarrierRoamingSatelliteSession} atom
+             * then returns Builder class
+             */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = carrierId;
+                return this;
+            }
+
+            /**
+             * Sets isNtnRoamingInHomeCountry value of {@link CarrierRoamingSatelliteSession} atom
+             * then returns Builder class
+             */
+            public Builder setIsNtnRoamingInHomeCountry(boolean isNtnRoamingInHomeCountry) {
+                this.mIsNtnRoamingInHomeCountry = isNtnRoamingInHomeCountry;
+                return this;
+            }
+
+            /**
+             * Sets totalSatelliteModeTimeSec value of {@link CarrierRoamingSatelliteSession} atom
+             * then returns Builder class
+             */
+            public Builder setTotalSatelliteModeTimeSec(int totalSatelliteModeTimeSec) {
+                this.mTotalSatelliteModeTimeSec = totalSatelliteModeTimeSec;
+                return this;
+            }
+
+
+            /**
+             * Sets numberOfSatelliteConnections value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setNumberOfSatelliteConnections(int numberOfSatelliteConnections) {
+                this.mNumberOfSatelliteConnections = numberOfSatelliteConnections;
+                return this;
+            }
+
+            /**
+             * Sets avgDurationOfSatelliteConnectionSec value of
+             * {@link CarrierRoamingSatelliteSession} atom then returns Builder class
+             */
+            public Builder setAvgDurationOfSatelliteConnectionSec(
+                    int avgDurationOfSatelliteConnectionSec) {
+                this.mAvgDurationOfSatelliteConnectionSec = avgDurationOfSatelliteConnectionSec;
+                return this;
+            }
+
+            /**
+             * Sets satelliteConnectionGapMinSec value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setSatelliteConnectionGapMinSec(int satelliteConnectionGapMinSec) {
+                this.mSatelliteConnectionGapMinSec = satelliteConnectionGapMinSec;
+                return this;
+            }
+
+            /**
+             * Sets satelliteConnectionGapAvgSec value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setSatelliteConnectionGapAvgSec(int satelliteConnectionGapAvgSec) {
+                this.mSatelliteConnectionGapAvgSec = satelliteConnectionGapAvgSec;
+                return this;
+            }
+
+            /**
+             * Sets satelliteConnectionGapMaxSec value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setSatelliteConnectionGapMaxSec(int satelliteConnectionGapMaxSec) {
+                this.mSatelliteConnectionGapMaxSec = satelliteConnectionGapMaxSec;
+                return this;
+            }
+
+            /**
+             * Sets rsrpAvg value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setRsrpAvg(int rsrpAvg) {
+                this.mRsrpAvg = rsrpAvg;
+                return this;
+            }
+
+            /**
+             * Sets rsrpMedian value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setRsrpMedian(int rsrpMedian) {
+                this.mRsrpMedian = rsrpMedian;
+                return this;
+            }
+
+            /**
+             * Sets rssnrAvg value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setRssnrAvg(int rssnrAvg) {
+                this.mRssnrAvg = rssnrAvg;
+                return this;
+            }
+
+            /**
+             * Sets rssnrMedian value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setRssnrMedian(int rssnrMedian) {
+                this.mRssnrMedian = rssnrMedian;
+                return this;
+            }
+
+
+            /**
+             * Sets countOfIncomingSms value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfIncomingSms(int countOfIncomingSms) {
+                this.mCountOfIncomingSms = countOfIncomingSms;
+                return this;
+            }
+
+            /**
+             * Sets countOfOutgoingSms value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfOutgoingSms(int countOfOutgoingSms) {
+                this.mCountOfOutgoingSms = countOfOutgoingSms;
+                return this;
+            }
+
+            /**
+             * Sets countOfIncomingMms value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfIncomingMms(int countOfIncomingMms) {
+                this.mCountOfIncomingMms = countOfIncomingMms;
+                return this;
+            }
+
+            /**
+             * Sets countOfOutgoingMms value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfOutgoingMms(int countOfOutgoingMms) {
+                this.mCountOfOutgoingMms = countOfOutgoingMms;
+                return this;
+            }
+
+            /**
+             * Returns CarrierRoamingSatelliteSessionParams, which contains whole component of
+             * {@link CarrierRoamingSatelliteSession} atom
+             */
+            public CarrierRoamingSatelliteSessionParams build() {
+                return new SatelliteStats()
+                        .new CarrierRoamingSatelliteSessionParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "CarrierRoamingSatelliteSessionParams("
+                    + "carrierId=" + mCarrierId
+                    + ", isNtnRoamingInHomeCountry=" + mIsNtnRoamingInHomeCountry
+                    + ", totalSatelliteModeTimeSec=" + mTotalSatelliteModeTimeSec
+                    + ", numberOfSatelliteConnections=" + mNumberOfSatelliteConnections
+                    + ", avgDurationOfSatelliteConnectionSec="
+                    + mAvgDurationOfSatelliteConnectionSec
+                    + ", satelliteConnectionGapMinSec=" + mSatelliteConnectionGapMinSec
+                    + ", satelliteConnectionGapAvgSec=" + mSatelliteConnectionGapAvgSec
+                    + ", satelliteConnectionGapMaxSec=" + mSatelliteConnectionGapMaxSec
+                    + ", rsrpAvg=" + mRsrpAvg
+                    + ", rsrpMedian=" + mRsrpMedian
+                    + ", rssnrAvg=" + mRssnrAvg
+                    + ", rssnrMedian=" + mRsrpMedian
+                    + ", countOfIncomingSms=" + mCountOfIncomingSms
+                    + ", countOfOutgoingSms=" + mCountOfOutgoingSms
+                    + ", countOfIncomingMms=" + mCountOfIncomingMms
+                    + ", countOfOutgoingMms=" + mCountOfOutgoingMms
+                    + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link CarrierRoamingSatelliteControllerStats}
+     * atom. Refer to {@link #onCarrierRoamingSatelliteControllerStatsMetrics(
+     * CarrierRoamingSatelliteControllerStatsParams)}.
+     */
+    public class CarrierRoamingSatelliteControllerStatsParams {
+        private final int mConfigDataSource;
+        private final int mCountOfEntitlementStatusQueryRequest;
+        private final int mCountOfSatelliteConfigUpdateRequest;
+        private final int mCountOfSatelliteNotificationDisplayed;
+        private final int mSatelliteSessionGapMinSec;
+        private final int mSatelliteSessionGapAvgSec;
+        private final int mSatelliteSessionGapMaxSec;
+
+        private CarrierRoamingSatelliteControllerStatsParams(Builder builder) {
+            this.mConfigDataSource = builder.mConfigDataSource;
+            this.mCountOfEntitlementStatusQueryRequest =
+                    builder.mCountOfEntitlementStatusQueryRequest;
+            this.mCountOfSatelliteConfigUpdateRequest =
+                    builder.mCountOfSatelliteConfigUpdateRequest;
+            this.mCountOfSatelliteNotificationDisplayed =
+                    builder.mCountOfSatelliteNotificationDisplayed;
+            this.mSatelliteSessionGapMinSec = builder.mSatelliteSessionGapMinSec;
+            this.mSatelliteSessionGapAvgSec = builder.mSatelliteSessionGapAvgSec;
+            this.mSatelliteSessionGapMaxSec = builder.mSatelliteSessionGapMaxSec;
+        }
+
+        public int getConfigDataSource() {
+            return mConfigDataSource;
+        }
+
+
+        public int getCountOfEntitlementStatusQueryRequest() {
+            return mCountOfEntitlementStatusQueryRequest;
+        }
+
+        public int getCountOfSatelliteConfigUpdateRequest() {
+            return mCountOfSatelliteConfigUpdateRequest;
+        }
+
+        public int getCountOfSatelliteNotificationDisplayed() {
+            return mCountOfSatelliteNotificationDisplayed;
+        }
+
+        public int getSatelliteSessionGapMinSec() {
+            return mSatelliteSessionGapMinSec;
+        }
+
+        public int getSatelliteSessionGapAvgSec() {
+            return mSatelliteSessionGapAvgSec;
+        }
+
+        public int getSatelliteSessionGapMaxSec() {
+            return mSatelliteSessionGapMaxSec;
+        }
+
+        /**
+         * A builder class to create {@link CarrierRoamingSatelliteControllerStatsParams}
+         * data structure class
+         */
+        public static class Builder {
+            private int mConfigDataSource = SatelliteConstants.CONFIG_DATA_SOURCE_UNKNOWN;
+            private int mCountOfEntitlementStatusQueryRequest = 0;
+            private int mCountOfSatelliteConfigUpdateRequest = 0;
+            private int mCountOfSatelliteNotificationDisplayed = 0;
+            private int mSatelliteSessionGapMinSec = 0;
+            private int mSatelliteSessionGapAvgSec = 0;
+            private int mSatelliteSessionGapMaxSec = 0;
+
+            /**
+             * Sets configDataSource value of {@link CarrierRoamingSatelliteControllerStats} atom
+             * then returns Builder class
+             */
+            public Builder setConfigDataSource(int configDataSource) {
+                this.mConfigDataSource = configDataSource;
+                return this;
+            }
+
+            /**
+             * Sets countOfEntitlementStatusQueryRequest value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setCountOfEntitlementStatusQueryRequest(
+                    int countOfEntitlementStatusQueryRequest) {
+                this.mCountOfEntitlementStatusQueryRequest = countOfEntitlementStatusQueryRequest;
+                return this;
+            }
+
+            /**
+             * Sets countOfSatelliteConfigUpdateRequest value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setCountOfSatelliteConfigUpdateRequest(
+                    int countOfSatelliteConfigUpdateRequest) {
+                this.mCountOfSatelliteConfigUpdateRequest = countOfSatelliteConfigUpdateRequest;
+                return this;
+            }
+
+            /**
+             * Sets countOfSatelliteNotificationDisplayed value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setCountOfSatelliteNotificationDisplayed(
+                    int countOfSatelliteNotificationDisplayed) {
+                this.mCountOfSatelliteNotificationDisplayed = countOfSatelliteNotificationDisplayed;
+                return this;
+            }
+
+            /**
+             * Sets satelliteSessionGapMinSec value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setSatelliteSessionGapMinSec(int satelliteSessionGapMinSec) {
+                this.mSatelliteSessionGapMinSec = satelliteSessionGapMinSec;
+                return this;
+            }
+
+            /**
+             * Sets satelliteSessionGapAvgSec value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setSatelliteSessionGapAvgSec(int satelliteSessionGapAvgSec) {
+                this.mSatelliteSessionGapAvgSec = satelliteSessionGapAvgSec;
+                return this;
+            }
+
+            /**
+             * Sets satelliteSessionGapMaxSec value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setSatelliteSessionGapMaxSec(int satelliteSessionGapMaxSec) {
+                this.mSatelliteSessionGapMaxSec = satelliteSessionGapMaxSec;
+                return this;
+            }
+
+            /**
+             * Returns CarrierRoamingSatelliteControllerStatsParams, which contains whole component
+             * of {@link CarrierRoamingSatelliteControllerStats} atom
+             */
+            public CarrierRoamingSatelliteControllerStatsParams build() {
+                return new SatelliteStats()
+                        .new CarrierRoamingSatelliteControllerStatsParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "CarrierRoamingSatelliteControllerStatsParams("
+                    + "configDataSource=" + mConfigDataSource
+                    + ", countOfEntitlementStatusQueryRequest="
+                    + mCountOfEntitlementStatusQueryRequest
+                    + ", countOfSatelliteConfigUpdateRequest="
+                    + mCountOfSatelliteConfigUpdateRequest
+                    + ", countOfSatelliteNotificationDisplayed="
+                    + mCountOfSatelliteNotificationDisplayed
+                    + ", satelliteSessionGapMinSec=" + mSatelliteSessionGapMinSec
+                    + ", satelliteSessionGapAvgSec=" + mSatelliteSessionGapAvgSec
+                    + ", satelliteSessionGapMaxSec=" + mSatelliteSessionGapMaxSec
+                    + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link SatelliteEntitlement} atom.
+     * Refer to {@link #onSatelliteEntitlementMetrics(SatelliteEntitlementParams)}.
+     */
+    public class SatelliteEntitlementParams {
+        private final int mCarrierId;
+        private final int mResult;
+        private final int mEntitlementStatus;
+        private final boolean mIsRetry;
+        private final int mCount;
+
+        private SatelliteEntitlementParams(Builder builder) {
+            this.mCarrierId = builder.mCarrierId;
+            this.mResult = builder.mResult;
+            this.mEntitlementStatus = builder.mEntitlementStatus;
+            this.mIsRetry = builder.mIsRetry;
+            this.mCount = builder.mCount;
+        }
+
+        public int getCarrierId() {
+            return mCarrierId;
+        }
+
+        public int getResult() {
+            return mResult;
+        }
+
+        public int getEntitlementStatus() {
+            return mEntitlementStatus;
+        }
+
+        public boolean getIsRetry() {
+            return mIsRetry;
+        }
+
+        public int getCount() {
+            return mCount;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteEntitlementParams} data structure class
+         */
+        public static class Builder {
+            private int mCarrierId = -1;
+            private int mResult = -1;
+            private int mEntitlementStatus = -1;
+            private boolean mIsRetry = false;
+            private int mCount = -1;
+
+            /**
+             * Sets carrierId value of {@link SatelliteEntitlement} atom
+             * then returns Builder class
+             */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = carrierId;
+                return this;
+            }
+
+            /**
+             * Sets result value of {@link SatelliteEntitlement} atom
+             * then returns Builder class
+             */
+            public Builder setResult(int result) {
+                this.mResult = result;
+                return this;
+            }
+
+            /**
+             * Sets entitlementStatus value of {@link SatelliteEntitlement} atom
+             * then returns Builder class
+             */
+            public Builder setEntitlementStatus(int entitlementStatus) {
+                this.mEntitlementStatus = entitlementStatus;
+                return this;
+            }
+
+            /**
+             * Sets isRetry value of {@link SatelliteEntitlement} atom
+             * then returns Builder class
+             */
+            public Builder setIsRetry(boolean isRetry) {
+                this.mIsRetry = isRetry;
+                return this;
+            }
+
+            /**
+             * Sets count value of {@link SatelliteEntitlement} atom
+             * then returns Builder class
+             */
+            public Builder setCount(int count) {
+                this.mCount = count;
+                return this;
+            }
+
+            /**
+             * Returns SatelliteEntitlementParams, which contains whole component of
+             * {@link SatelliteEntitlement} atom
+             */
+            public SatelliteEntitlementParams build() {
+                return new SatelliteStats()
+                        .new SatelliteEntitlementParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "SatelliteEntitlementParams("
+                    + "carrierId=" + mCarrierId
+                    + ", result=" + mResult
+                    + ", entitlementStatus=" + mEntitlementStatus
+                    + ", isRetry=" + mIsRetry
+                    + ", count=" + mCount + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link SatelliteConfigUpdater} atom.
+     * Refer to {@link #onSatelliteConfigUpdaterMetrics(SatelliteConfigUpdaterParams)}.
+     */
+    public class SatelliteConfigUpdaterParams {
+        private final int mConfigVersion;
+        private final int mOemConfigResult;
+        private final int mCarrierConfigResult;
+        private final int mCount;
+
+        private SatelliteConfigUpdaterParams(Builder builder) {
+            this.mConfigVersion = builder.mConfigVersion;
+            this.mOemConfigResult = builder.mOemConfigResult;
+            this.mCarrierConfigResult = builder.mCarrierConfigResult;
+            this.mCount = builder.mCount;
+        }
+
+        public int getConfigVersion() {
+            return mConfigVersion;
+        }
+
+        public int getOemConfigResult() {
+            return mOemConfigResult;
+        }
+
+        public int getCarrierConfigResult() {
+            return mCarrierConfigResult;
+        }
+
+        public int getCount() {
+            return mCount;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteConfigUpdaterParams} data structure class
+         */
+        public static class Builder {
+            private int mConfigVersion = -1;
+            private int mOemConfigResult = -1;
+            private int mCarrierConfigResult = -1;
+            private int mCount = -1;
+
+            /**
+             * Sets configVersion value of {@link SatelliteConfigUpdater} atom
+             * then returns Builder class
+             */
+            public Builder setConfigVersion(int configVersion) {
+                this.mConfigVersion = configVersion;
+                return this;
+            }
+
+            /**
+             * Sets oemConfigResult value of {@link SatelliteConfigUpdater} atom
+             * then returns Builder class
+             */
+            public Builder setOemConfigResult(int oemConfigResult) {
+                this.mOemConfigResult = oemConfigResult;
+                return this;
+            }
+
+            /**
+             * Sets carrierConfigResult value of {@link SatelliteConfigUpdater} atom
+             * then returns Builder class
+             */
+            public Builder setCarrierConfigResult(int carrierConfigResult) {
+                this.mCarrierConfigResult = carrierConfigResult;
+                return this;
+            }
+
+            /**
+             * Sets count value of {@link SatelliteConfigUpdater} atom
+             * then returns Builder class
+             */
+            public Builder setCount(int count) {
+                this.mCount = count;
+                return this;
+            }
+
+            /**
+             * Returns SatelliteConfigUpdaterParams, which contains whole component of
+             * {@link SatelliteConfigUpdater} atom
+             */
+            public SatelliteConfigUpdaterParams build() {
+                return new SatelliteStats()
+                        .new SatelliteConfigUpdaterParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "SatelliteConfigUpdaterParams("
+                    + "configVersion=" + mConfigVersion
+                    + ", oemConfigResult=" + mOemConfigResult
+                    + ", carrierConfigResult=" + mCarrierConfigResult
+                    + ", count=" + mCount + ")";
+        }
+    }
+
     /**  Create a new atom or update an existing atom for SatelliteController metrics */
     public synchronized void onSatelliteControllerMetrics(SatelliteControllerParams param) {
         SatelliteController proto = new SatelliteController();
@@ -1331,4 +2024,62 @@
         proto.count = 1;
         mAtomsStorage.addSatelliteSosMessageRecommenderStats(proto);
     }
+
+    /**  Create a new atom for CarrierRoamingSatelliteSession metrics */
+    public synchronized  void onCarrierRoamingSatelliteSessionMetrics(
+            CarrierRoamingSatelliteSessionParams param) {
+        CarrierRoamingSatelliteSession proto = new CarrierRoamingSatelliteSession();
+        proto.carrierId = param.getCarrierId();
+        proto.isNtnRoamingInHomeCountry = param.getIsNtnRoamingInHomeCountry();
+        proto.totalSatelliteModeTimeSec = param.getTotalSatelliteModeTimeSec();
+        proto.numberOfSatelliteConnections = param.getNumberOfSatelliteConnections();
+        proto.avgDurationOfSatelliteConnectionSec = param.getAvgDurationOfSatelliteConnectionSec();
+        proto.satelliteConnectionGapMinSec = param.mSatelliteConnectionGapMinSec;
+        proto.satelliteConnectionGapAvgSec = param.mSatelliteConnectionGapAvgSec;
+        proto.satelliteConnectionGapMaxSec = param.mSatelliteConnectionGapMaxSec;
+        proto.rsrpAvg = param.mRsrpAvg;
+        proto.rsrpMedian = param.mRsrpMedian;
+        proto.rssnrAvg = param.mRssnrAvg;
+        proto.rssnrMedian = param.mRssnrMedian;
+        proto.countOfIncomingSms = param.mCountOfIncomingSms;
+        proto.countOfOutgoingSms = param.mCountOfOutgoingSms;
+        proto.countOfIncomingMms = param.mCountOfIncomingMms;
+        proto.countOfOutgoingMms = param.mCountOfOutgoingMms;
+        mAtomsStorage.addCarrierRoamingSatelliteSessionStats(proto);
+    }
+
+    /**  Create a new atom for CarrierRoamingSatelliteSession metrics */
+    public synchronized  void onCarrierRoamingSatelliteControllerStatsMetrics(
+            CarrierRoamingSatelliteControllerStatsParams param) {
+        CarrierRoamingSatelliteControllerStats proto = new CarrierRoamingSatelliteControllerStats();
+        proto.configDataSource = param.mConfigDataSource;
+        proto.countOfEntitlementStatusQueryRequest = param.mCountOfEntitlementStatusQueryRequest;
+        proto.countOfSatelliteConfigUpdateRequest = param.mCountOfSatelliteConfigUpdateRequest;
+        proto.countOfSatelliteNotificationDisplayed = param.mCountOfSatelliteNotificationDisplayed;
+        proto.satelliteSessionGapMinSec = param.mSatelliteSessionGapMinSec;
+        proto.satelliteSessionGapAvgSec = param.mSatelliteSessionGapAvgSec;
+        proto.satelliteSessionGapMaxSec = param.mSatelliteSessionGapMaxSec;
+        mAtomsStorage.addCarrierRoamingSatelliteControllerStats(proto);
+    }
+
+    /**  Create a new atom for SatelliteEntitlement metrics */
+    public synchronized  void onSatelliteEntitlementMetrics(SatelliteEntitlementParams param) {
+        SatelliteEntitlement proto = new SatelliteEntitlement();
+        proto.carrierId = param.getCarrierId();
+        proto.result = param.getResult();
+        proto.entitlementStatus = param.getEntitlementStatus();
+        proto.isRetry = param.getIsRetry();
+        proto.count = param.getCount();
+        mAtomsStorage.addSatelliteEntitlementStats(proto);
+    }
+
+    /**  Create a new atom for SatelliteConfigUpdater metrics */
+    public synchronized  void onSatelliteConfigUpdaterMetrics(SatelliteConfigUpdaterParams param) {
+        SatelliteConfigUpdater proto = new SatelliteConfigUpdater();
+        proto.configVersion = param.getConfigVersion();
+        proto.oemConfigResult = param.getOemConfigResult();
+        proto.carrierConfigResult = param.getCarrierConfigResult();
+        proto.count = param.getCount();
+        mAtomsStorage.addSatelliteConfigUpdaterStats(proto);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java b/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
new file mode 100644
index 0000000..f88069f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class SatelliteConstants {
+    public static final int CONFIG_DATA_SOURCE_UNKNOWN = 0;
+    public static final int CONFIG_DATA_SOURCE_ENTITLEMENT = 1;
+    public static final int CONFIG_DATA_SOURCE_CONFIG_UPDATER = 2;
+    public static final int CONFIG_DATA_SOURCE_CARRIER_CONFIG = 3;
+    public static final int CONFIG_DATA_SOURCE_DEVICE_CONFIG = 4;
+
+    @IntDef(prefix = {"CONFIG_DATA_SOURCE_"}, value = {
+            CONFIG_DATA_SOURCE_UNKNOWN,
+            CONFIG_DATA_SOURCE_ENTITLEMENT,
+            CONFIG_DATA_SOURCE_CONFIG_UPDATER,
+            CONFIG_DATA_SOURCE_CARRIER_CONFIG,
+            CONFIG_DATA_SOURCE_DEVICE_CONFIG
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConfigDataSource {}
+
+    public static final int SATELLITE_ENTITLEMENT_STATUS_UNKNOWN = 0;
+    public static final int SATELLITE_ENTITLEMENT_STATUS_DISABLED = 1;
+    public static final int SATELLITE_ENTITLEMENT_STATUS_ENABLED = 2;
+    public static final int SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE = 3;
+    public static final int SATELLITE_ENTITLEMENT_STATUS_PROVISIONING = 4;
+
+    @IntDef(prefix = {"SATELLITE_ENTITLEMENT_STATUS_"}, value = {
+            SATELLITE_ENTITLEMENT_STATUS_UNKNOWN,
+            SATELLITE_ENTITLEMENT_STATUS_DISABLED,
+            SATELLITE_ENTITLEMENT_STATUS_ENABLED,
+            SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE,
+            SATELLITE_ENTITLEMENT_STATUS_PROVISIONING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SatelliteEntitlementStatus {}
+
+    public static final int CONFIG_UPDATE_RESULT_UNKNOWN = 0;
+    public static final int CONFIG_UPDATE_RESULT_SUCCESS = 1;
+    public static final int CONFIG_UPDATE_RESULT_INVALID_DOMAIN = 2;
+    public static final int CONFIG_UPDATE_RESULT_INVALID_VERSION = 3;
+    public static final int CONFIG_UPDATE_RESULT_NO_DATA = 4;
+    public static final int CONFIG_UPDATE_RESULT_NO_SATELLITE_DATA = 5;
+    public static final int CONFIG_UPDATE_RESULT_PARSE_ERROR = 6;
+    public static final int CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_PLMN = 7;
+    public static final int CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_SUPPORTED_SERVICES = 8;
+    public static final int CONFIG_UPDATE_RESULT_DEVICE_DATA_INVALID_COUNTRY_CODE = 9;
+    public static final int CONFIG_UPDATE_RESULT_DEVICE_DATA_INVALID_S2_CELL_FILE = 10;
+    public static final int CONFIG_UPDATE_RESULT_IO_ERROR = 11;
+
+    @IntDef(prefix = {"CONFIG_UPDATE_RESULT_"}, value = {
+            CONFIG_UPDATE_RESULT_UNKNOWN,
+            CONFIG_UPDATE_RESULT_SUCCESS,
+            CONFIG_UPDATE_RESULT_INVALID_DOMAIN,
+            CONFIG_UPDATE_RESULT_INVALID_VERSION,
+            CONFIG_UPDATE_RESULT_NO_DATA,
+            CONFIG_UPDATE_RESULT_NO_SATELLITE_DATA,
+            CONFIG_UPDATE_RESULT_PARSE_ERROR,
+            CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_PLMN,
+            CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_SUPPORTED_SERVICES,
+            CONFIG_UPDATE_RESULT_DEVICE_DATA_INVALID_COUNTRY_CODE,
+            CONFIG_UPDATE_RESULT_DEVICE_DATA_INVALID_S2_CELL_FILE,
+            CONFIG_UPDATE_RESULT_IO_ERROR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConfigUpdateResult {}
+}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index e3e77ca..aaad18b 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -117,6 +117,8 @@
 import com.android.internal.telephony.configupdate.ConfigProviderAdaptor;
 import com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.satellite.metrics.CarrierRoamingSatelliteControllerStats;
+import com.android.internal.telephony.satellite.metrics.CarrierRoamingSatelliteSessionStats;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
 import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
@@ -226,6 +228,7 @@
     @NonNull private final ControllerMetricsStats mControllerMetricsStats;
     @NonNull private final ProvisionMetricsStats mProvisionMetricsStats;
     @NonNull private SessionMetricsStats mSessionMetricsStats;
+    @NonNull private CarrierRoamingSatelliteControllerStats mCarrierRoamingSatelliteControllerStats;
     @NonNull private final SubscriptionManagerService mSubscriptionManagerService;
     private final CommandsInterface mCi;
     private ContentResolver mContentResolver;
@@ -372,6 +375,10 @@
     @GuardedBy("mSatelliteConnectedLock")
     @NonNull private final SparseBooleanArray mInitialized = new SparseBooleanArray();
 
+    @GuardedBy("mSatelliteConnectedLock")
+    @NonNull private final Map<Integer, CarrierRoamingSatelliteSessionStats>
+            mCarrierRoamingSatelliteSessionStatsMap = new HashMap<>();
+
     /**
      * Key: Subscription ID; Value: set of
      * {@link android.telephony.NetworkRegistrationInfo.ServiceType}
@@ -483,6 +490,8 @@
         mControllerMetricsStats = ControllerMetricsStats.make(mContext);
         mProvisionMetricsStats = ProvisionMetricsStats.getOrCreateInstance();
         mSessionMetricsStats = SessionMetricsStats.getInstance();
+        mCarrierRoamingSatelliteControllerStats =
+                CarrierRoamingSatelliteControllerStats.getOrCreateInstance();
         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
 
         // Create the DatagramController singleton,
@@ -3679,6 +3688,8 @@
                 if (!entitlementPlmnList.isEmpty()) {
                     mMergedPlmnListPerCarrier.put(subId, entitlementPlmnList);
                     logd("mMergedPlmnListPerCarrier is updated by Entitlement");
+                    mCarrierRoamingSatelliteControllerStats.reportConfigDataSource(
+                            SatelliteConstants.CONFIG_DATA_SOURCE_ENTITLEMENT);
                     return;
                 }
             }
@@ -3692,6 +3703,8 @@
                     logd("mMergedPlmnListPerCarrier is updated by ConfigUpdater : "
                             + String.join(",", plmnList));
                     mMergedPlmnListPerCarrier.put(subId, plmnList);
+                    mCarrierRoamingSatelliteControllerStats.reportConfigDataSource(
+                            SatelliteConstants.CONFIG_DATA_SOURCE_CONFIG_UPDATER);
                     return;
                 }
             }
@@ -3702,6 +3715,8 @@
                         mSatelliteServicesSupportedByCarriers.get(subId).keySet().stream().toList();
                 logd("mMergedPlmnListPerCarrier is updated by carrier config: "
                         + String.join(",", carrierPlmnList));
+                mCarrierRoamingSatelliteControllerStats.reportConfigDataSource(
+                        SatelliteConstants.CONFIG_DATA_SOURCE_CARRIER_CONFIG);
             } else {
                 carrierPlmnList = new ArrayList<>();
                 logd("Empty mMergedPlmnListPerCarrier");
@@ -4173,7 +4188,15 @@
             }
 
             synchronized (mSatelliteConnectedLock) {
+                CarrierRoamingSatelliteSessionStats sessionStats =
+                        mCarrierRoamingSatelliteSessionStatsMap.get(subId);
+
                 if (serviceState.isUsingNonTerrestrialNetwork()) {
+                    if (sessionStats != null && !mWasSatelliteConnectedViaCarrier.get(subId)) {
+                        // Log satellite connection start
+                        sessionStats.onConnectionStart();
+                    }
+
                     resetCarrierRoamingSatelliteModeParams(subId);
                     mWasSatelliteConnectedViaCarrier.put(subId, true);
 
@@ -4199,6 +4222,11 @@
                         sendMessageDelayed(obtainMessage(EVENT_NOTIFY_NTN_HYSTERESIS_TIMED_OUT,
                                         phone.getPhoneId()),
                                 getSatelliteConnectionHysteresisTimeMillis(subId));
+
+                        if (sessionStats != null) {
+                            // Log satellite connection end
+                            sessionStats.onConnectionEnd();
+                        }
                     }
                     mWasSatelliteConnectedViaCarrier.put(subId, false);
                 }
@@ -4223,6 +4251,27 @@
                 if (!initialized) mInitialized.put(subId, true);
                 mLastNotifiedNtnMode.put(subId, currNtnMode);
                 phone.notifyCarrierRoamingNtnModeChanged(currNtnMode);
+                logCarrierRoamingSatelliteSessionStats(phone, lastNotifiedNtnMode, currNtnMode);
+            }
+        }
+    }
+
+    private void logCarrierRoamingSatelliteSessionStats(@NonNull Phone phone,
+            boolean lastNotifiedNtnMode, boolean currNtnMode) {
+        synchronized (mSatelliteConnectedLock) {
+            int subId = phone.getSubId();
+            if (!lastNotifiedNtnMode && currNtnMode) {
+                // Log satellite session start
+                CarrierRoamingSatelliteSessionStats sessionStats =
+                        new CarrierRoamingSatelliteSessionStats(mContext, phone.getCarrierId());
+                sessionStats.onSessionStart();
+                mCarrierRoamingSatelliteSessionStatsMap.put(subId, sessionStats);
+            } else if (lastNotifiedNtnMode && !currNtnMode) {
+                // Log satellite session end
+                CarrierRoamingSatelliteSessionStats sessionStats =
+                        mCarrierRoamingSatelliteSessionStatsMap.get(subId);
+                sessionStats.onSessionEnd();
+                mCarrierRoamingSatelliteSessionStatsMap.remove(subId);
             }
         }
     }
@@ -4584,6 +4633,8 @@
 
         notificationManager.notifyAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
                 notificationBuilder.build(), UserHandle.ALL);
+
+        mCarrierRoamingSatelliteControllerStats.reportCountOfSatelliteNotificationDisplayed();
     }
 
     private void resetCarrierRoamingSatelliteModeParams() {
@@ -4597,7 +4648,6 @@
     private void resetCarrierRoamingSatelliteModeParams(int subId) {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) return;
 
-        logd("resetCarrierRoamingSatelliteModeParams subId:" + subId);
         synchronized (mSatelliteConnectedLock) {
             mLastSatelliteDisconnectedTimesMillis.put(subId, null);
             mSatModeCapabilitiesForCarrierRoaming.remove(subId);
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java
new file mode 100644
index 0000000..9524b75
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.util.Log;
+
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.SatelliteConstants;
+
+public class CarrierRoamingSatelliteControllerStats {
+    private static final String TAG = CarrierRoamingSatelliteControllerStats.class.getSimpleName();
+    private static CarrierRoamingSatelliteControllerStats sInstance = null;
+    private static final int ADD_COUNT = 1;
+
+    private SatelliteStats mSatelliteStats;
+
+    private CarrierRoamingSatelliteControllerStats() {
+        mSatelliteStats = SatelliteStats.getInstance();
+    }
+
+    /**
+     * Returns the Singleton instance of CarrierRoamingSatelliteControllerStats 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 CarrierRoamingSatelliteControllerStats
+     */
+    public static CarrierRoamingSatelliteControllerStats getOrCreateInstance() {
+        if (sInstance == null) {
+            logd("Create new CarrierRoamingSatelliteControllerStats.");
+            sInstance = new CarrierRoamingSatelliteControllerStats();
+        }
+        return sInstance;
+    }
+
+    /** Report config data source */
+    public void reportConfigDataSource(@SatelliteConstants.ConfigDataSource int configDataSource) {
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setConfigDataSource(configDataSource)
+                        .build());
+    }
+
+    /** Report count of entitlement status query request */
+    public void reportCountOfEntitlementStatusQueryRequest() {
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setCountOfEntitlementStatusQueryRequest(ADD_COUNT)
+                        .build());
+    }
+
+    /** Report count of satellite config update request */
+    public void reportCountOfSatelliteConfigUpdateRequest() {
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setCountOfSatelliteConfigUpdateRequest(ADD_COUNT)
+                        .build());
+    }
+
+    /** Report count of satellite notification displayed */
+    public void reportCountOfSatelliteNotificationDisplayed() {
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setCountOfSatelliteNotificationDisplayed(ADD_COUNT)
+                        .build());
+    }
+
+    private static void logd(@NonNull String log) {
+        Log.d(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteSessionStats.java b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteSessionStats.java
new file mode 100644
index 0000000..78c5222
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteSessionStats.java
@@ -0,0 +1,203 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.content.Context;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.telephony.metrics.SatelliteStats;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class CarrierRoamingSatelliteSessionStats {
+    private static final String TAG = CarrierRoamingSatelliteSessionStats.class.getSimpleName();
+    private final Context mContext;
+
+    private int mCarrierId;
+    private boolean mIsNtnRoamingInHomeCountry;
+    private int mRsrpAvg;
+    private int mRsrpMedian;
+    private int mRssnrAvg;
+    private int mRssnrMedian;
+    private int mCountOfIncomingSms;
+    private int mCountOfOutgoingSms;
+    private int mCountOfIncomingMms;
+    private int mCountOfOutgoingMms;
+
+    private int mSessionStartTimeSec;
+    private List<Long> mConnectionStartTimeList;
+    private List<Long> mConnectionEndTimeList;
+
+    public CarrierRoamingSatelliteSessionStats(@NonNull Context context, int carrierId) {
+        logd("Create new CarrierRoamingSatelliteSessionStats.");
+        initializeParams();
+
+        mContext = context;
+        mCarrierId = carrierId;
+    }
+
+    /** Log carrier roaming satellite session start */
+    public void onSessionStart() {
+        mSessionStartTimeSec = getCurrentTimeInSec();
+        onConnectionStart();
+    }
+
+    /** Log carrier roaming satellite connection start */
+    public void onConnectionStart() {
+        mConnectionStartTimeList.add(getCurrentTime());
+    }
+
+    /** Log carrier roaming satellite session end */
+    public void onSessionEnd() {
+        onConnectionEnd();
+        reportMetrics();
+    }
+
+    /** Log carrier roaming satellite connection end */
+    public void onConnectionEnd() {
+        mConnectionEndTimeList.add(getCurrentTime());
+    }
+
+    private void reportMetrics() {
+        int totalSatelliteModeTimeSec = mSessionStartTimeSec > 0
+                ? getCurrentTimeInSec() - mSessionStartTimeSec : 0;
+        int numberOfSatelliteConnections = getNumberOfSatelliteConnections();
+        int avgDurationOfSatelliteConnectionSec = getAvgDurationOfSatelliteConnection(
+                numberOfSatelliteConnections);
+
+        List<Integer> connectionGapList = getSatelliteConnectionGapList(
+                numberOfSatelliteConnections);
+        int satelliteConnectionGapMinSec = 0;
+        int satelliteConnectionGapMaxSec = 0;
+        if (!connectionGapList.isEmpty()) {
+            satelliteConnectionGapMinSec = Collections.min(connectionGapList);
+            satelliteConnectionGapMaxSec = Collections.max(connectionGapList);
+        }
+
+        SatelliteStats.CarrierRoamingSatelliteSessionParams params =
+                new SatelliteStats.CarrierRoamingSatelliteSessionParams.Builder()
+                        .setCarrierId(mCarrierId)
+                        .setIsNtnRoamingInHomeCountry(mIsNtnRoamingInHomeCountry)
+                        .setTotalSatelliteModeTimeSec(totalSatelliteModeTimeSec)
+                        .setNumberOfSatelliteConnections(numberOfSatelliteConnections)
+                        .setAvgDurationOfSatelliteConnectionSec(avgDurationOfSatelliteConnectionSec)
+                        .setSatelliteConnectionGapMinSec(satelliteConnectionGapMinSec)
+                        .setSatelliteConnectionGapAvgSec(getAvgConnectionGapSec(connectionGapList))
+                        .setSatelliteConnectionGapMaxSec(satelliteConnectionGapMaxSec)
+                        .setRsrpAvg(mRsrpAvg)
+                        .setRsrpMedian(mRsrpMedian)
+                        .setRssnrAvg(mRssnrAvg)
+                        .setRssnrMedian(mRssnrMedian)
+                        .setCountOfIncomingSms(mCountOfIncomingSms)
+                        .setCountOfOutgoingSms(mCountOfOutgoingSms)
+                        .setCountOfIncomingMms(mCountOfIncomingMms)
+                        .setCountOfOutgoingMms(mCountOfOutgoingMms)
+                        .build();
+        SatelliteStats.getInstance().onCarrierRoamingSatelliteSessionMetrics(params);
+        logd("reportMetrics: " + params);
+        initializeParams();
+    }
+
+    private void initializeParams() {
+        mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        mIsNtnRoamingInHomeCountry = false;
+        mRsrpAvg = 0;
+        mRsrpMedian = 0;
+        mRssnrAvg = 0;
+        mRssnrMedian = 0;
+        mCountOfIncomingSms = 0;
+        mCountOfOutgoingSms = 0;
+        mCountOfIncomingMms = 0;
+        mCountOfOutgoingMms = 0;
+
+        mSessionStartTimeSec = 0;
+        mConnectionStartTimeList = new ArrayList<>();
+        mConnectionEndTimeList = new ArrayList<>();
+    }
+
+    private int getNumberOfSatelliteConnections() {
+        return Math.min(mConnectionStartTimeList.size(), mConnectionEndTimeList.size());
+    }
+
+    private int getAvgDurationOfSatelliteConnection(int numberOfSatelliteConnections) {
+        if (numberOfSatelliteConnections == 0) {
+            return 0;
+        }
+
+        long totalConnectionsDuration = 0;
+        for (int i = 0; i < numberOfSatelliteConnections; i++) {
+            long endTime = mConnectionEndTimeList.get(i);
+            long startTime = mConnectionStartTimeList.get(i);
+            if (endTime >= startTime && startTime > 0) {
+                totalConnectionsDuration += endTime - startTime;
+            }
+        }
+
+        long avgConnectionDuration = totalConnectionsDuration / numberOfSatelliteConnections;
+        return (int) (avgConnectionDuration / 1000L);
+    }
+
+    private List<Integer> getSatelliteConnectionGapList(int numberOfSatelliteConnections) {
+        if (numberOfSatelliteConnections == 0) {
+            return new ArrayList<>();
+        }
+
+        List<Integer> connectionGapList = new ArrayList<>();
+        for (int i = 1; i < numberOfSatelliteConnections; i++) {
+            long prevConnectionEndTime = mConnectionEndTimeList.get(i - 1);
+            long currentConnectionStartTime = mConnectionStartTimeList.get(i);
+            if (currentConnectionStartTime > prevConnectionEndTime && prevConnectionEndTime > 0) {
+                connectionGapList.add((int) (
+                        (currentConnectionStartTime - prevConnectionEndTime) / 1000));
+            }
+        }
+        return connectionGapList;
+    }
+
+    private int getAvgConnectionGapSec(@NonNull List<Integer> connectionGapList) {
+        if (connectionGapList.isEmpty()) {
+            return 0;
+        }
+
+        int totalConnectionGap = 0;
+        for (int gap : connectionGapList) {
+            totalConnectionGap += gap;
+        }
+
+        return (totalConnectionGap / connectionGapList.size());
+    }
+
+    private int getCurrentTimeInSec() {
+        return (int) (System.currentTimeMillis() / 1000);
+    }
+
+    private long getCurrentTime() {
+        return System.currentTimeMillis();
+    }
+
+    private void logd(@NonNull String log) {
+        Log.d(TAG, log);
+    }
+
+    private void loge(@NonNull String log) {
+        Log.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ConfigUpdaterMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ConfigUpdaterMetricsStats.java
new file mode 100644
index 0000000..c379b83
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ConfigUpdaterMetricsStats.java
@@ -0,0 +1,124 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.util.Log;
+
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.SatelliteConstants;
+
+public class ConfigUpdaterMetricsStats {
+    private static final String TAG = ConfigUpdaterMetricsStats.class.getSimpleName();
+    private static ConfigUpdaterMetricsStats sInstance = null;
+
+    private int mConfigVersion;
+    private int mOemConfigResult;
+    private int mCarrierConfigResult;
+
+    private ConfigUpdaterMetricsStats() {
+        initializeConfigUpdaterParams();
+    }
+
+    /**
+     * Returns the Singleton instance of ConfigUpdaterMetricsStats 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 ConfigUpdaterMetricsStats
+     */
+    public static ConfigUpdaterMetricsStats getOrCreateInstance() {
+        if (sInstance == null) {
+            logd("Create new ConfigUpdaterMetricsStats.");
+            sInstance = new ConfigUpdaterMetricsStats();
+        }
+        return sInstance;
+    }
+
+    /** Set config version for config updater metrics */
+    public ConfigUpdaterMetricsStats setConfigVersion(int configVersion) {
+        mConfigVersion = configVersion;
+        return this;
+    }
+
+    /** Set oem config result for config updater metrics */
+    public ConfigUpdaterMetricsStats setOemConfigResult(int oemConfigResult) {
+        mOemConfigResult = oemConfigResult;
+        return this;
+    }
+
+    /** Set carrier config result for config updater metrics */
+    public ConfigUpdaterMetricsStats setCarrierConfigResult(int carrierConfigResult) {
+        mCarrierConfigResult = carrierConfigResult;
+        return this;
+    }
+
+    /** Report metrics on oem config update error */
+    public void reportOemConfigError(int error) {
+        mOemConfigResult = error;
+        reportConfigUpdaterMetrics();
+    }
+
+    /** Report metrics on carrier config update error */
+    public void reportCarrierConfigError(int error) {
+        mCarrierConfigResult = error;
+        reportConfigUpdaterMetrics();
+    }
+
+    /** Report metrics on config update error */
+    public void reportOemAndCarrierConfigError(int error) {
+        mOemConfigResult = error;
+        mCarrierConfigResult = error;
+        reportConfigUpdaterMetrics();
+    }
+
+    /** Report metrics on config update success */
+    public void reportConfigUpdateSuccess() {
+        mOemConfigResult = SatelliteConstants.CONFIG_UPDATE_RESULT_SUCCESS;
+        mCarrierConfigResult = SatelliteConstants.CONFIG_UPDATE_RESULT_SUCCESS;
+        reportConfigUpdaterMetrics();
+    }
+
+
+    /** Report config updater metrics atom to PersistAtomsStorage in telephony */
+    private void reportConfigUpdaterMetrics() {
+        SatelliteStats.SatelliteConfigUpdaterParams configUpdaterParams =
+                new SatelliteStats.SatelliteConfigUpdaterParams.Builder()
+                        .setConfigVersion(mConfigVersion)
+                        .setOemConfigResult(mOemConfigResult)
+                        .setCarrierConfigResult(mCarrierConfigResult)
+                        .setCount(1)
+                        .build();
+        SatelliteStats.getInstance().onSatelliteConfigUpdaterMetrics(configUpdaterParams);
+        logd("reportConfigUpdaterMetrics: " + configUpdaterParams);
+
+        CarrierRoamingSatelliteControllerStats.getOrCreateInstance()
+                .reportCountOfSatelliteConfigUpdateRequest();
+
+        initializeConfigUpdaterParams();
+    }
+
+    private void initializeConfigUpdaterParams() {
+        mConfigVersion = -1;
+        mOemConfigResult = SatelliteConstants.CONFIG_UPDATE_RESULT_UNKNOWN;
+        mCarrierConfigResult = SatelliteConstants.CONFIG_UPDATE_RESULT_UNKNOWN;
+    }
+
+    private static void logd(@NonNull String log) {
+        Log.d(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/EntitlementMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/EntitlementMetricsStats.java
new file mode 100644
index 0000000..4862188
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/EntitlementMetricsStats.java
@@ -0,0 +1,103 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.SatelliteConstants;
+
+public class EntitlementMetricsStats {
+    private static final String TAG = EntitlementMetricsStats.class.getSimpleName();
+    private static EntitlementMetricsStats sInstance = null;
+    private static final int RESULT_SUCCESS = 200;
+
+    private int mSubId;
+    private int mResult;
+    private int mEntitlementStatus;
+    private boolean mIsRetry;
+
+    private EntitlementMetricsStats() {}
+
+    /**
+     * Returns the Singleton instance of EntitlementMetricsStats 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 EntitlementMetricsStats
+     */
+    public static EntitlementMetricsStats getOrCreateInstance() {
+        if (sInstance == null) {
+            logd("Create new EntitlementMetricsStats.");
+            sInstance = new EntitlementMetricsStats();
+        }
+        return sInstance;
+    }
+
+    /** Report metrics on entitlement query request success */
+    public void reportSuccess(int subId,
+            @SatelliteConstants.SatelliteEntitlementStatus int entitlementStatus,
+            boolean isRetry) {
+        mSubId = subId;
+        mResult = RESULT_SUCCESS;
+        mEntitlementStatus = entitlementStatus;
+        mIsRetry = isRetry;
+        reportEntitlementMetrics();
+    }
+
+    /** Report metrics on entitlement query request error */
+    public void reportError(int subId, int result, boolean isRetry) {
+        mSubId = subId;
+        mResult = result;
+        mIsRetry = isRetry;
+        mEntitlementStatus = SatelliteConstants.SATELLITE_ENTITLEMENT_STATUS_UNKNOWN;
+        reportEntitlementMetrics();
+    }
+
+    /** Report entitlement metrics atom to PersistAtomsStorage in telephony */
+    private void reportEntitlementMetrics() {
+        SatelliteStats.SatelliteEntitlementParams entitlementParams =
+                new SatelliteStats.SatelliteEntitlementParams.Builder()
+                        .setCarrierId(getCarrierId(mSubId))
+                        .setResult(mResult)
+                        .setEntitlementStatus(mEntitlementStatus)
+                        .setIsRetry(mIsRetry)
+                        .setCount(1)
+                        .build();
+        SatelliteStats.getInstance().onSatelliteEntitlementMetrics(entitlementParams);
+        logd("reportEntitlementMetrics: " + entitlementParams);
+
+        CarrierRoamingSatelliteControllerStats.getOrCreateInstance()
+                .reportCountOfEntitlementStatusQueryRequest();
+    }
+
+    /** Returns the carrier ID of the given subscription id. */
+    private int getCarrierId(int subId) {
+        int phoneId = SubscriptionManager.getPhoneId(subId);
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        return phone != null ? phone.getCarrierId() : TelephonyManager.UNKNOWN_CARRIER_ID;
+    }
+
+    private static void logd(@NonNull String log) {
+        Log.d(TAG, log);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
index d11b730..7f42ad0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -138,6 +138,8 @@
 import java.util.Set;
 import java.util.concurrent.Executor;
 
+import javax.annotation.Nullable;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class DataNetworkControllerTest extends TelephonyTest {
@@ -1138,18 +1140,23 @@
     }
 
     private @NonNull TelephonyNetworkRequest createNetworkRequest(Integer... capabilities) {
-        return createNetworkRequest(false, capabilities);
+        return createNetworkRequest(null, capabilities);
     }
 
-    private @NonNull TelephonyNetworkRequest createNetworkRequest(boolean restricted,
+    private @NonNull TelephonyNetworkRequest createNetworkRequest(@Nullable Boolean restricted,
                                                                   Integer... capabilities) {
         NetworkCapabilities netCaps = new NetworkCapabilities();
         for (int networkCapability : capabilities) {
             netCaps.addCapability(networkCapability);
         }
 
-        if (restricted) {
-            netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        if (restricted != null) {
+            if (restricted) {
+                netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+            }
+        } else {
+            // Data Network uses the same to define its own capabilities.
+            netCaps.maybeMarkCapabilitiesRestricted();
         }
 
         NetworkRequest nativeNetworkRequest = new NetworkRequest(netCaps,
@@ -1741,6 +1748,14 @@
         verify(mMockedDataNetworkControllerCallback, never())
                 .onConnectedInternetDataNetworksChanged(any());
 
+        // However, WLAN network setup shouldn't be affected
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
+                .getPreferredTransportByNetworkCapability(anyInt());
+        mDataNetworkControllerUT.obtainMessage(5 /*EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS*/,
+                DataEvaluation.DataEvaluationReason.PREFERRED_TRANSPORT_CHANGED).sendToTarget();
+        processAllMessages();
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(any());
+
         mIsNonTerrestrialNetwork = false;
     }
 
@@ -3319,12 +3334,20 @@
                 NetworkCapabilities.NET_CAPABILITY_IMS);
         TelephonyNetworkRequest secondTnr = createNetworkRequest(
                 NetworkCapabilities.NET_CAPABILITY_IMS);
+        TelephonyNetworkRequest thirdTnr = new TelephonyNetworkRequest(
+                new NetworkRequest((new NetworkCapabilities.Builder())
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
+                        .build(),
+                        ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
 
         mDataNetworkControllerUT.addNetworkRequest(firstTnr);
         processAllMessages();
 
         mDataNetworkControllerUT.removeNetworkRequest(firstTnr);
         mDataNetworkControllerUT.addNetworkRequest(secondTnr);
+        mDataNetworkControllerUT.addNetworkRequest(thirdTnr);
         processAllFutureMessages();
 
         verify(mMockedWwanDataServiceManager, times(1)).setupDataCall(anyInt(),
@@ -4157,6 +4180,7 @@
 
         NetworkCapabilities netCaps = new NetworkCapabilities();
         netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+        netCaps.maybeMarkCapabilitiesRestricted();
         netCaps.setRequestorPackageName(FAKE_MMTEL_PACKAGE);
 
         NetworkRequest nativeNetworkRequest = new NetworkRequest(netCaps,
@@ -4209,6 +4233,7 @@
         // setup emergency data network.
         NetworkCapabilities netCaps = new NetworkCapabilities();
         netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
+        netCaps.maybeMarkCapabilitiesRestricted();
         netCaps.setRequestorPackageName(FAKE_MMTEL_PACKAGE);
 
         NetworkRequest nativeNetworkRequest = new NetworkRequest(netCaps,
@@ -5097,7 +5122,8 @@
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+                createNetworkRequest(true/*restricted*/,
+                        NetworkCapabilities.NET_CAPABILITY_INTERNET));
         processAllMessages();
         verifyConnectedNetworkHasDataProfile(mEsimBootstrapDataProfile);
 
@@ -5108,13 +5134,6 @@
                 .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), 2);
         processAllMessages();
         verifyConnectedNetworkHasDataProfile(mEsimBootstrapImsProfile);
-
-        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
-                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
-        mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_RCS));
-        processAllMessages();
-        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
     }
 
     @Test
@@ -5153,7 +5172,7 @@
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+                createNetworkRequest(true, NetworkCapabilities.NET_CAPABILITY_INTERNET));
         processAllMessages();
         // With allowed data limit unlimited, connection is allowed
         verifyConnectedNetworkHasDataProfile(mEsimBootstrapDataProfile);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
index 0426737..bdccb19 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
@@ -16,9 +16,13 @@
 
 package com.android.internal.telephony.metrics;
 
+import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS;
+import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ROAMING_SATELLITE_SESSION;
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH;
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE;
 import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SHORT_CODE_SMS;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_CONFIG_UPDATER;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_ENTITLEMENT;
 import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE;
 import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
@@ -45,9 +49,13 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
@@ -115,7 +123,7 @@
         mFeatureFlags = mock(FeatureFlags.class);
         mMetricsCollector =
                 new MetricsCollector(mContext, mPersistAtomsStorage,
-                        mDeviceStateHelper, mVonrHelper, mFeatureFlags);
+                        mDeviceStateHelper, mVonrHelper, mDefaultNetworkMonitor, mFeatureFlags);
         doReturn(mSST).when(mSecondPhone).getServiceStateTracker();
         doReturn(mServiceStateStats).when(mSST).getServiceStateStats();
     }
@@ -469,4 +477,178 @@
         assertThat(actualAtoms).hasSize(4);
         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
     }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteSession_empty() {
+        doReturn(new CarrierRoamingSatelliteSession[0]).when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteSessionStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_SESSION, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteSession_tooFrequent() {
+        doReturn(null).when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteSessionStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_SESSION, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+        verify(mPersistAtomsStorage, times(1))
+                .getCarrierRoamingSatelliteSessionStats(eq(MIN_COOLDOWN_MILLIS));
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteSession_multipleAtoms() {
+        CarrierRoamingSatelliteSession carrierRoamingSatelliteSession =
+                new CarrierRoamingSatelliteSession();
+        doReturn(new CarrierRoamingSatelliteSession[] {carrierRoamingSatelliteSession,
+                carrierRoamingSatelliteSession, carrierRoamingSatelliteSession,
+                carrierRoamingSatelliteSession})
+                .when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteSessionStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_SESSION, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(4);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteControllerStats_empty() {
+        doReturn(new CarrierRoamingSatelliteControllerStats[0]).when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteControllerStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS,
+                actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteControllerStats_multipleAtoms() {
+        CarrierRoamingSatelliteControllerStats carrierRoamingSatelliteControllerStats =
+                new CarrierRoamingSatelliteControllerStats();
+        doReturn(new CarrierRoamingSatelliteControllerStats[] {
+                carrierRoamingSatelliteControllerStats})
+                .when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteControllerStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS,
+                actualAtoms);
+
+        assertThat(actualAtoms).hasSize(1);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteControllerStats_tooFrequent() {
+        doReturn(null).when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteControllerStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS,
+                actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+        verify(mPersistAtomsStorage, times(1))
+                .getCarrierRoamingSatelliteControllerStats(eq(MIN_COOLDOWN_MILLIS));
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onPullAtom_satelliteEntitlement_empty() {
+        doReturn(new SatelliteEntitlement[0]).when(mPersistAtomsStorage)
+                .getSatelliteEntitlementStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_ENTITLEMENT, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_satelliteEntitlement_tooFrequent() {
+        doReturn(null).when(mPersistAtomsStorage).getSatelliteEntitlementStats(
+                anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_ENTITLEMENT, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+        verify(mPersistAtomsStorage, times(1))
+                .getSatelliteEntitlementStats(eq(MIN_COOLDOWN_MILLIS));
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onPullAtom_satelliteEntitlement_multipleAtoms() {
+        SatelliteEntitlement satelliteEntitlement = new SatelliteEntitlement();
+        doReturn(new SatelliteEntitlement[] {satelliteEntitlement, satelliteEntitlement,
+                satelliteEntitlement, satelliteEntitlement})
+                .when(mPersistAtomsStorage)
+                .getSatelliteEntitlementStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_ENTITLEMENT, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(4);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_satelliteConfigUpdater_empty() {
+        doReturn(new SatelliteConfigUpdater[0]).when(mPersistAtomsStorage)
+                .getSatelliteConfigUpdaterStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_CONFIG_UPDATER, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_satelliteConfigUpdater_tooFrequent() {
+        doReturn(null).when(mPersistAtomsStorage).getSatelliteConfigUpdaterStats(
+                anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_CONFIG_UPDATER, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+        verify(mPersistAtomsStorage, times(1))
+                .getSatelliteConfigUpdaterStats(eq(MIN_COOLDOWN_MILLIS));
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onPullAtom_satelliteConfigUpdater_multipleAtoms() {
+        SatelliteConfigUpdater satelliteConfigUpdater = new SatelliteConfigUpdater();
+        doReturn(new SatelliteConfigUpdater[] {satelliteConfigUpdater, satelliteConfigUpdater,
+                satelliteConfigUpdater, satelliteConfigUpdater})
+                .when(mPersistAtomsStorage)
+                .getSatelliteConfigUpdaterStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_CONFIG_UPDATER, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(4);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
 }
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 5e5cb9a..c1e4464 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -73,6 +73,8 @@
 
 import com.android.internal.telephony.TelephonyStatsLog;
 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.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
@@ -90,7 +92,9 @@
 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.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
@@ -286,6 +290,22 @@
     private SatelliteSosMessageRecommender mSatelliteSosMessageRecommender2;
     private SatelliteSosMessageRecommender[] mSatelliteSosMessageRecommenders;
 
+    private CarrierRoamingSatelliteSession mCarrierRoamingSatelliteSession1;
+    private CarrierRoamingSatelliteSession mCarrierRoamingSatelliteSession2;
+    private CarrierRoamingSatelliteSession[] mCarrierRoamingSatelliteSessions;
+
+    private CarrierRoamingSatelliteControllerStats mCarrierRoamingSatelliteControllerStats1;
+    private CarrierRoamingSatelliteControllerStats mCarrierRoamingSatelliteControllerStats2;
+    private CarrierRoamingSatelliteControllerStats[] mCarrierRoamingSatelliteControllerStats;
+
+    private SatelliteEntitlement mSatelliteEntitlement1;
+    private SatelliteEntitlement mSatelliteEntitlement2;
+    private SatelliteEntitlement[] mSatelliteEntitlements;
+
+    private SatelliteConfigUpdater mSatelliteConfigUpdater1;
+    private SatelliteConfigUpdater mSatelliteConfigUpdater2;
+    private SatelliteConfigUpdater[] mSatelliteConfigUpdaters;
+
     private void makeTestData() {
         // MO call with SRVCC (LTE to UMTS)
         mCall1Proto = new VoiceCallSession();
@@ -1256,6 +1276,106 @@
                 new SatelliteSosMessageRecommender[] {
                         mSatelliteSosMessageRecommender1, mSatelliteSosMessageRecommender2
                 };
+
+        mCarrierRoamingSatelliteSession1 = new CarrierRoamingSatelliteSession();
+        mCarrierRoamingSatelliteSession1.carrierId = 1;
+        mCarrierRoamingSatelliteSession1.isNtnRoamingInHomeCountry = false;
+        mCarrierRoamingSatelliteSession1.totalSatelliteModeTimeSec = 60;
+        mCarrierRoamingSatelliteSession1.numberOfSatelliteConnections = 3;
+        mCarrierRoamingSatelliteSession1.avgDurationOfSatelliteConnectionSec = 20;
+        mCarrierRoamingSatelliteSession1.satelliteConnectionGapMinSec = 2;
+        mCarrierRoamingSatelliteSession1.satelliteConnectionGapAvgSec = 5;
+        mCarrierRoamingSatelliteSession1.satelliteConnectionGapMaxSec = 8;
+        mCarrierRoamingSatelliteSession1.rsrpAvg = 3;
+        mCarrierRoamingSatelliteSession1.rsrpMedian = 2;
+        mCarrierRoamingSatelliteSession1.rssnrAvg = 5;
+        mCarrierRoamingSatelliteSession1.rssnrMedian = 3;
+        mCarrierRoamingSatelliteSession1.countOfIncomingSms = 2;
+        mCarrierRoamingSatelliteSession1.countOfOutgoingSms = 4;
+        mCarrierRoamingSatelliteSession1.countOfIncomingMms = 1;
+        mCarrierRoamingSatelliteSession1.countOfOutgoingMms = 1;
+
+        mCarrierRoamingSatelliteSession2 = new CarrierRoamingSatelliteSession();
+        mCarrierRoamingSatelliteSession2.carrierId = 2;
+        mCarrierRoamingSatelliteSession2.isNtnRoamingInHomeCountry = true;
+        mCarrierRoamingSatelliteSession2.totalSatelliteModeTimeSec = 120;
+        mCarrierRoamingSatelliteSession2.numberOfSatelliteConnections = 5;
+        mCarrierRoamingSatelliteSession2.avgDurationOfSatelliteConnectionSec = 20;
+        mCarrierRoamingSatelliteSession2.satelliteConnectionGapMinSec = 2;
+        mCarrierRoamingSatelliteSession2.satelliteConnectionGapAvgSec = 5;
+        mCarrierRoamingSatelliteSession2.satelliteConnectionGapMaxSec = 8;
+        mCarrierRoamingSatelliteSession2.rsrpAvg = 3;
+        mCarrierRoamingSatelliteSession2.rsrpMedian = 2;
+        mCarrierRoamingSatelliteSession2.rssnrAvg = 8;
+        mCarrierRoamingSatelliteSession2.rssnrMedian = 15;
+        mCarrierRoamingSatelliteSession2.countOfIncomingSms = 2;
+        mCarrierRoamingSatelliteSession2.countOfOutgoingSms = 4;
+        mCarrierRoamingSatelliteSession2.countOfIncomingMms = 1;
+        mCarrierRoamingSatelliteSession2.countOfOutgoingMms = 1;
+
+        mCarrierRoamingSatelliteSessions = new CarrierRoamingSatelliteSession[] {
+                mCarrierRoamingSatelliteSession1, mCarrierRoamingSatelliteSession2};
+
+        mCarrierRoamingSatelliteControllerStats1 = new CarrierRoamingSatelliteControllerStats();
+        mCarrierRoamingSatelliteControllerStats1.configDataSource =
+                SatelliteProtoEnums.CONFIG_DATA_SOURCE_ENTITLEMENT;
+        mCarrierRoamingSatelliteControllerStats1.countOfEntitlementStatusQueryRequest = 2;
+        mCarrierRoamingSatelliteControllerStats1.countOfSatelliteConfigUpdateRequest = 1;
+        mCarrierRoamingSatelliteControllerStats1.countOfSatelliteNotificationDisplayed = 1;
+        mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapMinSec = 2;
+        mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapAvgSec = 3;
+        mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapMaxSec = 4;
+
+        mCarrierRoamingSatelliteControllerStats2 = new CarrierRoamingSatelliteControllerStats();
+        mCarrierRoamingSatelliteControllerStats2.configDataSource =
+                SatelliteProtoEnums.CONFIG_DATA_SOURCE_CONFIG_UPDATER;
+        mCarrierRoamingSatelliteControllerStats2.countOfEntitlementStatusQueryRequest = 4;
+        mCarrierRoamingSatelliteControllerStats2.countOfSatelliteConfigUpdateRequest = 1;
+        mCarrierRoamingSatelliteControllerStats2.countOfSatelliteNotificationDisplayed = 1;
+        mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMinSec = 5;
+        mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapAvgSec = 10;
+        mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMaxSec = 15;
+
+        // CarrierRoamingSatelliteController has one data point
+        mCarrierRoamingSatelliteControllerStats = new CarrierRoamingSatelliteControllerStats[] {
+                mCarrierRoamingSatelliteControllerStats1};
+
+        mSatelliteEntitlement1 = new SatelliteEntitlement();
+        mSatelliteEntitlement1.carrierId = 1;
+        mSatelliteEntitlement1.result = 0;
+        mSatelliteEntitlement1.entitlementStatus =
+                SatelliteProtoEnums.SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+        mSatelliteEntitlement1.isRetry = false;
+        mSatelliteEntitlement1.count = 1;
+
+        mSatelliteEntitlement2 = new SatelliteEntitlement();
+        mSatelliteEntitlement2.carrierId = 2;
+        mSatelliteEntitlement2.result = 1;
+        mSatelliteEntitlement2.entitlementStatus =
+                SatelliteProtoEnums.SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+        mSatelliteEntitlement1.isRetry = true;
+        mSatelliteEntitlement2.count = 1;
+
+        mSatelliteEntitlements = new SatelliteEntitlement[] {mSatelliteEntitlement1,
+                mSatelliteEntitlement2};
+
+        mSatelliteConfigUpdater1 = new SatelliteConfigUpdater();
+        mSatelliteConfigUpdater1.configVersion = 1;
+        mSatelliteConfigUpdater1.oemConfigResult = SatelliteProtoEnums.CONFIG_UPDATE_RESULT_SUCCESS;
+        mSatelliteConfigUpdater1.carrierConfigResult =
+                SatelliteProtoEnums.CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_PLMN;
+        mSatelliteConfigUpdater1.count = 1;
+
+        mSatelliteConfigUpdater2 = new SatelliteConfigUpdater();
+        mSatelliteConfigUpdater2.configVersion = 2;
+        mSatelliteConfigUpdater2.oemConfigResult =
+                SatelliteProtoEnums.CONFIG_UPDATE_RESULT_DEVICE_DATA_INVALID_COUNTRY_CODE;
+        mSatelliteConfigUpdater2.carrierConfigResult =
+                SatelliteProtoEnums.CONFIG_UPDATE_RESULT_SUCCESS;
+        mSatelliteConfigUpdater2.count = 1;
+
+        mSatelliteConfigUpdaters = new SatelliteConfigUpdater[] {mSatelliteConfigUpdater1,
+                mSatelliteConfigUpdater2};
     }
 
     private static class TestablePersistAtomsStorage extends PersistAtomsStorage {
@@ -1423,6 +1543,18 @@
         mSatelliteSosMessageRecommender1 = null;
         mSatelliteSosMessageRecommender2 = null;
         mSatelliteSosMessageRecommenders = null;
+        mCarrierRoamingSatelliteSession1 = null;
+        mCarrierRoamingSatelliteSession2 = null;
+        mCarrierRoamingSatelliteSessions = null;
+        mCarrierRoamingSatelliteControllerStats1 = null;
+        mCarrierRoamingSatelliteControllerStats2 = null;
+        mCarrierRoamingSatelliteControllerStats = null;
+        mSatelliteEntitlement1 = null;
+        mSatelliteEntitlement2 = null;
+        mSatelliteEntitlements = null;
+        mSatelliteConfigUpdater1 = null;
+        mSatelliteConfigUpdater2 = null;
+        mSatelliteConfigUpdaters = null;
         super.tearDown();
     }
 
@@ -4578,6 +4710,405 @@
     }
 
     @Test
+    public void addCarrierRoamingSatelliteSessionStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteSessionStats(
+                mCarrierRoamingSatelliteSession1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        CarrierRoamingSatelliteSession[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(0L);
+        assertProtoArrayEquals(new CarrierRoamingSatelliteSession[] {
+                mCarrierRoamingSatelliteSession1}, output);
+    }
+
+    @Test
+    public void addCarrierRoamingSatelliteSessionStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteSessionStats(
+                mCarrierRoamingSatelliteSession1);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteSessionStats(
+                mCarrierRoamingSatelliteSession2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        CarrierRoamingSatelliteSession[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new CarrierRoamingSatelliteSession[] {mCarrierRoamingSatelliteSession2}, output);
+    }
+
+    @Test
+    public void addCarrierRoamingSatelliteSessionStats_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store atoms up to maximum number + 1
+        int maxCount = 1 + 1;
+        for (int i = 0; i < maxCount; i++) {
+            mPersistAtomsStorage.addCarrierRoamingSatelliteSessionStats(
+                    copyOf(mCarrierRoamingSatelliteSession1));
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage.addCarrierRoamingSatelliteSessionStats(
+                mCarrierRoamingSatelliteSession2);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        CarrierRoamingSatelliteSession[] result =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(0L);
+
+        // First atom has count 0, the other has 1
+        assertHasStatsAndCount(result, mCarrierRoamingSatelliteSession1, 0);
+        assertHasStatsAndCount(result, mCarrierRoamingSatelliteSession2, 1);
+    }
+
+    @Test
+    public void getCarrierRoamingSatelliteSessionStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        CarrierRoamingSatelliteSession[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
+    public void getCarrierRoamingSatelliteSessionStats_withSavedAtoms() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        CarrierRoamingSatelliteSession[] carrierRoamingSatelliteSessionStatsList1 =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(50L);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        CarrierRoamingSatelliteSession[] carrierRoamingSatelliteSessionStatsList2 =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(50L);
+
+        // First set of results should be equal to file contents.
+        CarrierRoamingSatelliteSession[] expectedList = new CarrierRoamingSatelliteSession[] {
+                mCarrierRoamingSatelliteSession1, mCarrierRoamingSatelliteSession2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList, carrierRoamingSatelliteSessionStatsList1);
+        // Second set of results should be empty.
+        assertProtoArrayEquals(new CarrierRoamingSatelliteSession[0],
+                carrierRoamingSatelliteSessionStatsList2);
+        // Corresponding pull timestamp should be updated and saved.
+        assertEquals(START_TIME_MILLIS + 200L, mPersistAtomsStorage
+                .getAtomsProto().carrierRoamingSatelliteSessionPullTimestampMillis);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        assertEquals(START_TIME_MILLIS + 100L,
+                getAtomsWritten(inOrder).carrierRoamingSatelliteSessionPullTimestampMillis);
+        assertEquals(START_TIME_MILLIS + 200L,
+                getAtomsWritten(inOrder).carrierRoamingSatelliteSessionPullTimestampMillis);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void addCarrierRoamingSatelliteControllerStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteControllerStats(
+                mCarrierRoamingSatelliteControllerStats1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        CarrierRoamingSatelliteControllerStats[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteControllerStats(0L);
+        assertProtoArrayEquals(new CarrierRoamingSatelliteControllerStats[] {
+                mCarrierRoamingSatelliteControllerStats1}, output);
+    }
+
+    @Test
+    public void addCarrierRoamingSatelliteControllerStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteControllerStats(
+                mCarrierRoamingSatelliteControllerStats1);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteControllerStats(
+                mCarrierRoamingSatelliteControllerStats2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        CarrierRoamingSatelliteControllerStats expected =
+                new CarrierRoamingSatelliteControllerStats();
+        expected.configDataSource = mCarrierRoamingSatelliteControllerStats2.configDataSource;
+        expected.countOfEntitlementStatusQueryRequest =
+                mCarrierRoamingSatelliteControllerStats1.countOfEntitlementStatusQueryRequest
+                        + mCarrierRoamingSatelliteControllerStats2
+                        .countOfEntitlementStatusQueryRequest;
+        expected.countOfSatelliteConfigUpdateRequest =
+                mCarrierRoamingSatelliteControllerStats1.countOfSatelliteConfigUpdateRequest
+                        + mCarrierRoamingSatelliteControllerStats2
+                        .countOfSatelliteConfigUpdateRequest;
+        expected.countOfSatelliteNotificationDisplayed =
+                mCarrierRoamingSatelliteControllerStats1.countOfSatelliteNotificationDisplayed
+                + mCarrierRoamingSatelliteControllerStats2
+                        .countOfSatelliteNotificationDisplayed;
+        expected.satelliteSessionGapMinSec =
+                mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMinSec;
+        expected.satelliteSessionGapAvgSec =
+                mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapAvgSec;
+        expected.satelliteSessionGapMaxSec =
+                mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMaxSec;
+
+        verifyCurrentStateSavedToFileOnce();
+        CarrierRoamingSatelliteControllerStats[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteControllerStats(0L);
+        assertHasStats(output, expected);
+    }
+
+    @Test
+    public void getCarrierRoamingSatelliteControllerStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        CarrierRoamingSatelliteControllerStats[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteControllerStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+
+    @Test
+    public void addSatelliteEntitlementStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteEntitlementStats(mSatelliteEntitlement1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteEntitlement[] output =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(0L);
+        assertProtoArrayEquals(new SatelliteEntitlement[] {mSatelliteEntitlement1}, output);
+    }
+
+    @Test
+    public void addSatelliteEntitlementStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteEntitlementStats(mSatelliteEntitlement1);
+        mPersistAtomsStorage.addSatelliteEntitlementStats(mSatelliteEntitlement2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteEntitlement[] output =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new SatelliteEntitlement[] {
+                        mSatelliteEntitlement1, mSatelliteEntitlement2}, output);
+    }
+
+    @Test
+    public void addSatelliteEntitlementStats_updateExistingEntries() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteEntitlementStats(copyOf(mSatelliteEntitlement1));
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Count should be increased by 1.
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteEntitlement newSatelliteEntitlement1 = copyOf(mSatelliteEntitlement1);
+        newSatelliteEntitlement1.count = 2;
+        SatelliteEntitlement[] expectedList = new SatelliteEntitlement[] {newSatelliteEntitlement1,
+                mSatelliteEntitlement2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList,
+                mPersistAtomsStorage.getSatelliteEntitlementStats(0L));
+    }
+
+    @Test
+    public void addSatelliteEntitlementStats_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.addSatelliteEntitlementStats(mSatelliteEntitlement1);
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage.addSatelliteEntitlementStats(mSatelliteEntitlement2);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        SatelliteEntitlement[] result =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(0L);
+
+        // First atom has count 14, the other has 1
+        assertHasStatsAndCount(result, mSatelliteEntitlement1, 16);
+        assertHasStatsAndCount(result, mSatelliteEntitlement2, 1);
+    }
+
+    @Test
+    public void getSatelliteEntitlementStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteEntitlement[] output =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
+    public void getSatelliteEntitlementStats_withSavedAtoms() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        SatelliteEntitlement[] satelliteEntitlementStatsList1 =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(50L);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        SatelliteEntitlement[] satelliteEntitlementStatsList2 =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(50L);
+
+        // First set of results should be equal to file contents.
+        SatelliteEntitlement[] expectedList = new SatelliteEntitlement[] {
+                mSatelliteEntitlement1, mSatelliteEntitlement2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList, satelliteEntitlementStatsList1);
+        // Second set of results should be empty.
+        assertProtoArrayEquals(new SatelliteEntitlement[0], satelliteEntitlementStatsList2);
+        // Corresponding pull timestamp should be updated and saved.
+        assertEquals(START_TIME_MILLIS + 200L, mPersistAtomsStorage
+                .getAtomsProto().satelliteEntitlementPullTimestampMillis);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        assertEquals(START_TIME_MILLIS + 100L,
+                getAtomsWritten(inOrder).satelliteEntitlementPullTimestampMillis);
+        assertEquals(START_TIME_MILLIS + 200L,
+                getAtomsWritten(inOrder).satelliteEntitlementPullTimestampMillis);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void addSatelliteConfigUpdaterStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteConfigUpdaterStats(mSatelliteConfigUpdater1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteConfigUpdater[] output =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(0L);
+        assertProtoArrayEquals(new SatelliteConfigUpdater[] {mSatelliteConfigUpdater1}, output);
+    }
+
+    @Test
+    public void addSatelliteConfigUpdaterStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteConfigUpdaterStats(mSatelliteConfigUpdater1);
+        mPersistAtomsStorage.addSatelliteConfigUpdaterStats(mSatelliteConfigUpdater2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteConfigUpdater[] output =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(new SatelliteConfigUpdater[] {
+                mSatelliteConfigUpdater1, mSatelliteConfigUpdater2}, output);
+    }
+
+    @Test
+    public void addSatelliteConfigUpdaterStats_updateExistingEntries() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteConfigUpdaterStats(copyOf(mSatelliteConfigUpdater1));
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Count should be increased by 1.
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteConfigUpdater newSatelliteConfigUpdater1 = copyOf(mSatelliteConfigUpdater1);
+        newSatelliteConfigUpdater1.count = 2;
+        SatelliteConfigUpdater[] expectedList = new SatelliteConfigUpdater[] {
+                newSatelliteConfigUpdater1, mSatelliteConfigUpdater2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList,
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(0L));
+    }
+
+    @Test
+    public void addSatelliteConfigUpdaterStats_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.addSatelliteConfigUpdaterStats(mSatelliteConfigUpdater1);
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage.addSatelliteConfigUpdaterStats(mSatelliteConfigUpdater2);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        SatelliteConfigUpdater[] result =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(0L);
+
+        // First atom has count 14, the other has 1
+        assertHasStatsAndCount(result, mSatelliteConfigUpdater1, 16);
+        assertHasStatsAndCount(result, mSatelliteConfigUpdater2, 1);
+    }
+
+    @Test
+    public void getSatelliteConfigUpdaterStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteConfigUpdater[] output =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
+    public void getSatelliteConfigUpdaterStats_withSavedAtoms() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        SatelliteConfigUpdater[] satelliteConfigUpdaterStatsList1 =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(50L);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        SatelliteConfigUpdater[] satelliteConfigUpdaterStatsList2 =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(50L);
+
+        // First set of results should be equal to file contents.
+        SatelliteConfigUpdater[] expectedList = new SatelliteConfigUpdater[] {
+                mSatelliteConfigUpdater1, mSatelliteConfigUpdater2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList, satelliteConfigUpdaterStatsList1);
+        // Second set of results should be empty.
+        assertProtoArrayEquals(new SatelliteConfigUpdater[0], satelliteConfigUpdaterStatsList2);
+        // Corresponding pull timestamp should be updated and saved.
+        assertEquals(START_TIME_MILLIS + 200L, mPersistAtomsStorage
+                .getAtomsProto().satelliteConfigUpdaterPullTimestampMillis);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        assertEquals(START_TIME_MILLIS + 100L,
+                getAtomsWritten(inOrder).satelliteConfigUpdaterPullTimestampMillis);
+        assertEquals(START_TIME_MILLIS + 200L,
+                getAtomsWritten(inOrder).satelliteConfigUpdaterPullTimestampMillis);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
     @SmallTest
     public void clearAtoms() throws Exception {
         createTestFile(START_TIME_MILLIS);
@@ -4662,6 +5193,14 @@
         atoms.satelliteProvisionPullTimestampMillis = lastPullTimeMillis;
         atoms.satelliteSosMessageRecommender = mSatelliteSosMessageRecommenders;
         atoms.satelliteSosMessageRecommenderPullTimestampMillis = lastPullTimeMillis;
+        atoms.carrierRoamingSatelliteSession = mCarrierRoamingSatelliteSessions;
+        atoms.carrierRoamingSatelliteSessionPullTimestampMillis = lastPullTimeMillis;
+        atoms.carrierRoamingSatelliteControllerStats = mCarrierRoamingSatelliteControllerStats;
+        atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteEntitlement = mSatelliteEntitlements;
+        atoms.satelliteEntitlementPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteConfigUpdater = mSatelliteConfigUpdaters;
+        atoms.satelliteConfigUpdaterPullTimestampMillis = lastPullTimeMillis;
         FileOutputStream stream = new FileOutputStream(mTestFile);
         stream.write(PersistAtoms.toByteArray(atoms));
         stream.close();
@@ -4825,6 +5364,24 @@
         return SatelliteSosMessageRecommender.parseFrom(MessageNano.toByteArray(source));
     }
 
+    private static CarrierRoamingSatelliteSession copyOf(CarrierRoamingSatelliteSession source)
+            throws Exception {
+        return CarrierRoamingSatelliteSession.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static CarrierRoamingSatelliteControllerStats copyOf(
+            CarrierRoamingSatelliteControllerStats source) throws Exception {
+        return CarrierRoamingSatelliteControllerStats.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static SatelliteEntitlement copyOf(SatelliteEntitlement source) throws Exception {
+        return SatelliteEntitlement.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static SatelliteConfigUpdater copyOf(SatelliteConfigUpdater source) throws Exception {
+        return SatelliteConfigUpdater.parseFrom(MessageNano.toByteArray(source));
+    }
+
     private void assertAllPullTimestampEquals(long timestamp) {
         assertEquals(
                 timestamp,
@@ -5323,4 +5880,82 @@
         }
         assertEquals(expectedCount, actualCount);
     }
+
+    private static void assertHasStatsAndCount(CarrierRoamingSatelliteSession[] tested,
+            @Nullable CarrierRoamingSatelliteSession expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (CarrierRoamingSatelliteSession stats : tested) {
+            if (stats.carrierId == expectedStats.carrierId
+                    && stats.isNtnRoamingInHomeCountry == expectedStats.isNtnRoamingInHomeCountry
+                    && stats.totalSatelliteModeTimeSec == expectedStats.totalSatelliteModeTimeSec
+                    && stats.numberOfSatelliteConnections
+                    == expectedStats.numberOfSatelliteConnections
+                    && stats.avgDurationOfSatelliteConnectionSec
+                    == expectedStats.avgDurationOfSatelliteConnectionSec
+                    && stats.satelliteConnectionGapMinSec
+                    == expectedStats.satelliteConnectionGapMinSec
+                    && stats.satelliteConnectionGapAvgSec
+                    == expectedStats.satelliteConnectionGapAvgSec
+                    && stats.satelliteConnectionGapMaxSec
+                    == expectedStats.satelliteConnectionGapMaxSec
+                    && stats.rsrpAvg == expectedStats.rsrpAvg
+                    && stats.rsrpMedian == expectedStats.rsrpMedian
+                    && stats.rssnrAvg == expectedStats.rssnrAvg
+                    && stats.rssnrMedian == expectedStats.rssnrMedian
+                    && stats.countOfIncomingSms == expectedStats.countOfIncomingSms
+                    && stats.countOfOutgoingSms == expectedStats.countOfOutgoingSms
+                    && stats.countOfIncomingMms == expectedStats.countOfIncomingMms
+                    && stats.countOfOutgoingMms == expectedStats.countOfOutgoingMms) {
+                actualCount++;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStats(CarrierRoamingSatelliteControllerStats[] tested,
+            @Nullable CarrierRoamingSatelliteControllerStats expectedStats) {
+        assertNotNull(tested);
+        assertEquals(tested[0].configDataSource, expectedStats.configDataSource);
+        assertEquals(tested[0].countOfEntitlementStatusQueryRequest,
+                expectedStats.countOfEntitlementStatusQueryRequest);
+        assertEquals(tested[0].countOfSatelliteConfigUpdateRequest,
+                expectedStats.countOfSatelliteConfigUpdateRequest);
+        assertEquals(tested[0].countOfSatelliteNotificationDisplayed,
+                expectedStats.countOfSatelliteNotificationDisplayed);
+        assertEquals(tested[0].satelliteSessionGapMinSec, expectedStats.satelliteSessionGapMinSec);
+        assertEquals(tested[0].satelliteSessionGapAvgSec, expectedStats.satelliteSessionGapAvgSec);
+        assertEquals(tested[0].satelliteSessionGapMaxSec, expectedStats.satelliteSessionGapMaxSec);
+    }
+
+    private static void assertHasStatsAndCount(
+            SatelliteEntitlement[] tested,
+            @Nullable SatelliteEntitlement expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (SatelliteEntitlement stats : tested) {
+            if (stats.carrierId == expectedStats.carrierId
+                    && stats.result == expectedStats.result
+                    && stats.entitlementStatus == expectedStats.entitlementStatus
+                    && stats.isRetry == expectedStats.isRetry) {
+                actualCount = stats.count;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStatsAndCount(
+            SatelliteConfigUpdater[] tested,
+            @Nullable SatelliteConfigUpdater expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (SatelliteConfigUpdater stats : tested) {
+            if (stats.configVersion == expectedStats.configVersion
+                    && stats.oemConfigResult == expectedStats.oemConfigResult
+                    && stats.carrierConfigResult == expectedStats.carrierConfigResult) {
+                actualCount = stats.count;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
 }
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 bc665f8..daa47c1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
@@ -26,7 +26,11 @@
 import android.telephony.TelephonyProtoEnums;
 
 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.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
@@ -300,4 +304,136 @@
                 stats.isSatelliteAllowedInCurrentLocation);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
+
+    @Test
+    public void onCarrierRoamingSatelliteSessionMetrics_withAtoms() throws Exception {
+        SatelliteStats.CarrierRoamingSatelliteSessionParams param =
+                new SatelliteStats.CarrierRoamingSatelliteSessionParams.Builder()
+                        .setCarrierId(100)
+                        .setIsNtnRoamingInHomeCountry(true)
+                        .setTotalSatelliteModeTimeSec(10 * 60)
+                        .setNumberOfSatelliteConnections(5)
+                        .setAvgDurationOfSatelliteConnectionSec(2 * 60)
+                        .setSatelliteConnectionGapMinSec(30)
+                        .setSatelliteConnectionGapAvgSec(300)
+                        .setSatelliteConnectionGapMaxSec(500)
+                        .setRsrpAvg(2)
+                        .setRsrpMedian(3)
+                        .setRssnrAvg(12)
+                        .setRssnrMedian(18)
+                        .setCountOfIncomingSms(6)
+                        .setCountOfOutgoingSms(11)
+                        .setCountOfIncomingMms(9)
+                        .setCountOfOutgoingMms(14)
+                        .build();
+
+        mSatelliteStats.onCarrierRoamingSatelliteSessionMetrics(param);
+
+        ArgumentCaptor<CarrierRoamingSatelliteSession> captor =
+                ArgumentCaptor.forClass(CarrierRoamingSatelliteSession.class);
+        verify(mPersistAtomsStorage).addCarrierRoamingSatelliteSessionStats(captor.capture());
+        CarrierRoamingSatelliteSession stats = captor.getValue();
+        assertEquals(param.getCarrierId(), stats.carrierId);
+        assertEquals(param.getIsNtnRoamingInHomeCountry(), stats.isNtnRoamingInHomeCountry);
+        assertEquals(param.getTotalSatelliteModeTimeSec(), stats.totalSatelliteModeTimeSec);
+        assertEquals(param.getNumberOfSatelliteConnections(), stats.numberOfSatelliteConnections);
+        assertEquals(param.getAvgDurationOfSatelliteConnectionSec(),
+                stats.avgDurationOfSatelliteConnectionSec);
+        assertEquals(param.getSatelliteConnectionGapMinSec(), stats.satelliteConnectionGapMinSec);
+        assertEquals(param.getSatelliteConnectionGapAvgSec(), stats.satelliteConnectionGapAvgSec);
+        assertEquals(param.getSatelliteConnectionGapMaxSec(), stats.satelliteConnectionGapMaxSec);
+        assertEquals(param.getRsrpAvg(), stats.rsrpAvg);
+        assertEquals(param.getRsrpMedian(), stats.rsrpMedian);
+        assertEquals(param.getRssnrAvg(), stats.rssnrAvg);
+        assertEquals(param.getRssnrMedian(), stats.rssnrMedian);
+        assertEquals(param.getCountOfIncomingSms(), stats.countOfIncomingSms);
+        assertEquals(param.getCountOfOutgoingSms(), stats.countOfOutgoingSms);
+        assertEquals(param.getCountOfIncomingMms(), stats.countOfIncomingMms);
+        assertEquals(param.getCountOfOutgoingMms(), stats.countOfOutgoingMms);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onCarrierRoamingSatelliteControllerStatsMetrics_withAtoms() throws Exception {
+        SatelliteStats.CarrierRoamingSatelliteControllerStatsParams param =
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setConfigDataSource(4)
+                        .setCountOfEntitlementStatusQueryRequest(6)
+                        .setCountOfSatelliteConfigUpdateRequest(2)
+                        .setCountOfSatelliteNotificationDisplayed(1)
+                        .setSatelliteSessionGapMinSec(15)
+                        .setSatelliteSessionGapAvgSec(30)
+                        .setSatelliteSessionGapMaxSec(45)
+                        .build();
+
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(param);
+
+        ArgumentCaptor<CarrierRoamingSatelliteControllerStats> captor =
+                ArgumentCaptor.forClass(CarrierRoamingSatelliteControllerStats.class);
+        verify(mPersistAtomsStorage).addCarrierRoamingSatelliteControllerStats(captor.capture());
+        CarrierRoamingSatelliteControllerStats stats = captor.getValue();
+        assertEquals(param.getConfigDataSource(), stats.configDataSource);
+        assertEquals(param.getCountOfEntitlementStatusQueryRequest(),
+                stats.countOfEntitlementStatusQueryRequest);
+        assertEquals(param.getCountOfSatelliteConfigUpdateRequest(),
+                stats.countOfSatelliteConfigUpdateRequest);
+        assertEquals(param.getCountOfSatelliteNotificationDisplayed(),
+                stats.countOfSatelliteNotificationDisplayed);
+        assertEquals(param.getSatelliteSessionGapMinSec(), stats.satelliteSessionGapMinSec);
+        assertEquals(param.getSatelliteSessionGapAvgSec(), stats.satelliteSessionGapAvgSec);
+        assertEquals(param.getSatelliteSessionGapMaxSec(), stats.satelliteSessionGapMaxSec);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onSatelliteEntitlementMetrics_withAtoms() throws Exception {
+        SatelliteStats.SatelliteEntitlementParams param =
+                new SatelliteStats.SatelliteEntitlementParams.Builder()
+                        .setCarrierId(10)
+                        .setResult(500)
+                        .setEntitlementStatus(2)
+                        .setIsRetry(true)
+                        .setCount(5)
+                        .build();
+
+        mSatelliteStats.onSatelliteEntitlementMetrics(param);
+
+        ArgumentCaptor<SatelliteEntitlement> captor =
+                ArgumentCaptor.forClass(SatelliteEntitlement.class);
+        verify(mPersistAtomsStorage).addSatelliteEntitlementStats(captor.capture());
+        SatelliteEntitlement stats = captor.getValue();
+        assertEquals(param.getCarrierId(), stats.carrierId);
+        assertEquals(param.getResult(), stats.result);
+        assertEquals(param.getEntitlementStatus(), stats.entitlementStatus);
+        assertEquals(param.getIsRetry(), stats.isRetry);
+        assertEquals(param.getCount(), stats.count);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onSatelliteConfigUpdaterMetrics_withAtoms() throws Exception {
+        SatelliteStats.SatelliteConfigUpdaterParams param =
+                new SatelliteStats.SatelliteConfigUpdaterParams.Builder()
+                        .setConfigVersion(8)
+                        .setOemConfigResult(9)
+                        .setCarrierConfigResult(7)
+                        .setCount(3)
+                        .build();
+
+        mSatelliteStats.onSatelliteConfigUpdaterMetrics(param);
+
+        ArgumentCaptor<SatelliteConfigUpdater> captor =
+                ArgumentCaptor.forClass(SatelliteConfigUpdater.class);
+        verify(mPersistAtomsStorage).addSatelliteConfigUpdaterStats(captor.capture());
+        SatelliteConfigUpdater stats = captor.getValue();
+        assertEquals(param.getConfigVersion(), stats.configVersion);
+        assertEquals(param.getOemConfigResult(), stats.oemConfigResult);
+        assertEquals(param.getCarrierConfigResult(), stats.carrierConfigResult);
+        assertEquals(param.getCount(), stats.count);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
 }