[automerger skipped] Merge Android 24Q2 Release (ab/11526283) to aosp-main-future am: dd1eeb280f -s ours

am skip reason: Merged-In Iba5169f65fbba4b90e6767caebf1d0c96f299a63 with SHA-1 e189fb70f7 is already in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Mms/+/27273847

Change-Id: Ifca56a8216ade16d8ab63329ac2fc04741890530
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/OWNERS b/OWNERS
index 01985bc..1befe3e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -6,3 +6,4 @@
 jianxiangp@google.com
 stephshi@google.com
 bellavicevh@google.com
+sasindran@google.com
diff --git a/proto/src/persist_mms_atoms.proto b/proto/src/persist_mms_atoms.proto
index bde1cb9..8be233b 100644
--- a/proto/src/persist_mms_atoms.proto
+++ b/proto/src/persist_mms_atoms.proto
@@ -51,6 +51,7 @@
   optional int32 retry_id = 10;
   optional bool handled_by_carrier_app = 11;
   optional bool is_managed_profile = 12;
+  optional bool is_ntn = 13;
 }
 
 message OutgoingMms {
@@ -67,4 +68,5 @@
   optional int32 retry_id = 11;
   optional bool handled_by_carrier_app = 12;
   optional bool is_managed_profile = 13;
+  optional bool is_ntn = 14;
 }
diff --git a/src/com/android/mms/service/ApnSettings.java b/src/com/android/mms/service/ApnSettings.java
index 42d6427..8ac3482 100644
--- a/src/com/android/mms/service/ApnSettings.java
+++ b/src/com/android/mms/service/ApnSettings.java
@@ -16,6 +16,8 @@
 
 package com.android.mms.service;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
@@ -158,6 +160,31 @@
         return null;
     }
 
+    /**
+     * Convert the APN received from network to an APN used by MMS to make http request. Essentially
+     * the same as {@link #getApnSettingsFromCursor}.
+     * @param apn network APN for setup network.
+     * @return APN to make http request.
+     */
+    @Nullable
+    public static ApnSettings getApnSettingsFromNetworkApn(@NonNull ApnSetting apn) {
+        // Default proxy port to 80
+        int proxyPort = 80;
+        // URL
+        if (apn.getMmsc() == null) return null;
+        String mmscUrl = apn.getMmsc().toString().trim();
+        if (TextUtils.isEmpty(mmscUrl)) return null;
+        // proxy address
+        String proxy = trimWithNullCheck(apn.getMmsProxyAddressAsString());
+        if (!TextUtils.isEmpty(proxy)) {
+            proxy = Inet4AddressUtils.trimAddressZeros(proxy);
+            // proxy port
+            if (apn.getMmsProxyPort() != -1 /*UNSPECIFIED_INT*/) proxyPort = apn.getMmsProxyPort();
+        }
+
+        return new ApnSettings(mmscUrl, proxy, proxyPort, apn.toString());
+    }
+
     private static String getDebugText(Cursor cursor) {
         final StringBuilder sb = new StringBuilder();
         sb.append("APN [");
@@ -217,7 +244,8 @@
         return false;
     }
 
+    @Override
     public String toString() {
-        return mDebugText;
+        return mServiceCenter + " " + mProxyAddress + " " + mProxyPort + " " + mDebugText;
     }
 }
diff --git a/src/com/android/mms/service/LogUtil.java b/src/com/android/mms/service/LogUtil.java
index 349af32..49ab3f1 100644
--- a/src/com/android/mms/service/LogUtil.java
+++ b/src/com/android/mms/service/LogUtil.java
@@ -16,71 +16,72 @@
 
 package com.android.mms.service;
 
-import android.util.Log;
+
+import android.telephony.Rlog;
 
 /**
  * Logging utility
  */
 public class LogUtil {
-    private static final String TAG = "MmsService";
+    public static final String TAG = "MmsService";
 
     public static void i(final String requestId, final String message) {
-        Log.i(TAG, "[" + requestId + "] " + message);
+        Rlog.i(TAG, "[" + requestId + "] " + message);
     }
 
     public static void i(final String message) {
-        Log.i(TAG, message);
+        Rlog.i(TAG, message);
     }
 
     public static void d(final String requestId, final String message) {
-        Log.d(TAG, "[" + requestId + "] " + message);
+        Rlog.d(TAG, "[" + requestId + "] " + message);
     }
 
     public static void d(final String message) {
-        Log.d(TAG, message);
+        Rlog.d(TAG, message);
     }
 
     public static void v(final String requestId, final String message) {
-        Log.v(TAG, "[" + requestId + "] " + message);
+        Rlog.v(TAG, "[" + requestId + "] " + message);
     }
 
     public static void v(final String message) {
-        Log.v(TAG, message);
+        Rlog.v(TAG, message);
     }
 
     public static void e(final String requestId, final String message, final Throwable t) {
-        Log.e(TAG, "[" + requestId + "] " + message, t);
+        Rlog.e(TAG, "[" + requestId + "] " + message, t);
     }
 
     public static void e(final String message, final Throwable t) {
-        Log.e(TAG, message, t);
+        Rlog.e(TAG, message, t);
     }
 
     public static void e(final String requestId, final String message) {
-        Log.e(TAG, "[" + requestId + "] " + message);
+        Rlog.e(TAG, "[" + requestId + "] " + message);
     }
 
     public static void e(final String message) {
-        Log.e(TAG, message);
+        Rlog.e(TAG, message);
     }
 
     public static void w(final String requestId, final String message, final Throwable t) {
-        Log.w(TAG, "[" + requestId + "] " + message, t);
+        Rlog.w(TAG, "[" + requestId + "] " + message, t);
     }
 
     public static void w(final String message, final Throwable t) {
-        Log.w(TAG, message, t);
+        Rlog.w(TAG, message, t);
     }
 
     public static void w(final String requestId, final String message) {
-        Log.w(TAG, "[" + requestId + "] " + message);
+        Rlog.w(TAG, "[" + requestId + "] " + message);
     }
 
     public static void w(final String message) {
-        Log.w(TAG, message);
+        Rlog.w(TAG, message);
     }
 
     public static boolean isLoggable(final int logLevel) {
-        return Log.isLoggable(TAG, logLevel);
+        return Rlog.isLoggable(TAG, logLevel);
     }
 }
diff --git a/src/com/android/mms/service/MmsHttpClient.java b/src/com/android/mms/service/MmsHttpClient.java
index 7978a71..367253e 100644
--- a/src/com/android/mms/service/MmsHttpClient.java
+++ b/src/com/android/mms/service/MmsHttpClient.java
@@ -31,7 +31,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.mms.service.exception.MmsHttpException;
-import com.android.mms.service.exception.VoluntaryDisconnectMmsHttpException;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -51,9 +50,6 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -90,11 +86,6 @@
     private final Network mNetwork;
     private final ConnectivityManager mConnectivityManager;
 
-    /** Store all currently open connections, for potential voluntarily early disconnect. */
-    private final Set<HttpURLConnection> mAllUrlConnections = ConcurrentHashMap.newKeySet();
-    /** Flag indicating whether a disconnection is voluntary. */
-    private final AtomicBoolean mVoluntarilyDisconnectingConnections = new AtomicBoolean(false);
-
     /**
      * Constructor
      *
@@ -144,7 +135,6 @@
             maybeWaitForIpv4(requestId, url);
             // Now get the connection
             connection = (HttpURLConnection) mNetwork.openConnection(url, proxy);
-            if (connection != null) mAllUrlConnections.add(connection);
             connection.setDoInput(true);
             connection.setConnectTimeout(
                     mmsConfig.getInt(SmsManager.MMS_CONFIG_HTTP_SOCKET_TIMEOUT));
@@ -247,46 +237,15 @@
             LogUtil.e(requestId, "HTTP: invalid URL protocol " + redactedUrl, e);
             throw new MmsHttpException(0/*statusCode*/, "Invalid URL protocol " + redactedUrl, e);
         } catch (IOException e) {
-            if (mVoluntarilyDisconnectingConnections.get()) {
-                // If in the process of voluntarily disconnecting all connections, the exception
-                // is casted as VoluntaryDisconnectMmsHttpException to indicate this attempt is
-                // cancelled rather than failure.
-                LogUtil.d(requestId,
-                        "HTTP voluntarily disconnected due to WLAN network available");
-                throw new VoluntaryDisconnectMmsHttpException(0/*statusCode*/,
-                        "Expected disconnection due to WLAN network available");
-            } else {
-                LogUtil.e(requestId, "HTTP: IO failure ", e);
-                throw new MmsHttpException(0/*statusCode*/, e);
-            }
+            LogUtil.e(requestId, "HTTP: IO failure", e);
+            throw new MmsHttpException(0/*statusCode*/, e);
         } finally {
             if (connection != null) {
                 connection.disconnect();
-                mAllUrlConnections.remove(connection);
-                // If all connections are done disconnected, flag voluntary disconnection done if
-                // applicable.
-                if (mAllUrlConnections.isEmpty() && mVoluntarilyDisconnectingConnections
-                        .compareAndSet(/*expectedValue*/true, /*newValue*/false)) {
-                    LogUtil.d("All voluntarily disconnected connections are removed.");
-                }
             }
         }
     }
 
-    /**
-     * Voluntarily disconnect all Http URL connections. This will trigger
-     * {@link VoluntaryDisconnectMmsHttpException} to be thrown, to indicate voluntary disconnection
-     */
-    public void disconnectAllUrlConnections() {
-        LogUtil.d("Disconnecting all Url connections, size = " + mAllUrlConnections.size());
-        if (mAllUrlConnections.isEmpty()) return;
-        mVoluntarilyDisconnectingConnections.set(true);
-        for (HttpURLConnection connection : mAllUrlConnections) {
-            // TODO: An improvement is to check the writing/reading progress before disconnect.
-            connection.disconnect();
-        }
-    }
-
     private void maybeWaitForIpv4(final String requestId, final URL url) {
         // If it's a literal IPv4 address and we're on an IPv6-only network,
         // wait until IPv4 is available.
diff --git a/src/com/android/mms/service/MmsNetworkManager.java b/src/com/android/mms/service/MmsNetworkManager.java
index 73c749a..bcafbc7 100644
--- a/src/com/android/mms/service/MmsNetworkManager.java
+++ b/src/com/android/mms/service/MmsNetworkManager.java
@@ -33,7 +33,6 @@
 import android.os.PersistableBundle;
 import android.provider.DeviceConfig;
 import android.telephony.CarrierConfigManager;
-import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
@@ -46,10 +45,8 @@
  * Manages the MMS network connectivity
  */
 public class MmsNetworkManager {
-    /** Device Config Keys */
     private static final String MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS =
             "mms_service_network_request_timeout_millis";
-    private static final String MMS_ENHANCEMENT_ENABLED = "mms_enhancement_enabled";
 
     // Default timeout used to call ConnectivityManager.requestNetwork if the
     // MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS flag is not set.
@@ -62,8 +59,6 @@
 
     /* Event created when receiving ACTION_CARRIER_CONFIG_CHANGED */
     private static final int EVENT_CARRIER_CONFIG_CHANGED = 1;
-    /** Event when a WLAN network newly available despite of the existing available one. */
-    private static final int EVENT_IWLAN_NETWORK_NEWLY_AVAILABLE = 2;
 
     private final Context mContext;
 
@@ -71,8 +66,6 @@
     // We need this when we unbind from it. This is also used to indicate if the
     // MMS network is available.
     private Network mNetwork;
-    /** Whether an Iwlan MMS network is available to use. */
-    private boolean mIsLastAvailableNetworkIwlan;
     // The current count of MMS requests that require the MMS network
     // If mMmsRequestCount is 0, we should release the MMS network.
     private int mMmsRequestCount;
@@ -104,6 +97,10 @@
     private final Dependencies mDeps;
 
     private int mNetworkReleaseTimeoutMillis;
+
+    // satellite transport status of associated mms active network
+    private boolean  mIsSatelliteTransport;
+
     private EventHandler mEventHandler;
 
     private final class EventHandler extends Handler {
@@ -123,9 +120,6 @@
                     // Reload mNetworkReleaseTimeoutMillis from CarrierConfigManager.
                     handleCarrierConfigChanged();
                     break;
-                case EVENT_IWLAN_NETWORK_NEWLY_AVAILABLE:
-                    onIwlanNetworkNewlyAvailable();
-                    break;
                 default:
                     LogUtil.e("MmsNetworkManager: ignoring message of unexpected type " + msg.what);
             }
@@ -197,17 +191,6 @@
         }
     };
 
-    /**
-     * Called when a WLAN network newly available. This new WLAN network should replace the
-     * existing network and retry sending traffic on this network.
-     */
-    private void onIwlanNetworkNewlyAvailable() {
-        if (mMmsHttpClient == null || mNetwork == null) return;
-        LogUtil.d("onIwlanNetworkNewlyAvailable net " + mNetwork.getNetId());
-        mMmsHttpClient.disconnectAllUrlConnections();
-        populateHttpClientWithCurrentNetwork();
-    }
-
     private void handleCarrierConfigChanged() {
         final CarrierConfigManager configManager =
                 (CarrierConfigManager)
@@ -251,13 +234,8 @@
             // onAvailable will always immediately be followed by a onCapabilitiesChanged. Check
             // network status here is enough.
             super.onCapabilitiesChanged(network, nc);
-            final NetworkInfo networkInfo = getConnectivityManager().getNetworkInfo(network);
-            // wlan network is preferred over wwan network, because its existence meaning it's
-            // recommended by QualifiedNetworksService.
-            final boolean isWlan = networkInfo != null
-                    && networkInfo.getSubtype() == TelephonyManager.NETWORK_TYPE_IWLAN;
             LogUtil.w("NetworkCallbackListener.onCapabilitiesChanged: network="
-                    + network + ", isWlan=" + isWlan + ", nc=" + nc);
+                    + network + ", nc=" + nc);
             synchronized (MmsNetworkManager.this) {
                 final boolean isAvailable =
                         nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
@@ -270,18 +248,12 @@
                     return;
                 }
 
-                // Use new available network
-                if (isAvailable) {
-                    if (mNetwork == null) {
-                        mNetwork = network;
-                        MmsNetworkManager.this.notifyAll();
-                    } else if (mDeps.isMmsEnhancementEnabled()
-                            // Iwlan network newly available, try send MMS over the new network.
-                            && !mIsLastAvailableNetworkIwlan && isWlan) {
-                        mNetwork = network;
-                        mEventHandler.sendEmptyMessage(EVENT_IWLAN_NETWORK_NEWLY_AVAILABLE);
-                    }
-                    mIsLastAvailableNetworkIwlan = isWlan;
+                // New available network
+                if (mNetwork == null && isAvailable) {
+                    mIsSatelliteTransport = Flags.satelliteInternet()
+                            && nc.hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE);
+                    mNetwork = network;
+                    MmsNetworkManager.this.notifyAll();
                 }
             }
         }
@@ -305,11 +277,6 @@
                     DEFAULT_MMS_SERVICE_NETWORK_REQUEST_TIMEOUT_MILLIS);
         }
 
-        public boolean isMmsEnhancementEnabled() {
-            return DeviceConfig.getBoolean(
-                    DeviceConfig.NAMESPACE_TELEPHONY, MMS_ENHANCEMENT_ENABLED, true);
-        }
-
         public int getAdditionalNetworkAcquireTimeoutMillis() {
             return ADDITIONAL_NETWORK_ACQUIRE_TIMEOUT_MILLIS;
         }
@@ -327,31 +294,24 @@
         mSubId = subId;
         mReleaseHandler = new Handler(Looper.getMainLooper());
 
-        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
-        ServiceState serviceState = null;
-        if (telephonyManager != null) {
-            serviceState = telephonyManager.getServiceState();
-        }
+        NetworkRequest.Builder builder = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(mSubId).build());
 
-        // During Satellite network , network available is Satellite transport with restricted
-        // capability only . So build mms message network request with related capability
-        if (Flags.carrierEnabledSatelliteFlag()
-                && serviceState != null && serviceState.isUsingNonTerrestrialNetwork()) {
-            mNetworkRequest = new NetworkRequest.Builder()
-                    .addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
-                    .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
-                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                    .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
-                            .setSubscriptionId(mSubId).build())
-                    .build();
-         } else {
-            mNetworkRequest = new NetworkRequest.Builder()
-                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                    .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
-                    .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
-                            .setSubscriptionId(mSubId).build())
-                    .build();
+        // With Satellite internet support, add satellite transport with restricted capability to
+        // support mms over satellite network
+        if (Flags.satelliteInternet()) {
+            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+            try {
+                // TODO: b/331622062 remove the try/catch
+                builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+            } catch (IllegalArgumentException exception) {
+                LogUtil.e("TRANSPORT_SATELLITE is not supported.");
+            }
         }
+        mNetworkRequest = builder.build();
 
         mNetworkReleaseTask = new Runnable() {
             @Override
@@ -381,8 +341,9 @@
      *
      * @param requestId request ID for logging
      * @throws com.android.mms.service.exception.MmsNetworkException if we fail to acquire it
+     * @return The net Id of the acquired network.
      */
-    public void acquireNetwork(final String requestId) throws MmsNetworkException {
+    public int acquireNetwork(final String requestId) throws MmsNetworkException {
         int networkRequestTimeoutMillis = mDeps.getNetworkRequestTimeoutMillis();
 
         synchronized (this) {
@@ -392,7 +353,7 @@
             if (mNetwork != null) {
                 // Already available
                 LogUtil.d(requestId, "MmsNetworkManager: already available");
-                return;
+                return mNetwork.getNetId();
             }
 
             if (!mSimCardStateChangedReceiverRegistered) {
@@ -430,7 +391,7 @@
 
             if (mNetwork != null) {
                 // Success
-                return;
+                return mNetwork.getNetId();
             }
 
             if (mNetworkCallback != null) { // Timed out
@@ -454,22 +415,17 @@
      * Release the MMS network when nobody is holding on to it.
      *
      * @param requestId          request ID for logging.
-     * @param canRelease         whether the request can be released. An early release of a request
-     *                           can result in unexpected network torn down, as that network is used
-     *                           for immediate retry.
      * @param shouldDelayRelease whether the release should be delayed for a carrier-configured
      *                           timeout (default 5 seconds), the regular use case is to delay this
      *                           for DownloadRequests to use the network for sending an
      *                           acknowledgement on the same network.
      */
-    public void releaseNetwork(final String requestId, final boolean canRelease,
-            final boolean shouldDelayRelease) {
+    public void releaseNetwork(final String requestId, final boolean shouldDelayRelease) {
         synchronized (this) {
             if (mMmsRequestCount > 0) {
                 mMmsRequestCount -= 1;
-                LogUtil.d(requestId, "MmsNetworkManager: release, count=" + mMmsRequestCount
-                        + " canRelease=" + canRelease);
-                if (mMmsRequestCount < 1 && canRelease) {
+                LogUtil.d(requestId, "MmsNetworkManager: release, count=" + mMmsRequestCount);
+                if (mMmsRequestCount < 1) {
                     if (shouldDelayRelease) {
                         // remove previously posted task and post a delayed task on the release
                         // handler to release the network
@@ -544,19 +500,15 @@
     public MmsHttpClient getOrCreateHttpClient() {
         synchronized (this) {
             if (mMmsHttpClient == null) {
-                populateHttpClientWithCurrentNetwork();
+                if (mNetwork != null) {
+                    // Create new MmsHttpClient for the current Network
+                    mMmsHttpClient = new MmsHttpClient(mContext, mNetwork, mConnectivityManager);
+                }
             }
             return mMmsHttpClient;
         }
     }
 
-    // Create new MmsHttpClient for the current Network
-    private void populateHttpClientWithCurrentNetwork() {
-        if (mNetwork != null) {
-            mMmsHttpClient = new MmsHttpClient(mContext, mNetwork, mConnectivityManager);
-        }
-    }
-
     /**
      * Get the APN name for the active network
      *
@@ -583,4 +535,15 @@
     protected int getNetworkReleaseTimeoutMillis() {
         return mNetworkReleaseTimeoutMillis;
     }
+
+    /**
+     * Indicates satellite transport status for active network
+     *
+     * @return {@code true} if satellite transport, otherwise {@code false}
+     */
+    public boolean isSatelliteTransport() {
+        LogUtil.w("satellite transport status: " + mIsSatelliteTransport);
+        return mIsSatelliteTransport;
+    }
+
 }
diff --git a/src/com/android/mms/service/MmsRequest.java b/src/com/android/mms/service/MmsRequest.java
index 1d0cb2b..604d9de 100644
--- a/src/com/android/mms/service/MmsRequest.java
+++ b/src/com/android/mms/service/MmsRequest.java
@@ -38,13 +38,14 @@
 import android.telephony.ims.ImsMmTelManager;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.flags.Flags;
 import com.android.mms.service.exception.ApnException;
 import com.android.mms.service.exception.MmsHttpException;
 import com.android.mms.service.exception.MmsNetworkException;
-import com.android.mms.service.exception.VoluntaryDisconnectMmsHttpException;
 import com.android.mms.service.metrics.MmsStats;
 
 import java.util.UUID;
@@ -129,18 +130,30 @@
 
     class MonitorTelephonyCallback extends TelephonyCallback implements
             TelephonyCallback.PreciseDataConnectionStateListener {
+
+        /** The lock to update mNetworkIdToApn. */
+        private final Object mLock = new Object();
+        /**
+         * Track the network agent Id to APN. Usually we have at most 2 networks that are capable of
+         * MMS at the same time (terrestrial and satellite)
+         */
+        @GuardedBy("mLock")
+        private final SparseArray<ApnSetting> mNetworkIdToApn = new SparseArray<>(2);
         @Override
         public void onPreciseDataConnectionStateChanged(
-                PreciseDataConnectionState connectionState) {
-            if (connectionState == null) {
-                return;
-            }
+                @NonNull PreciseDataConnectionState connectionState) {
             ApnSetting apnSetting = connectionState.getApnSetting();
-            int apnTypes = apnSetting.getApnTypeBitmask();
-            if ((apnTypes & ApnSetting.TYPE_MMS) != 0) {
-                mLastConnectionFailure = connectionState.getLastCauseCode();
-                LogUtil.d("onPreciseDataConnectionStateChanged mLastConnectionFailure: "
-                        + mLastConnectionFailure);
+            if (apnSetting != null) {
+                // Only track networks that are capable of MMS.
+                if ((apnSetting.getApnTypeBitmask() & ApnSetting.TYPE_MMS) != 0) {
+                    LogUtil.d("onPreciseDataConnectionStateChanged: " + connectionState);
+                    mLastConnectionFailure = connectionState.getLastCauseCode();
+                    if (Flags.mmsGetApnFromPdsc()) {
+                        synchronized (mLock) {
+                            mNetworkIdToApn.put(connectionState.getNetId(), apnSetting);
+                        }
+                    }
+                }
             }
         }
     }
@@ -177,38 +190,56 @@
         byte[] response = null;
         int retryId = 0;
         currentState = MmsRequestState.PrepareForHttpRequest;
-        int attemptedTimes = 0;
+
         if (!prepareForHttpRequest()) { // Prepare request, like reading pdu data from user
             LogUtil.e(requestId, "Failed to prepare for request");
             result = SmsManager.MMS_ERROR_IO_ERROR;
         } else { // Execute
             long retryDelaySecs = 2;
             // Try multiple times of MMS HTTP request, depending on the error.
-            while (retryId < RETRY_TIMES) {
+            for (retryId = 0; retryId < RETRY_TIMES; retryId++) {
                 httpStatusCode = 0; // Clear for retry.
                 MonitorTelephonyCallback connectionStateCallback = new MonitorTelephonyCallback();
                 try {
                     listenToDataConnectionState(connectionStateCallback);
                     currentState = MmsRequestState.AcquiringNetwork;
-                    networkManager.acquireNetwork(requestId);
-                    final String apnName = networkManager.getApnName();
-                    LogUtil.d(requestId, "APN name is " + apnName);
-                    ApnSettings apn = null;
+                    int networkId = networkManager.acquireNetwork(requestId);
                     currentState = MmsRequestState.LoadingApn;
-                    try {
-                        apn = ApnSettings.load(context, apnName, mSubId, requestId);
-                    } catch (ApnException e) {
-                        // If no APN could be found, fall back to trying without the APN name
-                        if (apnName == null) {
-                            // If the APN name was already null then don't need to retry
-                            throw (e);
+                    ApnSettings apn = null;
+                    ApnSetting networkApn = null;
+                    if (Flags.mmsGetApnFromPdsc()) {
+                        synchronized (connectionStateCallback.mLock) {
+                            networkApn = connectionStateCallback.mNetworkIdToApn.get(networkId);
                         }
-                        LogUtil.i(requestId, "No match with APN name: "
-                                + apnName + ", try with no name");
-                        apn = ApnSettings.load(context, null, mSubId, requestId);
+                        if (networkApn != null) {
+                            apn = ApnSettings.getApnSettingsFromNetworkApn(networkApn);
+                        }
                     }
-                    LogUtil.d(requestId, "Using " + apn.toString());
+                    if (apn == null) {
+                        final String apnName = networkManager.getApnName();
+                        LogUtil.d(requestId, "APN name is " + apnName);
+                        try {
+                            apn = ApnSettings.load(context, apnName, mSubId, requestId);
+                        } catch (ApnException e) {
+                            // If no APN could be found, fall back to trying without the APN name
+                            if (apnName == null) {
+                                // If the APN name was already null then don't need to retry
+                                throw (e);
+                            }
+                            LogUtil.i(requestId, "No match with APN name: "
+                                    + apnName + ", try with no name");
+                            apn = ApnSettings.load(context, null, mSubId, requestId);
+                        }
+                    }
+
+                    if (Flags.mmsGetApnFromPdsc() && networkApn == null && apn != null) {
+                        reportAnomaly("Can't find MMS APN in mms network",
+                                UUID.fromString("2bdda74d-3cf4-44ad-a87f-24c961212a6f"));
+                    }
+
+                    LogUtil.d(requestId, "Using APN " + apn);
                     if (Flags.carrierEnabledSatelliteFlag()
+                            && networkManager.isSatelliteTransport()
                             && !canTransferPayloadOnCurrentNetwork()) {
                         LogUtil.e(requestId, "PDU too large for satellite");
                         result = SmsManager.MMS_ERROR_TOO_LARGE_FOR_TRANSPORT;
@@ -228,12 +259,8 @@
                     result = SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS;
                     break;
                 } catch (MmsHttpException e) {
-                    if (e instanceof VoluntaryDisconnectMmsHttpException) {
-                        result = Activity.RESULT_CANCELED;
-                    } else {
-                        LogUtil.e(requestId, "HTTP or network I/O failure", e);
-                        result = SmsManager.MMS_ERROR_HTTP_FAILURE;
-                    }
+                    LogUtil.e(requestId, "HTTP or network I/O failure", e);
+                    result = SmsManager.MMS_ERROR_HTTP_FAILURE;
                     httpStatusCode = e.getStatusCode();
                     // Retry
                 } catch (Exception e) {
@@ -241,36 +268,13 @@
                     result = SmsManager.MMS_ERROR_UNSPECIFIED;
                     break;
                 } finally {
-                    // Don't release the MMS network if the last attempt was voluntarily
-                    // cancelled (due to better network available), because releasing the request
-                    // could result that network being torn down as it's thought to be useless.
-                    boolean canRelease = false;
-                    if (result != Activity.RESULT_CANCELED) {
-                        retryId++;
-                        canRelease = true;
-                    }
-                    // Otherwise, delay the release for successful download request.
-                    networkManager.releaseNetwork(requestId, canRelease,
-                            this instanceof DownloadRequest && result == Activity.RESULT_OK);
-
+                    // Release the MMS network immediately except successful DownloadRequest.
+                    networkManager.releaseNetwork(requestId,
+                            this instanceof DownloadRequest
+                                    && result == Activity.RESULT_OK);
                     stopListeningToDataConnectionState(connectionStateCallback);
                 }
 
-                // THEORETICALLY WOULDN'T OCCUR - PUTTING HERE AS A SAFETY NET.
-                // TODO: REMOVE WITH FLAG mms_enhancement_enabled after soaking enough time, V-QPR.
-                // Only possible if network kept disconnecting due to Activity.RESULT_CANCELED,
-                // causing retryId doesn't increase and thus stuck in the infinite loop.
-                // However, it's theoretically impossible because RESULT_CANCELED is only triggered
-                // when a WLAN network becomes newly available in addition to an existing network.
-                // Therefore, the WLAN network's own death cannot be triggered by RESULT_CANCELED,
-                // and thus must result in retryId++.
-                if (++attemptedTimes > RETRY_TIMES * 2) {
-                    LogUtil.e(requestId, "Retry is performed too many times");
-                    reportAnomaly("MMS retried too many times",
-                            UUID.fromString("038c9155-5daa-4515-86ae-aafdd33c1435"));
-                    break;
-                }
-
                 if (result != Activity.RESULT_CANCELED) {
                     try { // Cool down retry if the previous attempt wasn't voluntarily cancelled.
                         new CountDownLatch(1).await(retryDelaySecs, TimeUnit.SECONDS);
@@ -592,11 +596,6 @@
             LogUtil.d("canTransferPayloadOnCurrentNetwork serviceState null");
             return true;    // assume we're not connected to a satellite
         }
-        LogUtil.d("canTransferPayloadOnCurrentNetwork onSatellite: "
-                + serviceState.isUsingNonTerrestrialNetwork());
-        if (!serviceState.isUsingNonTerrestrialNetwork()) {
-            return true;    // not connected to satellite, no size limit
-        }
         long payloadSize = getPayloadSize();
         int maxPduSize = mMmsConfig
                 .getInt(CarrierConfigManager.KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT);
diff --git a/src/com/android/mms/service/MmsService.java b/src/com/android/mms/service/MmsService.java
index 388eceb..6213513 100644
--- a/src/com/android/mms/service/MmsService.java
+++ b/src/com/android/mms/service/MmsService.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.app.Service;
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -1095,6 +1096,11 @@
             LogUtil.e("Uri is null");
             return 0;
         }
+        int contentUriUserID = ContentProvider.getUserIdFromUri(contentUri, UserHandle.myUserId());
+        if (UserHandle.myUserId() != contentUriUserID) {
+            LogUtil.e("Uri is invalid");
+            return 0;
+        }
         Callable<Integer> copyPduToArray = new Callable<Integer>() {
             public Integer call() {
                 ParcelFileDescriptor.AutoCloseInputStream inStream = null;
diff --git a/src/com/android/mms/service/exception/VoluntaryDisconnectMmsHttpException.java b/src/com/android/mms/service/exception/VoluntaryDisconnectMmsHttpException.java
deleted file mode 100644
index 2b6840a..0000000
--- a/src/com/android/mms/service/exception/VoluntaryDisconnectMmsHttpException.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 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.mms.service.exception;
-
-/**
- * Thrown when voluntarily disconnect an MMS http connection to trigger immediate retry. This
- * exception indicates the connection is voluntarily cancelled, instead of a failure.
- */
-public class VoluntaryDisconnectMmsHttpException extends MmsHttpException{
-    public VoluntaryDisconnectMmsHttpException(int statusCode, String message) {
-        super(statusCode, message);
-    }
-}
diff --git a/src/com/android/mms/service/metrics/MmsMetricsCollector.java b/src/com/android/mms/service/metrics/MmsMetricsCollector.java
index 8da61ba..1bf9211 100644
--- a/src/com/android/mms/service/metrics/MmsMetricsCollector.java
+++ b/src/com/android/mms/service/metrics/MmsMetricsCollector.java
@@ -26,14 +26,13 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.util.ConcurrentUtils;
 import com.android.mms.IncomingMms;
 import com.android.mms.MmsStatsLog;
 import com.android.mms.OutgoingMms;
-import com.android.internal.util.ConcurrentUtils;
 
 import java.time.Duration;
 import java.util.List;
-import java.util.concurrent.Executor;
 
 /**
  * Implements statsd pullers for Mms.
@@ -91,7 +90,8 @@
                 mms.getMmsCount(),
                 mms.getRetryId(),
                 mms.getHandledByCarrierApp(),
-                mms.getIsManagedProfile());
+                mms.getIsManagedProfile(),
+                mms.getIsNtn());
     }
 
     private static StatsEvent buildStatsEvent(OutgoingMms mms) {
@@ -109,7 +109,8 @@
                 mms.getIsFromDefaultApp(),
                 mms.getRetryId(),
                 mms.getHandledByCarrierApp(),
-                mms.getIsManagedProfile());
+                mms.getIsManagedProfile(),
+                mms.getIsNtn());
     }
 
     @Override
diff --git a/src/com/android/mms/service/metrics/MmsStats.java b/src/com/android/mms/service/metrics/MmsStats.java
index fd45a5d..6df9f79 100644
--- a/src/com/android/mms/service/metrics/MmsStats.java
+++ b/src/com/android/mms/service/metrics/MmsStats.java
@@ -33,6 +33,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.UiccCardInfo;
+import android.util.Log;
 
 import com.android.internal.telephony.SmsApplication;
 import com.android.internal.telephony.flags.Flags;
@@ -104,6 +105,7 @@
                 .setRetryId(retryId)
                 .setHandledByCarrierApp(handledByCarrierApp)
                 .setIsManagedProfile(isManagedProfile())
+                .setIsNtn(isUsingNonTerrestrialNetwork())
                 .build();
         mPersistMmsAtomsStorage.addIncomingMms(incomingMms);
     }
@@ -124,6 +126,7 @@
                 .setRetryId(retryId)
                 .setHandledByCarrierApp(handledByCarrierApp)
                 .setIsManagedProfile(isManagedProfile())
+                .setIsNtn(isUsingNonTerrestrialNetwork())
                 .build();
         mPersistMmsAtomsStorage.addOutgoingMms(outgoingMms);
     }
@@ -224,6 +227,21 @@
         return SmsApplication.isDefaultMmsApplicationAsUser(mContext, mCallingPkg, userHandle);
     }
 
+    /** Determines whether device is non-terrestrial network or not. */
+    private boolean isUsingNonTerrestrialNetwork() {
+        if (!Flags.carrierEnabledSatelliteFlag()) {
+            return false;
+        }
+
+        ServiceState ss = mTelephonyManager.getServiceState();
+        if (ss != null) {
+            return ss.isUsingNonTerrestrialNetwork();
+        } else {
+            Log.e(TAG, "isUsingNonTerrestrialNetwork(): ServiceState is null");
+        }
+        return false;
+    }
+
     /**
      * Returns the interval in milliseconds between sending/receiving MMS message and current time.
      * Calculates the time taken to send message to the network
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index 9689e31..0c41dbf 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -21,4 +21,6 @@
 
     instrumentation_for: "MmsService",
     upstream: true,
+
+    strict_mode: false,
 }
diff --git a/tests/robotests/src/com/android/mms/service/ApnSettingsTest.java b/tests/robotests/src/com/android/mms/service/ApnSettingsTest.java
index b184e16..7f920ef 100644
--- a/tests/robotests/src/com/android/mms/service/ApnSettingsTest.java
+++ b/tests/robotests/src/com/android/mms/service/ApnSettingsTest.java
@@ -43,6 +43,11 @@
 
 @RunWith(RobolectricTestRunner.class)
 public final class ApnSettingsTest {
+    private static final String APN_NAME = "mms.com";
+    private static final String ENTRY_NAME = "mms apn";
+    private static final String URI = "mmscUrl";
+    private static final String ADDRESS = "mmsProxyAddress";
+    private static final int PORT = 15;
 
     private Context context;
 
@@ -94,6 +99,42 @@
                 Telephony.Carriers.CONTENT_URI.getAuthority(), new FakeApnSettingsProvider(cursor));
     }
 
+    @Test
+    public void getApnSettingsFromNetworkApn_normal() {
+        ApnSetting networkApn = new ApnSetting.Builder().setApnName(APN_NAME)
+                .setEntryName(ENTRY_NAME)
+                .setApnTypeBitmask(ApnSetting.TYPE_MMS)
+                .setMmsc(Uri.parse(URI))
+                .setMmsProxyPort(PORT)
+                .setMmsProxyAddress(ADDRESS).build();
+        ApnSettings apnSettings = ApnSettings.getApnSettingsFromNetworkApn(networkApn);
+        assertThat(apnSettings.getMmscUrl()).isEqualTo(URI);
+        assertThat(apnSettings.getProxyPort()).isEqualTo(PORT);
+        assertThat(apnSettings.getProxyAddress()).isEqualTo(ADDRESS);
+    }
+
+    @Test
+    public void getApnSettingsFromNetworkApn_emptyMmsUrl_returnEmpty() {
+        ApnSetting networkApn = new ApnSetting.Builder().setApnName(APN_NAME)
+                .setEntryName(ENTRY_NAME)
+                .setApnTypeBitmask(ApnSetting.TYPE_MMS)
+                .setMmsProxyPort(PORT)
+                .setMmsProxyAddress(ADDRESS).build();
+        ApnSettings apnSettings = ApnSettings.getApnSettingsFromNetworkApn(networkApn);
+        assertThat(apnSettings).isNull();
+    }
+
+    @Test
+    public void getApnSettingsFromNetworkApn_nullMmsPort_defaultProxyPortUsed() {
+        ApnSetting networkApn = new ApnSetting.Builder().setApnName(APN_NAME)
+                .setEntryName(ENTRY_NAME)
+                .setApnTypeBitmask(ApnSetting.TYPE_MMS)
+                .setMmsc(Uri.parse(URI))
+                .setMmsProxyAddress(ADDRESS).build();
+        ApnSettings apnSettings = ApnSettings.getApnSettingsFromNetworkApn(networkApn);
+        assertThat(apnSettings.getProxyPort()).isEqualTo(80);
+    }
+
     private final class FakeApnSettingsProvider extends ContentProvider {
 
         private final Cursor cursor;
diff --git a/tests/robotests/src/com/android/mms/service/MmsNetworkManagerTest.java b/tests/robotests/src/com/android/mms/service/MmsNetworkManagerTest.java
index 70b1a0a..dff2bab 100644
--- a/tests/robotests/src/com/android/mms/service/MmsNetworkManagerTest.java
+++ b/tests/robotests/src/com/android/mms/service/MmsNetworkManagerTest.java
@@ -22,16 +22,13 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.fail;
 
-import static org.junit.Assert.assertNotSame;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
-import static org.robolectric.RuntimeEnvironment.getMasterScheduler;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -41,7 +38,6 @@
 import android.net.NetworkInfo;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.TelephonyManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -51,7 +47,6 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 
-import java.lang.reflect.Field;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -155,29 +150,6 @@
     }
 
     @Test
-    public void testAvailableNetwork_wwanNetworkReplacedByWlanNetwork() throws Exception {
-        MmsHttpClient mockMmsHttpClient = mock(MmsHttpClient.class);
-        doReturn(true).when(mDeps).isMmsEnhancementEnabled();
-
-        // WWAN network is always available, whereas WLAN network needs extra time to be set up.
-        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mNetworkInfo).getSubtype();
-        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mNetworkInfo2).getSubtype();
-
-        final NetworkCallback callback = acquireAvailableNetworkAndGetCallback(
-                mTestNetwork /* expectNetwork */, MMS_APN /* expectApn */);
-        replaceInstance(MmsNetworkManager.class, "mMmsHttpClient", mMnm,
-                mockMmsHttpClient);
-
-        // The WLAN network become available.
-        callback.onCapabilitiesChanged(mTestNetwork2, USABLE_NC);
-        getMasterScheduler().advanceToLastPostedRunnable();
-
-        // Verify current connections disconnect, then the client is replaced with a new network.
-        verify(mockMmsHttpClient).disconnectAllUrlConnections();
-        assertNotSame(mMnm.getOrCreateHttpClient(), mockMmsHttpClient);
-    }
-
-    @Test
     public void testAvailableNetwork_networkBecomeSuspend() throws Exception {
         final NetworkCallback callback = acquireAvailableNetworkAndGetCallback(
                 mTestNetwork /* expectNetwork */, MMS_APN /* expectApn */);
@@ -284,12 +256,4 @@
 
         return future;
     }
-
-    /** Helper to replace instance field with reflection. */
-    private void replaceInstance(final Class c, final String instanceName,
-            final Object obj, final Object newValue) throws Exception {
-        Field field = c.getDeclaredField(instanceName);
-        field.setAccessible(true);
-        field.set(obj, newValue);
-    }
 }
diff --git a/tests/unittests/src/com/android/mms/service/MmsHttpClientTest.java b/tests/unittests/src/com/android/mms/service/MmsHttpClientTest.java
index e144ef7..6fc2e72 100644
--- a/tests/unittests/src/com/android/mms/service/MmsHttpClientTest.java
+++ b/tests/unittests/src/com/android/mms/service/MmsHttpClientTest.java
@@ -18,10 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -31,29 +28,17 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.Network;
 import android.os.Bundle;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.mms.service.exception.VoluntaryDisconnectMmsHttpException;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.MockitoAnnotations;
 
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.SocketException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
 public class MmsHttpClientTest {
     // Mocked classes
     private Context mContext;
@@ -153,39 +138,4 @@
         assertThat(phoneNo).contains(subscriberPhoneNumber);
         verify(mSubscriptionManager).getPhoneNumber(subId);
     }
-
-    @Test
-    public void testDisconnectAllUrlConnections() throws IOException {
-        Network mockNetwork = mock(Network.class);
-        HttpURLConnection mockConnection = mock(HttpURLConnection.class);
-        doReturn(mockConnection).when(mockNetwork).openConnection(any(), any());
-        doReturn(mockNetwork).when(mockNetwork).getPrivateDnsBypassingCopy();
-        ConnectivityManager mockCm = mock(ConnectivityManager.class);
-        Bundle config = new Bundle();
-
-        // The external thread that voluntarily silently close the socket.
-        CountDownLatch latch = new CountDownLatch(1);
-        final ExecutorService externalThread = Executors.newSingleThreadExecutor();
-        doAnswer(invok -> {
-            latch.countDown();
-            return null;
-        }).when(mockConnection).disconnect();
-
-        MmsHttpClient clientUT = new MmsHttpClient(mContext, mockNetwork, mockCm);
-        doAnswer(invok -> {
-            externalThread.execute(clientUT::disconnectAllUrlConnections);
-            // connection.disconnect is silent, but it will trigger SocketException thrown from the
-            // connect thread.
-            if (latch.await(1, TimeUnit.SECONDS)) {
-                throw new SocketException("Socket Closed");
-            }
-            return null;
-        }).when(mockConnection).getResponseCode();
-
-        // Verify SocketException is transformed into VoluntaryDisconnectMmsHttpException
-        assertThrows(VoluntaryDisconnectMmsHttpException.class, () -> {
-            clientUT.execute("http://test", new byte[0], "GET", false,
-                                "", 0, config, 1, "requestId");
-        });
-    }
 }
diff --git a/tests/unittests/src/com/android/mms/service/metrics/MmsStatsTest.java b/tests/unittests/src/com/android/mms/service/metrics/MmsStatsTest.java
index 2b2cae5..089b3d7 100644
--- a/tests/unittests/src/com/android/mms/service/metrics/MmsStatsTest.java
+++ b/tests/unittests/src/com/android/mms/service/metrics/MmsStatsTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
@@ -34,13 +35,13 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.mms.IncomingMms;
 import com.android.mms.OutgoingMms;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-
 import org.mockito.ArgumentCaptor;
 
 public class MmsStatsTest {
@@ -92,6 +93,7 @@
         assertThat(incomingMms.getRetryId()).isEqualTo(0);
         assertThat(incomingMms.getHandledByCarrierApp()).isEqualTo(false);
         assertThat(incomingMms.getIsManagedProfile()).isEqualTo(false);
+        assertThat(incomingMms.getIsNtn()).isEqualTo(false);
         verifyNoMoreInteractions(mPersistMmsAtomsStorage);
     }
 
@@ -120,6 +122,7 @@
         assertThat(outgoingMms.getHandledByCarrierApp()).isEqualTo(false);
         assertThat(outgoingMms.getIsFromDefaultApp()).isEqualTo(false);
         assertThat(outgoingMms.getIsManagedProfile()).isEqualTo(false);
+        assertThat(outgoingMms.getIsNtn()).isEqualTo(false);
         verifyNoMoreInteractions(mPersistMmsAtomsStorage);
     }
 
@@ -151,4 +154,55 @@
         // getSubscriptionUserHandle should not be called if subID is inactive.
         verify(mSubscriptionManager, never()).getSubscriptionUserHandle(eq(inactiveSubId));
     }
+
+    @Test
+    public void testIsNtn_serviceState_notNull() {
+        if (!Flags.carrierEnabledSatelliteFlag()) {
+            return;
+        }
+
+        ServiceState serviceState = mock(ServiceState.class);
+        doReturn(serviceState).when(mTelephonyManager).getServiceState();
+        doReturn(true).when(serviceState).isUsingNonTerrestrialNetwork();
+
+        MmsStats mmsStats = new MmsStats(mContext, mPersistMmsAtomsStorage, 1,
+                mTelephonyManager, null, true);
+        mmsStats.addAtomToStorage(Activity.RESULT_OK);
+
+        ArgumentCaptor<IncomingMms> incomingMmsCaptor = ArgumentCaptor.forClass(IncomingMms.class);
+        verify(mPersistMmsAtomsStorage).addIncomingMms(incomingMmsCaptor.capture());
+        IncomingMms incomingMms = incomingMmsCaptor.getValue();
+        assertThat(incomingMms.getIsNtn()).isEqualTo(true);
+
+        reset(mPersistMmsAtomsStorage);
+        reset(serviceState);
+        doReturn(false).when(serviceState).isUsingNonTerrestrialNetwork();
+
+        mmsStats = new MmsStats(mContext, mPersistMmsAtomsStorage, 1,
+                mTelephonyManager, null, true);
+        mmsStats.addAtomToStorage(Activity.RESULT_OK);
+
+        incomingMmsCaptor = ArgumentCaptor.forClass(IncomingMms.class);
+        verify(mPersistMmsAtomsStorage).addIncomingMms(incomingMmsCaptor.capture());
+        incomingMms = incomingMmsCaptor.getValue();
+        assertThat(incomingMms.getIsNtn()).isEqualTo(false);
+    }
+
+    @Test
+    public void testIsNtn_serviceState_Null() {
+        if (!Flags.carrierEnabledSatelliteFlag()) {
+            return;
+        }
+
+        doReturn(null).when(mTelephonyManager).getServiceState();
+
+        MmsStats mmsStats = new MmsStats(mContext, mPersistMmsAtomsStorage, 1,
+                mTelephonyManager, null, true);
+        mmsStats.addAtomToStorage(Activity.RESULT_OK);
+
+        ArgumentCaptor<IncomingMms> incomingMmsCaptor = ArgumentCaptor.forClass(IncomingMms.class);
+        verify(mPersistMmsAtomsStorage).addIncomingMms(incomingMmsCaptor.capture());
+        IncomingMms incomingMms = incomingMmsCaptor.getValue();
+        assertThat(incomingMms.getIsNtn()).isEqualTo(false);
+    }
 }
\ No newline at end of file