Merge "Update flag meta data for idenfitying the owner and target release" into main
diff --git a/flags/misc.aconfig b/flags/misc.aconfig
index c9816b9..51f10cd 100644
--- a/flags/misc.aconfig
+++ b/flags/misc.aconfig
@@ -93,3 +93,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "hide_preinstalled_carrier_app_at_most_once"
+    namespace: "telephony"
+    description: "Fix bug when preloaded carrier app is uninstalled and lose provisioning data"
+    bug:"158028151"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/flags/network.aconfig b/flags/network.aconfig
index 7ed6c7a..cefd19b 100644
--- a/flags/network.aconfig
+++ b/flags/network.aconfig
@@ -1,10 +1,14 @@
 package: "com.android.internal.telephony.flags"
 
 flag {
-    name: "enable_carrier_config_n1_control"
+    name: "enable_carrier_config_n1_control_attempt2"
     namespace: "telephony"
     description: "enabling this flag allows KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY to control N1 mode enablement"
-    bug:"302033535"
+    bug:"328848947"
+
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
 
 flag {
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index c07c797..e54c969 100644
--- a/proto/src/persist_atoms.proto
+++ b/proto/src/persist_atoms.proto
@@ -310,6 +310,7 @@
     optional int64 message_id = 14;
     optional int32 count = 15;
     optional bool is_managed_profile = 16;
+    optional bool is_ntn = 17;
 
     // Internal use only
     optional int32 hashCode = 10001;
@@ -334,6 +335,8 @@
     optional int32 send_error_code = 16;
     optional int32 network_error_code = 17;
     optional bool is_managed_profile = 18;
+    optional bool is_emergency = 19;
+    optional bool is_ntn = 20;
 
     // Internal use only
     optional int32 hashCode = 10001;
diff --git a/src/java/com/android/internal/telephony/CarrierInfoManager.java b/src/java/com/android/internal/telephony/CarrierInfoManager.java
index 863db93..8364c0a 100644
--- a/src/java/com/android/internal/telephony/CarrierInfoManager.java
+++ b/src/java/com/android/internal/telephony/CarrierInfoManager.java
@@ -33,6 +33,7 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 
 import java.security.PublicKey;
@@ -297,7 +298,12 @@
         final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
                 .createForSubscriptionId(subId);
         int carrierId = telephonyManager.getSimCarrierId();
-        deleteCarrierInfoForImsiEncryption(context, subId, carrierId);
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            String simOperator = telephonyManager.getSimOperator();
+            deleteCarrierInfoForImsiEncryption(context, subId, carrierId, simOperator);
+        } else {
+            deleteCarrierInfoForImsiEncryption(context, subId, carrierId);
+        }
         Intent resetIntent = new Intent(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
         SubscriptionManager.putPhoneIdAndSubIdExtra(resetIntent, mPhoneId);
         context.sendBroadcastAsUser(resetIntent, UserHandle.ALL);
@@ -312,13 +318,29 @@
      */
     public static void deleteCarrierInfoForImsiEncryption(Context context, int subId,
             int carrierId) {
+        deleteCarrierInfoForImsiEncryption(context, subId, carrierId, null);
+    }
+
+    /**
+     * Deletes all the keys for a given Carrier from the device keystore.
+     * @param context Context
+     * @param subId SubscriptionId
+     * @param carrierId delete the key which matches the carrierId
+     * @param simOperator delete the key which matches the MCCMNC
+     *
+     */
+    public static void deleteCarrierInfoForImsiEncryption(Context context, int subId,
+            int carrierId, String simOperator) {
         Log.i(LOG_TAG, "deleting carrier key from db for subId=" + subId);
         String mcc = "";
         String mnc = "";
 
-        final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
-                .createForSubscriptionId(subId);
-        String simOperator = telephonyManager.getSimOperator();
+        if (TextUtils.isEmpty(simOperator)) {
+            final TelephonyManager telephonyManager = context.getSystemService(
+                            TelephonyManager.class)
+                    .createForSubscriptionId(subId);
+            simOperator = telephonyManager.getSimOperator();
+        }
         if (!TextUtils.isEmpty(simOperator)) {
             mcc = simOperator.substring(0, 3);
             mnc = simOperator.substring(3);
diff --git a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
index 9143f21..10a3a32 100644
--- a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -18,6 +18,7 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import android.annotation.NonNull;
 import android.app.AlarmManager;
 import android.app.DownloadManager;
 import android.app.KeyguardManager;
@@ -27,6 +28,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.net.Network;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Message;
@@ -34,19 +37,22 @@
 import android.os.UserManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ImsiEncryptionInfo;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 
+
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.Flags;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
@@ -57,6 +63,7 @@
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.Date;
+import java.util.List;
 import java.util.Random;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.ZipException;
@@ -99,6 +106,8 @@
 
     private static final int EVENT_ALARM_OR_CONFIG_CHANGE = 0;
     private static final int EVENT_DOWNLOAD_COMPLETE = 1;
+    private static final int EVENT_NETWORK_AVAILABLE = 2;
+    private static final int EVENT_SCREEN_UNLOCKED = 3;
 
 
     private static final int[] CARRIER_KEY_TYPES = {TelephonyManager.KEY_TYPE_EPDG,
@@ -111,47 +120,77 @@
     private boolean mAllowedOverMeteredNetwork = false;
     private boolean mDeleteOldKeyAfterDownload = false;
     private boolean mIsRequiredToHandleUnlock;
-    private TelephonyManager mTelephonyManager;
+    private final TelephonyManager mTelephonyManager;
     private UserManager mUserManager;
-
     @VisibleForTesting
-    public String mMccMncForDownload;
-    public int mCarrierId;
+    public String mMccMncForDownload = "";
+    public int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
     @VisibleForTesting
     public long mDownloadId;
-    private final FeatureFlags mFeatureFlags;
+    private DefaultNetworkCallback mDefaultNetworkCallback;
+    private ConnectivityManager mConnectivityManager;
+    private KeyguardManager mKeyguardManager;
 
-    public CarrierKeyDownloadManager(Phone phone, FeatureFlags featureFlags) {
+    public CarrierKeyDownloadManager(Phone phone) {
         mPhone = phone;
-        mFeatureFlags = featureFlags;
         mContext = phone.getContext();
         IntentFilter filter = new IntentFilter();
         filter.addAction(INTENT_KEY_RENEWAL_ALARM_PREFIX);
         filter.addAction(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
-        filter.addAction(Intent.ACTION_USER_PRESENT);
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        }
         mContext.registerReceiver(mBroadcastReceiver, filter, null, phone);
         mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
                 .createForSubscriptionId(mPhone.getSubId());
-        mUserManager = mContext.getSystemService(UserManager.class);
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+        } else {
+            mUserManager = mContext.getSystemService(UserManager.class);
+        }
         CarrierConfigManager carrierConfigManager = mContext.getSystemService(
                 CarrierConfigManager.class);
         // Callback which directly handle config change should be executed on handler thread
         carrierConfigManager.registerCarrierConfigChangeListener(this::post,
                 (slotIndex, subId, carrierId, specificCarrierId) -> {
-                    boolean isUserUnlocked = mUserManager.isUserUnlocked();
-
-                    if (isUserUnlocked && slotIndex == mPhone.getPhoneId()) {
-                        Log.d(LOG_TAG, "Carrier Config changed: slotIndex=" + slotIndex);
-                        handleAlarmOrConfigChange();
+                    if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+                        logd("CarrierConfig changed slotIndex = " + slotIndex + " subId = " + subId
+                                + " CarrierId = " + carrierId + " phoneId = "
+                                + mPhone.getPhoneId());
+                        // Below checks are necessary to optimise the logic.
+                        if ((slotIndex == mPhone.getPhoneId()) && (carrierId > 0
+                                || !TextUtils.isEmpty(
+                                mMccMncForDownload))) {
+                            mCarrierId = carrierId;
+                            updateSimOperator();
+                            // If device is screen locked do not proceed to handle
+                            // EVENT_ALARM_OR_CONFIG_CHANGE
+                            if (mKeyguardManager.isDeviceLocked()) {
+                                logd("Device is Locked");
+                                mIsRequiredToHandleUnlock = true;
+                            } else {
+                                logd("Carrier Config changed: slotIndex=" + slotIndex);
+                                sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                            }
+                        }
                     } else {
-                        Log.d(LOG_TAG, "User is locked");
-                        mContext.registerReceiver(mUserUnlockedReceiver, new IntentFilter(
-                                Intent.ACTION_USER_UNLOCKED));
+                        boolean isUserUnlocked = mUserManager.isUserUnlocked();
+
+                        if (isUserUnlocked && slotIndex == mPhone.getPhoneId()) {
+                            Log.d(LOG_TAG, "Carrier Config changed: slotIndex=" + slotIndex);
+                            handleAlarmOrConfigChange();
+                        } else {
+                            Log.d(LOG_TAG, "User is locked");
+                            mContext.registerReceiver(mUserUnlockedReceiver, new IntentFilter(
+                                    Intent.ACTION_USER_UNLOCKED));
+                        }
                     }
                 });
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
     }
 
+    // TODO remove this method upon imsiKeyRetryDownloadOnPhoneUnlock enabled.
     private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -167,7 +206,7 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
             if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
-                Log.d(LOG_TAG, "Download Complete");
+                logd("Download Complete");
                 sendMessage(obtainMessage(EVENT_DOWNLOAD_COMPLETE,
                         intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)));
             }
@@ -180,27 +219,36 @@
             String action = intent.getAction();
             int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
             int phoneId = mPhone.getPhoneId();
-            if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX)) {
-                int slotIndexExtra = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, -1);
-                if (slotIndexExtra == slotIndex) {
-                    Log.d(LOG_TAG, "Handling key renewal alarm: " + action);
-                    sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+            switch (action) {
+                case INTENT_KEY_RENEWAL_ALARM_PREFIX -> {
+                    int slotIndexExtra = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+                            -1);
+                    if (slotIndexExtra == slotIndex) {
+                        logd("Handling key renewal alarm: " + action);
+                        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+                            updateSimOperator();
+                        }
+                        sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                    }
                 }
-            } else if (action.equals(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD)) {
-                if (phoneId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
-                        SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
-                    Log.d(LOG_TAG, "Handling reset intent: " + action);
-                    sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                case TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD -> {
+                    if (phoneId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
+                            SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
+                        logd("Handling reset intent: " + action);
+                        sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                    }
                 }
-            }  else if (action.equals(Intent.ACTION_USER_PRESENT)) {
-                // The Carrier key download fails when SIM is inserted while device is locked
-                // hence adding a retry logic when device is unlocked.
-                Log.d(LOG_TAG,
-                        "device unlocked, isRequiredToHandleUnlock = " + mIsRequiredToHandleUnlock
-                                + ", slotIndex = " + slotIndex);
-                if (mIsRequiredToHandleUnlock) {
-                    mIsRequiredToHandleUnlock = false;
-                    sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                case Intent.ACTION_USER_UNLOCKED -> {
+                    // The Carrier key download fails when SIM is inserted while device is locked
+                    // hence adding a retry logic when device is unlocked.
+                    logd("device fully unlocked, isRequiredToHandleUnlock = "
+                            + mIsRequiredToHandleUnlock
+                            + ", slotIndex = " + slotIndex + "  hasActiveDataNetwork = " + (
+                            mConnectivityManager.getActiveNetwork() != null));
+                    if (mIsRequiredToHandleUnlock) {
+                        mIsRequiredToHandleUnlock = false;
+                        sendEmptyMessage(EVENT_SCREEN_UNLOCKED);
+                    }
                 }
             }
         }
@@ -209,76 +257,124 @@
     @Override
     public void handleMessage (Message msg) {
         switch (msg.what) {
-            case EVENT_ALARM_OR_CONFIG_CHANGE:
-                handleAlarmOrConfigChange();
-                break;
-            case EVENT_DOWNLOAD_COMPLETE:
+            case EVENT_ALARM_OR_CONFIG_CHANGE, EVENT_NETWORK_AVAILABLE, EVENT_SCREEN_UNLOCKED ->
+                    handleAlarmOrConfigChange();
+            case EVENT_DOWNLOAD_COMPLETE -> {
                 long carrierKeyDownloadIdentifier = (long) msg.obj;
-                String currentMccMnc = getSimOperator();
-                int carrierId = getSimCarrierId();
+                String currentMccMnc = Flags.imsiKeyRetryDownloadOnPhoneUnlock()
+                        ? mTelephonyManager.getSimOperator(mPhone.getSubId()) : getSimOperator();
+                int carrierId = Flags.imsiKeyRetryDownloadOnPhoneUnlock()
+                        ? mTelephonyManager.getSimCarrierId() : getSimCarrierId();
                 if (isValidDownload(currentMccMnc, carrierKeyDownloadIdentifier, carrierId)) {
                     onDownloadComplete(carrierKeyDownloadIdentifier, currentMccMnc, carrierId);
-                    onPostDownloadProcessing(carrierKeyDownloadIdentifier);
+                    onPostDownloadProcessing();
                 }
-                break;
+            }
         }
     }
 
-    private void onPostDownloadProcessing(long carrierKeyDownloadIdentifier) {
+    private void onPostDownloadProcessing() {
         resetRenewalAlarm();
-        cleanupDownloadInfo();
-
+        if(Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            mDownloadId = -1;
+        } else {
+            cleanupDownloadInfo();
+        }
         // unregister from DOWNLOAD_COMPLETE
         mContext.unregisterReceiver(mDownloadReceiver);
     }
 
     private void handleAlarmOrConfigChange() {
-        if (carrierUsesKeys()) {
-            if (areCarrierKeysAbsentOrExpiring()) {
-                boolean downloadStartedSuccessfully = downloadKey();
-                // if the download was attempted, but not started successfully, and if carriers uses
-                // keys, we'll still want to renew the alarms, and try downloading the key a day
-                // later.
-                if (!downloadStartedSuccessfully) {
-                    // If download fails due to the device lock, we will reattempt once the device
-                    // is unlocked.
-                    if (mFeatureFlags.imsiKeyRetryDownloadOnPhoneUnlock()) {
-                        KeyguardManager keyguardManager = mContext.getSystemService(
-                                KeyguardManager.class);
-                        if (keyguardManager.isKeyguardSecure()) {
-                            Log.e(LOG_TAG, "Key download failed in device lock state");
-                            mIsRequiredToHandleUnlock = true;
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            if (carrierUsesKeys()) {
+                if (areCarrierKeysAbsentOrExpiring()) {
+                    boolean hasActiveDataNetwork =
+                            (mConnectivityManager.getActiveNetwork() != null);
+                    boolean downloadStartedSuccessfully = hasActiveDataNetwork && downloadKey();
+                    // if the download was attempted, but not started successfully, and if
+                    // carriers uses keys, we'll still want to renew the alarms, and try
+                    // downloading the key a day later.
+                    int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
+                    if (downloadStartedSuccessfully) {
+                        unregisterDefaultNetworkCb(slotIndex);
+                    } else {
+                        // If download fails due to the device lock, we will reattempt once the
+                        // device is unlocked.
+                        mIsRequiredToHandleUnlock = mKeyguardManager.isDeviceLocked();
+                        loge("hasActiveDataConnection = " + hasActiveDataNetwork
+                                + "    isDeviceLocked = " + mIsRequiredToHandleUnlock);
+                        if (!hasActiveDataNetwork) {
+                            registerDefaultNetworkCb(slotIndex);
                         }
+                        resetRenewalAlarm();
                     }
-                    resetRenewalAlarm();
                 }
+                logd("handleAlarmOrConfigChange :: areCarrierKeysAbsentOrExpiring returned false");
             } else {
-                return;
+                cleanupRenewalAlarms();
+                if (!isOtherSlotHasCarrier()) {
+                    // delete any existing alarms.
+                    mPhone.deleteCarrierInfoForImsiEncryption(getSimCarrierId(), getSimOperator());
+                }
+                cleanupDownloadInfo();
             }
         } else {
-            // delete any existing alarms.
-            cleanupRenewalAlarms();
-            mPhone.deleteCarrierInfoForImsiEncryption(getSimCarrierId());
-
+            if (carrierUsesKeys()) {
+                if (areCarrierKeysAbsentOrExpiring()) {
+                    boolean downloadStartedSuccessfully = downloadKey();
+                    // if the download was attempted, but not started successfully, and if
+                    // carriers uses keys, we'll still want to renew the alarms, and try
+                    // downloading the key a day later.
+                    if (!downloadStartedSuccessfully) {
+                        resetRenewalAlarm();
+                    }
+                }
+            } else {
+                // delete any existing alarms.
+                cleanupRenewalAlarms();
+                mPhone.deleteCarrierInfoForImsiEncryption(getSimCarrierId());
+            }
         }
     }
 
+    private boolean isOtherSlotHasCarrier() {
+        SubscriptionManager subscriptionManager = mPhone.getContext().getSystemService(
+                SubscriptionManager.class);
+        List<SubscriptionInfo> subscriptionInfoList =
+                subscriptionManager.getActiveSubscriptionInfoList();
+        loge("handleAlarmOrConfigChange ActiveSubscriptionInfoList = " + (
+                (subscriptionInfoList != null) ? subscriptionInfoList.size() : null));
+        for (SubscriptionInfo subInfo : subscriptionInfoList) {
+            if (mPhone.getSubId() != subInfo.getSubscriptionId()
+                    && subInfo.getCarrierId() == mPhone.getCarrierId()) {
+                // We do not proceed to remove the Key from the DB as another slot contains
+                // same operator sim which is in active state.
+                loge("handleAlarmOrConfigChange same operator sim in another slot");
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void cleanupDownloadInfo() {
-        Log.d(LOG_TAG, "Cleaning up download info");
+        logd("Cleaning up download info");
         mDownloadId = -1;
-        mMccMncForDownload = null;
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            mMccMncForDownload = "";
+        } else {
+            mMccMncForDownload = null;
+        }
         mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
     }
 
     private void cleanupRenewalAlarms() {
-        Log.d(LOG_TAG, "Cleaning up existing renewal alarms");
+        logd("Cleaning up existing renewal alarms");
         int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
         Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX);
         intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
         PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-        AlarmManager alarmManager =
-                (AlarmManager) mContext.getSystemService(mContext.ALARM_SERVICE);
+        AlarmManager alarmManager =mContext.getSystemService(AlarmManager.class);
         alarmManager.cancel(carrierKeyDownloadIntent);
     }
 
@@ -331,7 +427,7 @@
         cleanupRenewalAlarms();
         int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
         long minExpirationDate = getExpirationDate();
-        Log.d(LOG_TAG, "minExpirationDate: " + new Date(minExpirationDate));
+        logd("minExpirationDate: " + new Date(minExpirationDate));
         final AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
                 Context.ALARM_SERVICE);
         Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX);
@@ -339,16 +435,35 @@
         PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         alarmManager.set(AlarmManager.RTC_WAKEUP, minExpirationDate, carrierKeyDownloadIntent);
-        Log.d(LOG_TAG, "setRenewalAlarm: action=" + intent.getAction() + " time="
+        logd("setRenewalAlarm: action=" + intent.getAction() + " time="
                 + new Date(minExpirationDate));
     }
 
     /**
+     * Read the store the sim operetor value and update the value in case of change in the sim
+     * operetor.
+     */
+    public void updateSimOperator() {
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            String simOperator = mPhone.getOperatorNumeric();
+            if (!TextUtils.isEmpty(simOperator) && !simOperator.equals(mMccMncForDownload)) {
+                mMccMncForDownload = simOperator;
+                logd("updateSimOperator, Initialized mMccMncForDownload = " + mMccMncForDownload);
+            }
+        }
+    }
+
+    /**
      * Returns the sim operator.
      **/
     @VisibleForTesting
     public String getSimOperator() {
-        return mTelephonyManager.getSimOperator(mPhone.getSubId());
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            updateSimOperator();
+            return mMccMncForDownload;
+        } else {
+            return mTelephonyManager.getSimOperator(mPhone.getSubId());
+        }
     }
 
     /**
@@ -356,7 +471,11 @@
      **/
     @VisibleForTesting
     public int getSimCarrierId() {
-        return mTelephonyManager.getSimCarrierId();
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            return (mCarrierId > 0) ? mCarrierId : mPhone.getCarrierId();
+        } else {
+            return mTelephonyManager.getSimCarrierId();
+        }
     }
 
     /**
@@ -367,7 +486,7 @@
     @VisibleForTesting
     public boolean isValidDownload(String currentMccMnc, long currentDownloadId, int carrierId) {
         if (currentDownloadId != mDownloadId) {
-            Log.e(LOG_TAG, "download ID=" + currentDownloadId
+            loge( "download ID=" + currentDownloadId
                     + " for completed download does not match stored id=" + mDownloadId);
             return false;
         }
@@ -375,12 +494,12 @@
         if (TextUtils.isEmpty(currentMccMnc) || TextUtils.isEmpty(mMccMncForDownload)
                 || !TextUtils.equals(currentMccMnc, mMccMncForDownload)
                 || mCarrierId != carrierId) {
-            Log.e(LOG_TAG, "currentMccMnc=" + currentMccMnc + " storedMccMnc =" + mMccMncForDownload
+            loge( "currentMccMnc=" + currentMccMnc + " storedMccMnc =" + mMccMncForDownload
                     + "currentCarrierId = " + carrierId + "  storedCarrierId = " + mCarrierId);
             return false;
         }
 
-        Log.d(LOG_TAG, "Matched MccMnc =  " + currentMccMnc + ", carrierId = " + carrierId
+        logd("Matched MccMnc =  " + currentMccMnc + ", carrierId = " + carrierId
                 + ", downloadId: " + currentDownloadId);
         return true;
     }
@@ -390,7 +509,7 @@
      **/
     private void onDownloadComplete(long carrierKeyDownloadIdentifier, String mccMnc,
             int carrierId) {
-        Log.d(LOG_TAG, "onDownloadComplete: " + carrierKeyDownloadIdentifier);
+        logd("onDownloadComplete: " + carrierKeyDownloadIdentifier);
         String jsonStr;
         DownloadManager.Query query = new DownloadManager.Query();
         query.setFilterById(carrierKeyDownloadIdentifier);
@@ -405,22 +524,21 @@
                 try {
                     jsonStr = convertToString(mDownloadManager, carrierKeyDownloadIdentifier);
                     if (TextUtils.isEmpty(jsonStr)) {
-                        Log.d(LOG_TAG, "fallback to no gzip");
+                        logd("fallback to no gzip");
                         jsonStr = convertToStringNoGZip(mDownloadManager,
                                 carrierKeyDownloadIdentifier);
                     }
                     parseJsonAndPersistKey(jsonStr, mccMnc, carrierId);
                 } catch (Exception e) {
-                    Log.e(LOG_TAG, "Error in download:" + carrierKeyDownloadIdentifier
+                    loge( "Error in download:" + carrierKeyDownloadIdentifier
                             + ". " + e);
                 } finally {
                     mDownloadManager.remove(carrierKeyDownloadIdentifier);
                 }
             }
-            Log.d(LOG_TAG, "Completed downloading keys");
+            logd("Completed downloading keys");
         }
         cursor.close();
-        return;
     }
 
     /**
@@ -442,7 +560,7 @@
                     CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING,
                     CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL);
         } catch (RuntimeException e) {
-            Log.e(LOG_TAG, "CarrierConfigLoader is not available.");
+            loge( "CarrierConfigLoader is not available.");
         }
         if (b == null || b.isEmpty()) {
             return false;
@@ -452,10 +570,10 @@
         mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING);
         mAllowedOverMeteredNetwork = b.getBoolean(
                 CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL);
+
         if (mKeyAvailability == 0 || TextUtils.isEmpty(mURL)) {
-            Log.d(LOG_TAG,
-                    "Carrier not enabled or invalid values. mKeyAvailability=" + mKeyAvailability
-                            + " mURL=" + mURL);
+            logd("Carrier not enabled or invalid values. mKeyAvailability=" + mKeyAvailability
+                    + " mURL=" + mURL);
             return false;
         }
         for (int key_type : CARRIER_KEY_TYPES) {
@@ -469,7 +587,7 @@
     private static String convertToStringNoGZip(DownloadManager downloadManager, long downloadId) {
         StringBuilder sb = new StringBuilder();
         try (InputStream source = new FileInputStream(
-                    downloadManager.openDownloadedFile(downloadId).getFileDescriptor())) {
+                downloadManager.openDownloadedFile(downloadId).getFileDescriptor())) {
             // If the carrier does not have the data gzipped, fallback to assuming it is not zipped.
             // parseJsonAndPersistKey may still fail if the data is malformed, so we won't be
             // persisting random bogus strings thinking it's the cert
@@ -488,8 +606,8 @@
 
     private static String convertToString(DownloadManager downloadManager, long downloadId) {
         try (InputStream source = new FileInputStream(
-                    downloadManager.openDownloadedFile(downloadId).getFileDescriptor());
-                    InputStream gzipIs = new GZIPInputStream(source)) {
+                downloadManager.openDownloadedFile(downloadId).getFileDescriptor());
+             InputStream gzipIs = new GZIPInputStream(source)) {
             BufferedReader reader = new BufferedReader(new InputStreamReader(gzipIs, UTF_8));
             StringBuilder sb = new StringBuilder();
 
@@ -523,7 +641,7 @@
     public void parseJsonAndPersistKey(String jsonStr, String mccMnc, int carrierId) {
         if (TextUtils.isEmpty(jsonStr) || TextUtils.isEmpty(mccMnc)
                 || carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
-            Log.e(LOG_TAG, "jsonStr or mcc, mnc: is empty or carrierId is UNKNOWN_CARRIER_ID");
+            loge( "jsonStr or mcc, mnc: is empty or carrierId is UNKNOWN_CARRIER_ID");
             return;
         }
         try {
@@ -548,22 +666,28 @@
                     if (typeString.equals(JSON_TYPE_VALUE_EPDG)) {
                         type = TelephonyManager.KEY_TYPE_EPDG;
                     } else if (!typeString.equals(JSON_TYPE_VALUE_WLAN)) {
-                        Log.e(LOG_TAG, "Invalid key-type specified: " + typeString);
+                        loge( "Invalid key-type specified: " + typeString);
                     }
                 }
                 String identifier = key.getString(JSON_IDENTIFIER);
                 Pair<PublicKey, Long> keyInfo =
                         getKeyInformation(cleanCertString(cert).getBytes());
                 if (mDeleteOldKeyAfterDownload) {
-                    mPhone.deleteCarrierInfoForImsiEncryption(TelephonyManager.UNKNOWN_CARRIER_ID);
+                    if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+                        mPhone.deleteCarrierInfoForImsiEncryption(
+                                TelephonyManager.UNKNOWN_CARRIER_ID, null);
+                    } else {
+                        mPhone.deleteCarrierInfoForImsiEncryption(
+                                TelephonyManager.UNKNOWN_CARRIER_ID);
+                    }
                     mDeleteOldKeyAfterDownload = false;
                 }
                 savePublicKey(keyInfo.first, type, identifier, keyInfo.second, mcc, mnc, carrierId);
             }
         } catch (final JSONException e) {
-            Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
+            loge( "Json parsing error: " + e.getMessage());
         } catch (final Exception e) {
-            Log.e(LOG_TAG, "Exception getting certificate: " + e);
+            loge( "Exception getting certificate: " + e);
         }
     }
 
@@ -584,7 +708,7 @@
     public static boolean isKeyEnabled(int keyType, int keyAvailability) {
         // since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
         int returnValue = (keyAvailability >> (keyType - 1)) & 1;
-        return (returnValue == 1) ? true : false;
+        return returnValue == 1;
     }
 
     /**
@@ -603,31 +727,42 @@
             ImsiEncryptionInfo imsiEncryptionInfo =
                     mPhone.getCarrierInfoForImsiEncryption(key_type, false);
             if (imsiEncryptionInfo == null) {
-                Log.d(LOG_TAG, "Key not found for: " + key_type);
+                logd("Key not found for: " + key_type);
                 return true;
             } else if (imsiEncryptionInfo.getCarrierId() == TelephonyManager.UNKNOWN_CARRIER_ID) {
-                Log.d(LOG_TAG, "carrier key is unknown carrier, so prefer to reDownload");
+                logd("carrier key is unknown carrier, so prefer to reDownload");
                 mDeleteOldKeyAfterDownload = true;
                 return true;
             }
             Date imsiDate = imsiEncryptionInfo.getExpirationTime();
             long timeToExpire = imsiDate.getTime() - System.currentTimeMillis();
-            return (timeToExpire < START_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS) ? true : false;
+            return timeToExpire < START_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS;
         }
         return false;
     }
 
     private boolean downloadKey() {
-        Log.d(LOG_TAG, "starting download from: " + mURL);
-        String mccMnc = getSimOperator();
-        int carrierId = getSimCarrierId();
-        if (!TextUtils.isEmpty(mccMnc) || carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
-            Log.d(LOG_TAG, "downloading key for mccmnc : " + mccMnc + ", carrierId : "
-                    + carrierId);
+        logd("starting download from: " + mURL);
+        String mccMnc = null;
+        int carrierId = -1;
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            if (TextUtils.isEmpty(mMccMncForDownload)
+                    || mCarrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
+                loge("mccmnc or carrierId is UnKnown");
+                return false;
+            }
         } else {
-            Log.e(LOG_TAG, "mccmnc or carrierId is UnKnown");
-            return false;
+            mccMnc = getSimOperator();
+            carrierId = getSimCarrierId();
+            if (!TextUtils.isEmpty(mccMnc) || carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
+                Log.d(LOG_TAG, "downloading key for mccmnc : " + mccMnc + ", carrierId : "
+                        + carrierId);
+            } else {
+                Log.e(LOG_TAG, "mccmnc or carrierId is UnKnown");
+                return false;
+            }
         }
+
         try {
             // register the broadcast receiver to listen for download complete
             IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
@@ -641,15 +776,16 @@
             request.setAllowedOverMetered(mAllowedOverMeteredNetwork);
             request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
             request.addRequestHeader("Accept-Encoding", "gzip");
-            Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request);
-
-            Log.d(LOG_TAG, "saving values mccmnc: " + mccMnc + ", downloadId: "
-                    + carrierKeyDownloadRequestId + ", carrierId: " + carrierId);
-            mMccMncForDownload = mccMnc;
-            mCarrierId = carrierId;
+            long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request);
+            if (!Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+                mMccMncForDownload = mccMnc;
+                mCarrierId = carrierId;
+            }
             mDownloadId = carrierKeyDownloadRequestId;
+            logd("saving values mccmnc: " + mMccMncForDownload + ", downloadId: "
+                    + carrierKeyDownloadRequestId + ", carrierId: " + mCarrierId);
         } catch (Exception e) {
-            Log.e(LOG_TAG, "exception trying to download key from url: " + mURL + ", Exception = "
+            loge( "exception trying to download key from url: " + mURL + ",  Exception = "
                     + e.getMessage());
             return false;
         }
@@ -667,7 +803,7 @@
         CertificateFactory cf = CertificateFactory.getInstance("X.509");
         X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
         Pair<PublicKey, Long> keyInformation =
-                new Pair(cert.getPublicKey(), cert.getNotAfter().getTime());
+                new Pair<>(cert.getPublicKey(), cert.getNotAfter().getTime());
         return keyInformation;
     }
 
@@ -699,4 +835,59 @@
                 cert.indexOf(CERT_BEGIN_STRING),
                 cert.indexOf(CERT_END_STRING) + CERT_END_STRING.length());
     }
+
+    /**
+     * Registering the callback to listen on data connection availability.
+     *
+     * @param slotId Sim slotIndex that tries to download the key.
+     */
+    private void registerDefaultNetworkCb(int slotId) {
+        logd("RegisterDefaultNetworkCb for slotId = " + slotId);
+        if (mDefaultNetworkCallback == null
+                && slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            mDefaultNetworkCallback = new DefaultNetworkCallback(slotId);
+            mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
+        }
+    }
+
+    /**
+     * Unregister the data connection monitor listener.
+     */
+    private void unregisterDefaultNetworkCb(int slotId) {
+        logd("unregisterDefaultNetworkCb for slotId = " + slotId);
+        if (mDefaultNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
+            mDefaultNetworkCallback = null;
+        }
+    }
+
+    final class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
+        final int mSlotIndex;
+
+        public DefaultNetworkCallback(int slotId) {
+            mSlotIndex = slotId;
+        }
+
+        /** Called when the framework connects and has declared a new network ready for use. */
+        @Override
+        public void onAvailable(@NonNull Network network) {
+            logd("Data network connected, slotId = " + mSlotIndex);
+            if (mConnectivityManager.getActiveNetwork() != null && SubscriptionManager.getSlotIndex(
+                    mPhone.getSubId()) == mSlotIndex) {
+                mIsRequiredToHandleUnlock = false;
+                unregisterDefaultNetworkCb(mSlotIndex);
+                sendEmptyMessage(EVENT_NETWORK_AVAILABLE);
+            }
+        }
+    }
+
+    private void loge(String logStr) {
+        String TAG = LOG_TAG + " [" + mPhone.getPhoneId() + "]";
+        Log.e(TAG, logStr);
+    }
+
+    private void logd(String logStr) {
+        String TAG = LOG_TAG + " [" + mPhone.getPhoneId() + "]";
+        Log.d(TAG, logStr);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 1124780..eeba86b 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -539,7 +539,7 @@
         mContext.registerReceiver(mBroadcastReceiver, filter,
                 android.Manifest.permission.MODIFY_PHONE_STATE, null, Context.RECEIVER_EXPORTED);
 
-        mCDM = new CarrierKeyDownloadManager(this, mFeatureFlags);
+        mCDM = new CarrierKeyDownloadManager(this);
         mCIM = new CarrierInfoManager();
 
         mCi.registerForImeiMappingChanged(this, EVENT_IMEI_MAPPING_CHANGED, null);
@@ -2124,8 +2124,13 @@
 
     @Override
     public void deleteCarrierInfoForImsiEncryption(int carrierId) {
+        CarrierInfoManager.deleteCarrierInfoForImsiEncryption(mContext, getSubId(), carrierId);
+    }
+
+    @Override
+    public void deleteCarrierInfoForImsiEncryption(int carrierId, String simOperator) {
         CarrierInfoManager.deleteCarrierInfoForImsiEncryption(mContext, getSubId(),
-                carrierId);
+                carrierId, simOperator);
     }
 
     @Override
@@ -2448,7 +2453,7 @@
      */
     @Override
     public void setN1ModeEnabled(boolean enable, @Nullable Message result) {
-        if (mFeatureFlags.enableCarrierConfigN1Control()) {
+        if (mFeatureFlags.enableCarrierConfigN1ControlAttempt2()) {
             // This might be called by IMS on another thread, so to avoid the requirement to
             // lock, post it through the handler.
             post(() -> {
@@ -2489,7 +2494,7 @@
 
     /** Only called on the handler thread. */
     private void updateCarrierN1ModeSupported(@NonNull PersistableBundle b) {
-        if (!mFeatureFlags.enableCarrierConfigN1Control()) return;
+        if (!mFeatureFlags.enableCarrierConfigN1ControlAttempt2()) return;
 
         if (!CarrierConfigManager.isConfigForIdentifiedCarrier(b)) return;
 
@@ -3674,6 +3679,7 @@
             case EVENT_SUBSCRIPTIONS_CHANGED:
                 logd("EVENT_SUBSCRIPTIONS_CHANGED");
                 updateUsageSetting();
+                updateNullCipherNotifier();
                 break;
             case EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE:
                 logd("EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE");
@@ -3774,7 +3780,8 @@
                         && mNullCipherNotifier != null) {
                     ar = (AsyncResult) msg.obj;
                     SecurityAlgorithmUpdate update = (SecurityAlgorithmUpdate) ar.result;
-                    mNullCipherNotifier.onSecurityAlgorithmUpdate(mContext, getSubId(), update);
+                    mNullCipherNotifier.onSecurityAlgorithmUpdate(mContext, getPhoneId(),
+                            getSubId(), update);
                 }
                 break;
 
@@ -5449,6 +5456,25 @@
                 obtainMessage(EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE));
     }
 
+    /**
+     * Update the phoneId -> subId mapping of the null cipher notifier.
+     */
+    @VisibleForTesting
+    public void updateNullCipherNotifier() {
+        if (!mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
+            return;
+        }
+
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(getSubId());
+        boolean active = false;
+        if (subInfo != null) {
+            active = subInfo.isActive();
+        }
+        mNullCipherNotifier.setSubscriptionMapping(mContext, getPhoneId(),
+                active ? subInfo.getSubscriptionId() : -1);
+    }
+
     @Override
     public boolean isNullCipherAndIntegritySupported() {
         return mIsNullCipherAndIntegritySupported;
diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
index 4146c24..1eafa04 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -247,7 +247,8 @@
                         networkReasonCode,
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
-                        tracker.getInterval());
+                        tracker.getInterval(),
+                        mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
                 if (mPhone != null) {
                     TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                     if (telephonyAnalytics != null) {
@@ -664,7 +665,8 @@
                     SmsManager.RESULT_SYSTEM_ERROR,
                     tracker.mMessageId,
                     tracker.isFromDefaultSmsApplication(mContext),
-                    tracker.getInterval());
+                    tracker.getInterval(),
+                    mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index ebf7cfd..6858533 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -4025,6 +4025,15 @@
         return;
     }
 
+    /**
+     * Deletes all the keys for a given Carrier from the device keystore.
+     * @param carrierId : the carrier ID which needs to be matched in the delete query
+     * @param simOperator : MccMnc which needs to be matched in the delete query.
+     */
+    public void deleteCarrierInfoForImsiEncryption(int carrierId, String simOperator) {
+
+    }
+
     public int getCarrierId() {
         return TelephonyManager.UNKNOWN_CARRIER_ID;
     }
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 498535b..3296f2f 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -1063,7 +1063,8 @@
                     SmsManager.RESULT_ERROR_NONE,
                     tracker.mMessageId,
                     tracker.isFromDefaultSmsApplication(mContext),
-                    tracker.getInterval());
+                    tracker.getInterval(),
+                    mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
@@ -1113,7 +1114,8 @@
                         getNotInServiceError(ss),
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
-                        tracker.getInterval());
+                        tracker.getInterval(),
+                        mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
                 if (mPhone != null) {
                     TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                     if (telephonyAnalytics != null) {
@@ -1149,7 +1151,8 @@
                         errorCode,
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
-                        tracker.getInterval());
+                        tracker.getInterval(),
+                        mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
                 if (mPhone != null) {
                     TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                     if (telephonyAnalytics != null) {
@@ -1175,7 +1178,8 @@
                         errorCode,
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
-                        tracker.getInterval());
+                        tracker.getInterval(),
+                        mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
                 if (mPhone != null) {
                     TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                     if (telephonyAnalytics != null) {
@@ -2403,7 +2407,8 @@
                     error,
                     trackers[0].mMessageId,
                     trackers[0].isFromDefaultSmsApplication(mContext),
-                    trackers[0].getInterval());
+                    trackers[0].getInterval(),
+                    mTelephonyManager.isEmergencyNumber(trackers[0].mDestAddress));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
diff --git a/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java b/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java
index 29729c8..9ab52fb 100644
--- a/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java
+++ b/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java
@@ -41,7 +41,7 @@
                 .registerCallback(
                         new HandlerExecutor(new Handler(mHandlerThread.getLooper())),
                         state -> {
-                            updateFoldState(state);
+                            updateFoldState(state.getIdentifier());
                         });
     }
 
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index 856045f..d7f07c5 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -1055,7 +1055,8 @@
                 sms.carrierId,
                 sms.messageId,
                 sms.count,
-                sms.isManagedProfile);
+                sms.isManagedProfile,
+                sms.isNtn);
     }
 
     private static StatsEvent buildStatsEvent(OutgoingSms sms) {
@@ -1078,7 +1079,9 @@
                 sms.count,
                 sms.sendErrorCode,
                 sms.networkErrorCode,
-                sms.isManagedProfile);
+                sms.isManagedProfile,
+                sms.isEmergency,
+                sms.isNtn);
     }
 
     private static StatsEvent buildStatsEvent(DataCallSession dataCallSession) {
diff --git a/src/java/com/android/internal/telephony/metrics/SmsStats.java b/src/java/com/android/internal/telephony/metrics/SmsStats.java
index 949b72e..6f9a764 100644
--- a/src/java/com/android/internal/telephony/metrics/SmsStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SmsStats.java
@@ -57,6 +57,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
@@ -157,18 +158,18 @@
     /** Create a new atom when an outgoing SMS is sent. */
     public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs,
             @SmsManager.Result int sendErrorCode, long messageId, boolean isFromDefaultApp,
-            long intervalMillis) {
+            long intervalMillis, boolean isEmergency) {
         onOutgoingSms(isOverIms, is3gpp2, fallbackToCs, sendErrorCode, NO_ERROR_CODE,
-                messageId, isFromDefaultApp, intervalMillis);
+                messageId, isFromDefaultApp, intervalMillis, isEmergency);
     }
 
     /** Create a new atom when an outgoing SMS is sent. */
     public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs,
             @SmsManager.Result int sendErrorCode, int networkErrorCode, long messageId,
-            boolean isFromDefaultApp, long intervalMillis) {
+            boolean isFromDefaultApp, long intervalMillis, boolean isEmergency) {
         OutgoingSms proto =
                 getOutgoingDefaultProto(is3gpp2, isOverIms, messageId, isFromDefaultApp,
-                        intervalMillis);
+                        intervalMillis, isEmergency);
 
         // The field errorCode is used for up-to-Android-13 devices. From Android 14, sendErrorCode
         // and networkErrorCode will be used. The field errorCode will be deprecated when most
@@ -234,12 +235,13 @@
         proto.messageId = RANDOM.nextLong();
         proto.count = 1;
         proto.isManagedProfile = mPhone.isManagedProfile();
+        proto.isNtn = isNonTerrestrialNetwork();
         return proto;
     }
 
     /** Create a proto for a normal {@code OutgoingSms} with default values. */
     private OutgoingSms getOutgoingDefaultProto(boolean is3gpp2, boolean isOverIms,
-            long messageId, boolean isFromDefaultApp, long intervalMillis) {
+            long messageId, boolean isFromDefaultApp, long intervalMillis, boolean isEmergency) {
         OutgoingSms proto = new OutgoingSms();
         proto.smsFormat = getSmsFormat(is3gpp2);
         proto.smsTech = getSmsTech(isOverIms, is3gpp2);
@@ -260,6 +262,8 @@
         proto.intervalMillis = intervalMillis;
         proto.count = 1;
         proto.isManagedProfile = mPhone.isManagedProfile();
+        proto.isEmergency = isEmergency;
+        proto.isNtn = isNonTerrestrialNetwork();
         return proto;
     }
 
@@ -397,6 +401,20 @@
         return phone.getCarrierId();
     }
 
+    private boolean isNonTerrestrialNetwork() {
+        if (!Flags.carrierEnabledSatelliteFlag()) {
+            return false;
+        }
+
+        ServiceState ss = getServiceState();
+        if (ss != null) {
+            return ss.isUsingNonTerrestrialNetwork();
+        } else {
+            Rlog.e(TAG, "isNonTerrestrialNetwork(), ServiceState is null");
+            return false;
+        }
+    }
+
     private void loge(String format, Object... args) {
         Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args));
     }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java
index 877eaf1..6783e38 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramController.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java
@@ -35,6 +35,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 
@@ -59,6 +61,8 @@
     public static final int TIMEOUT_TYPE_DATAGRAM_WAIT_FOR_CONNECTED_STATE = 2;
     /** This type is used by CTS to override the time to wait for response of the send request */
     public static final int TIMEOUT_TYPE_WAIT_FOR_DATAGRAM_SENDING_RESPONSE = 3;
+    /** This type is used by CTS to override the time to datagram delay in demo mode */
+    public static final int TIMEOUT_TYPE_DATAGRAM_DELAY_IN_DEMO_MODE = 4;
     private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
     private static final boolean DEBUG = !"user".equals(Build.TYPE);
 
@@ -83,8 +87,8 @@
     private int mReceivePendingCount = 0;
     @GuardedBy("mLock")
     private int mReceiveErrorCode = SatelliteManager.SATELLITE_RESULT_SUCCESS;
-
-    private SatelliteDatagram mDemoModeDatagram;
+    @GuardedBy("mLock")
+    private final List<SatelliteDatagram> mDemoModeDatagramList;
     private boolean mIsDemoMode = false;
     private long mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT;
     private long mDatagramWaitTimeForConnectedState;
@@ -143,6 +147,7 @@
 
         mDatagramWaitTimeForConnectedState = getDatagramWaitForConnectedStateTimeoutMillis();
         mModemImageSwitchingDuration = getSatelliteModemImageSwitchingDurationMillis();
+        mDemoModeDatagramList = new ArrayList<>();
     }
 
     /**
@@ -208,7 +213,7 @@
     public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
             @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
             @NonNull Consumer<Integer> callback) {
-        setDemoModeDatagram(datagramType, datagram);
+        pushDemoModeDatagram(datagramType, datagram);
         mDatagramDispatcher.sendSatelliteDatagram(subId, datagramType, datagram,
                 needFullScreenPointingUI, callback);
     }
@@ -349,14 +354,25 @@
         mDatagramReceiver.setDemoMode(isDemoMode);
 
         if (!isDemoMode) {
-            mDemoModeDatagram = null;
+            synchronized (mLock) {
+                mDemoModeDatagramList.clear();
+            }
+            setDeviceAlignedWithSatellite(false);
         }
+        logd("setDemoMode: mIsDemoMode=" + mIsDemoMode);
     }
 
     /** Get the last sent datagram for demo mode */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public SatelliteDatagram getDemoModeDatagram() {
-        return mDemoModeDatagram;
+    public SatelliteDatagram popDemoModeDatagram() {
+        if (!mIsDemoMode) {
+            return null;
+        }
+
+        synchronized (mLock) {
+            logd("popDemoModeDatagram");
+            return mDemoModeDatagramList.size() > 0 ? mDemoModeDatagramList.remove(0) : null;
+        }
     }
 
     /**
@@ -365,10 +381,13 @@
      * @param datagram datagram The last datagram saved when sendSatelliteDatagramForDemo is called
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    protected void setDemoModeDatagram(@SatelliteManager.DatagramType int datagramType,
+    protected void pushDemoModeDatagram(@SatelliteManager.DatagramType int datagramType,
             SatelliteDatagram datagram) {
-        if (mIsDemoMode &&  datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
-            mDemoModeDatagram = datagram;
+        if (mIsDemoMode && datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+            synchronized (mLock) {
+                mDemoModeDatagramList.add(datagram);
+                logd("pushDemoModeDatagram size=" + mDemoModeDatagramList.size());
+            }
         }
     }
 
@@ -419,6 +438,8 @@
             }
         } else if (timeoutType == TIMEOUT_TYPE_WAIT_FOR_DATAGRAM_SENDING_RESPONSE) {
             mDatagramDispatcher.setWaitTimeForDatagramSendingResponse(reset, timeoutMillis);
+        } else if (timeoutType == TIMEOUT_TYPE_DATAGRAM_DELAY_IN_DEMO_MODE) {
+            mDatagramDispatcher.setTimeoutDatagramDelayInDemoMode(reset, timeoutMillis);
         } else {
             loge("Invalid timeout type " + timeoutType);
             return false;
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
index 5cac1dd..83caf2e 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
@@ -45,6 +45,7 @@
 import java.util.LinkedHashMap;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
@@ -61,7 +62,8 @@
     private static final int EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT = 4;
     private static final int EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT = 5;
     private static final int EVENT_ABORT_SENDING_SATELLITE_DATAGRAMS_DONE = 6;
-
+    private static final int CMD_POLL_PENDING_SATELLITE_DATAGRAMS = 7;
+    private static final Long TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE = TimeUnit.SECONDS.toMillis(10);
     @NonNull private static DatagramDispatcher sInstance;
     @NonNull private final Context mContext;
     @NonNull private final DatagramController mDatagramController;
@@ -76,6 +78,7 @@
     private AtomicBoolean mShouldSendDatagramToModemInDemoMode = null;
 
     private final Object mLock = new Object();
+    private long mDemoTimeoutDuration = TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE;
 
     @GuardedBy("mLock")
     private boolean mSendingDatagramInProgress;
@@ -196,7 +199,9 @@
 
         switch(msg.what) {
             case CMD_SEND_SATELLITE_DATAGRAM: {
-                logd("CMD_SEND_SATELLITE_DATAGRAM");
+                logd("CMD_SEND_SATELLITE_DATAGRAM mIsDemoMode=" + mIsDemoMode
+                        + ", shouldSendDatagramToModemInDemoMode="
+                        + shouldSendDatagramToModemInDemoMode());
                 request = (DatagramDispatcherHandlerRequest) msg.obj;
                 SendSatelliteDatagramArgument argument =
                         (SendSatelliteDatagramArgument) request.argument;
@@ -205,7 +210,7 @@
                 synchronized (mLock) {
                     if (mIsDemoMode && !shouldSendDatagramToModemInDemoMode()) {
                         AsyncResult.forMessage(onCompleted, SATELLITE_RESULT_SUCCESS, null);
-                        onCompleted.sendToTarget();
+                        sendMessageDelayed(onCompleted, getDemoTimeoutDuration());
                     } else {
                         SatelliteModemInterface.getInstance().sendSatelliteDatagram(
                                 argument.datagram,
@@ -266,7 +271,11 @@
                                 getPendingDatagramCount(), error);
                         mControllerMetricsStats.reportOutgoingDatagramSuccessCount(
                                 argument.datagramType);
-
+                        if (mIsDemoMode) {
+                            sendMessageDelayed(
+                                    obtainMessage(CMD_POLL_PENDING_SATELLITE_DATAGRAMS, request),
+                                    getDemoTimeoutDuration());
+                        }
                         if (getPendingDatagramCount() > 0) {
                             // Send response for current datagram
                             argument.callback.accept(error);
@@ -314,6 +323,15 @@
                 handleEventDatagramWaitForConnectedStateTimedOut();
                 break;
 
+            case CMD_POLL_PENDING_SATELLITE_DATAGRAMS:
+                if (mIsDemoMode) {
+                    request = (DatagramDispatcherHandlerRequest) msg.obj;
+                    SendSatelliteDatagramArgument argument =
+                            (SendSatelliteDatagramArgument) request.argument;
+                    pollPendingSatelliteDatagrams(argument.subId);
+                }
+                break;
+
             default:
                 logw("DatagramDispatcherHandler: unexpected message code: " + msg.what);
                 break;
@@ -392,15 +410,15 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected void setDemoMode(boolean isDemoMode) {
         mIsDemoMode = isDemoMode;
+        logd("setDemoMode: mIsDemoMode=" + mIsDemoMode);
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected void setDeviceAlignedWithSatellite(boolean isAligned) {
-        if (mIsDemoMode) {
-            synchronized (mLock) {
-                mIsAligned = isAligned;
-                if (isAligned) handleEventSatelliteAligned();
-            }
+        synchronized (mLock) {
+            mIsAligned = isAligned;
+            logd("setDeviceAlignedWithSatellite: " + mIsAligned);
+            if (isAligned && mIsDemoMode) handleEventSatelliteAligned();
         }
     }
 
@@ -435,12 +453,15 @@
                 mSendSatelliteDatagramRequest = null;
                 AsyncResult.forMessage(message, null, null);
                 message.sendToTarget();
+                logd("handleEventSatelliteAligned: EVENT_SEND_SATELLITE_DATAGRAM_DONE");
             }
         }
     }
 
     private void handleEventSatelliteAlignedTimeout(
             @NonNull DatagramDispatcherHandlerRequest request) {
+        logd("handleEventSatelliteAlignedTimeout");
+        mSendSatelliteDatagramRequest = null;
         SatelliteManager.SatelliteException exception =
                 new SatelliteManager.SatelliteException(
                         SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
@@ -608,6 +629,7 @@
 
     @GuardedBy("mLock")
     private void cleanUpResources() {
+        logd("cleanUpResources");
         mSendingDatagramInProgress = false;
         if (getPendingDatagramCount() > 0) {
             mDatagramController.updateSendStatus(
@@ -789,6 +811,36 @@
         }
     }
 
+    private void pollPendingSatelliteDatagrams(int subId) {
+        logd("pollPendingSatelliteDatagrams");
+        Consumer<Integer> internalCallback = new Consumer<Integer>() {
+            @Override
+            public void accept(Integer result) {
+                logd("pollPendingSatelliteDatagrams result: " + result);
+            }
+        };
+        mDatagramController.pollPendingSatelliteDatagrams(subId, internalCallback);
+    }
+
+    long getDemoTimeoutDuration() {
+        return mDemoTimeoutDuration;
+    }
+
+    /**
+     * This API is used by CTS tests to override the mDemoTimeoutDuration.
+     */
+    void setTimeoutDatagramDelayInDemoMode(boolean reset, long timeoutMillis) {
+        if (!mIsDemoMode) {
+            return;
+        }
+        if (reset) {
+            mDemoTimeoutDuration = TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE;
+        } else {
+            mDemoTimeoutDuration = timeoutMillis;
+        }
+        logd("setTimeoutDatagramDelayInDemoMode " + mDemoTimeoutDuration + " reset=" + reset);
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
index c267fd7..6c52a8c 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
@@ -439,7 +439,7 @@
                         "pollPendingSatelliteDatagrams");
 
                 if (mIsDemoMode && error == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
-                    SatelliteDatagram datagram = mDatagramController.getDemoModeDatagram();
+                    SatelliteDatagram datagram = mDatagramController.popDemoModeDatagram();
                     final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(
                             request.subId, mContext);
                     SatelliteDatagramListenerHandler listenerHandler =
@@ -744,11 +744,10 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected void setDeviceAlignedWithSatellite(boolean isAligned) {
-        if (mIsDemoMode) {
-            synchronized (mLock) {
-                mIsAligned = isAligned;
-                if (isAligned) handleEventSatelliteAligned();
-            }
+        synchronized (mLock) {
+            mIsAligned = isAligned;
+            logd("setDeviceAlignedWithSatellite: " + mIsAligned);
+            if (isAligned && mIsDemoMode) handleEventSatelliteAligned();
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 92d9372..b7f37d0 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -3219,6 +3219,7 @@
     private void setDemoModeEnabled(boolean enabled) {
         mIsDemoModeEnabled = enabled;
         mDatagramController.setDemoMode(mIsDemoModeEnabled);
+        logd("setDemoModeEnabled: mIsDemoModeEnabled=" + mIsDemoModeEnabled);
     }
 
     private boolean isMockModemAllowed() {
diff --git a/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java b/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
index 34f26e3..605b197 100644
--- a/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
+++ b/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
@@ -25,6 +25,8 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
 import android.safetycenter.SafetyCenterManager;
 import android.safetycenter.SafetyEvent;
 import android.safetycenter.SafetySourceData;
@@ -62,10 +64,6 @@
 
     private static final Intent CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT =
             new Intent("android.settings.CELLULAR_NETWORK_SECURITY");
-    // TODO(b/321999913): direct to a help page URL e.g.
-    //                    new Intent(Intent.ACTION_VIEW, Uri.parse("https://..."));
-    private static final Intent LEARN_MORE_INTENT = new Intent();
-
     static final int NULL_CIPHER_STATE_ENCRYPTED = 0;
     static final int NULL_CIPHER_STATE_NOTIFY_ENCRYPTED = 1;
     static final int NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED = 2;
@@ -123,6 +121,13 @@
     }
 
     /**
+     * Clears issue state for the identified subscription
+     */
+    public synchronized  void clearNullCipherState(Context context, int subId) {
+        mNullCipherStates.remove(subId);
+        updateSafetyCenter(context);
+    }
+    /**
      * Enables or disables the identifier disclosure issue and clears any current issues if the
      * enable state is changed.
      */
@@ -216,7 +221,8 @@
                 builder = new SafetySourceIssue.Builder(
                         NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID + "_" + subId,
                         context.getString(
-                            R.string.scNullCipherIssueNonEncryptedTitle, subInfo.getDisplayName()),
+                                R.string.scNullCipherIssueNonEncryptedTitle,
+                                subInfo.getDisplayName()),
                         context.getString(R.string.scNullCipherIssueNonEncryptedSummary),
                         SEVERITY_LEVEL_RECOMMENDATION,
                         NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID);
@@ -225,7 +231,7 @@
                 builder = new SafetySourceIssue.Builder(
                         NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID + "_" + subId,
                         context.getString(
-                            R.string.scNullCipherIssueEncryptedTitle, subInfo.getDisplayName()),
+                                R.string.scNullCipherIssueEncryptedTitle, subInfo.getDisplayName()),
                         context.getString(R.string.scNullCipherIssueEncryptedSummary),
                         SEVERITY_LEVEL_INFORMATION,
                         NULL_CIPHER_ISSUE_ENCRYPTED_ID);
@@ -233,26 +239,31 @@
             default:
                 throw new AssertionError();
         }
-
-        return Optional.of(
-                builder
-                    .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
-                    .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
-                    .addAction(
+        builder
+                .setNotificationBehavior(
+                        SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
+                .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
+                .addAction(
                         new SafetySourceIssue.Action.Builder(
                                 NULL_CIPHER_ACTION_SETTINGS_ID,
                                 context.getString(R.string.scNullCipherIssueActionSettings),
                                 mSafetyCenterManagerWrapper.getActivityPendingIntent(
                                         context, CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
-                            .build())
-                    .addAction(
-                        new SafetySourceIssue.Action.Builder(
-                                NULL_CIPHER_ACTION_LEARN_MORE_ID,
-                                context.getString(R.string.scNullCipherIssueActionLearnMore),
-                                mSafetyCenterManagerWrapper.getActivityPendingIntent(
-                                        context, LEARN_MORE_INTENT))
-                            .build())
-                    .build());
+                                .build());
+
+        Intent learnMoreIntent = getLearnMoreIntent(context);
+        if (learnMoreIntent != null) {
+            builder.addAction(
+                    new SafetySourceIssue.Action.Builder(
+                            NULL_CIPHER_ACTION_LEARN_MORE_ID,
+                            context.getString(
+                                    R.string.scNullCipherIssueActionLearnMore),
+                            mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                                    context, learnMoreIntent))
+                            .build());
+        }
+
+        return Optional.of(builder.build());
     }
 
     /** Builds the identity disclosure issue if it's enabled and there are disclosures to report. */
@@ -264,7 +275,8 @@
 
         SubscriptionInfoInternal subInfo =
                 mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
-        return Optional.of(
+
+        SafetySourceIssue.Builder builder =
                 new SafetySourceIssue.Builder(
                         IDENTIFIER_DISCLOSURE_ISSUE_ID + "_" + subId,
                         context.getString(R.string.scIdentifierDisclosureIssueTitle),
@@ -276,23 +288,51 @@
                                 subInfo.getDisplayName()),
                         SEVERITY_LEVEL_RECOMMENDATION,
                         IDENTIFIER_DISCLOSURE_ISSUE_ID)
-                    .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
-                    .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
-                    .addAction(
-                        new SafetySourceIssue.Action.Builder(
-                                NULL_CIPHER_ACTION_SETTINGS_ID,
-                                context.getString(R.string.scNullCipherIssueActionSettings),
-                                mSafetyCenterManagerWrapper.getActivityPendingIntent(
-                                        context, CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
-                            .build())
-                    .addAction(
-                        new SafetySourceIssue.Action.Builder(
-                                NULL_CIPHER_ACTION_LEARN_MORE_ID,
-                                context.getString(R.string.scNullCipherIssueActionLearnMore),
-                                mSafetyCenterManagerWrapper.getActivityPendingIntent(
-                                        context, LEARN_MORE_INTENT))
-                            .build())
-                .build());
+                        .setNotificationBehavior(
+                                SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
+                        .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
+                        .addAction(
+                                new SafetySourceIssue.Action.Builder(
+                                        NULL_CIPHER_ACTION_SETTINGS_ID,
+                                        context.getString(
+                                                R.string.scNullCipherIssueActionSettings),
+                                        mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                                                context,
+                                                CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
+                                        .build());
+
+        Intent learnMoreIntent = getLearnMoreIntent(context);
+        if (learnMoreIntent != null) {
+            builder.addAction(
+                    new SafetySourceIssue.Action.Builder(
+                            NULL_CIPHER_ACTION_LEARN_MORE_ID,
+                            context.getString(R.string.scNullCipherIssueActionLearnMore),
+                            mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                                    context, learnMoreIntent)).build()
+            );
+        }
+
+        return Optional.of(builder.build());
+    }
+
+    /**
+     * Return Intent for learn more action, or null if resource associated with the Intent
+     * uri is
+     * missing or empty.
+     */
+    private Intent getLearnMoreIntent(Context context) {
+        String learnMoreUri;
+        try {
+            learnMoreUri = context.getString(R.string.scCellularNetworkSecurityLearnMore);
+        } catch (Resources.NotFoundException e) {
+            return null;
+        }
+
+        if (learnMoreUri.isEmpty()) {
+            return null;
+        }
+
+        return new Intent(Intent.ACTION_VIEW, Uri.parse(learnMoreUri));
     }
 
     /** A wrapper around {@link SafetyCenterManager} that can be instrumented in tests. */
@@ -337,7 +377,7 @@
         private IdentifierDisclosure(int count, Instant start, Instant end) {
             mDisclosureCount = count;
             mWindowStart = start;
-            mWindowEnd  = end;
+            mWindowEnd = end;
         }
 
         private int getDisclosureCount() {
diff --git a/src/java/com/android/internal/telephony/security/NullCipherNotifier.java b/src/java/com/android/internal/telephony/security/NullCipherNotifier.java
index 3ece701..e13c5b0 100644
--- a/src/java/com/android/internal/telephony/security/NullCipherNotifier.java
+++ b/src/java/com/android/internal/telephony/security/NullCipherNotifier.java
@@ -56,6 +56,7 @@
 
     private final CellularNetworkSecuritySafetySource mSafetySource;
     private final HashMap<Integer, SubscriptionState> mSubscriptionState = new HashMap<>();
+    private final HashMap<Integer, Integer> mActiveSubscriptions = new HashMap<>();
 
     private final Object mEnabledLock = new Object();
     @GuardedBy("mEnabledLock")
@@ -90,31 +91,78 @@
      * Adds a security algorithm update. If appropriate, this will trigger a user notification.
      */
     public void onSecurityAlgorithmUpdate(
-            Context context, int subId, SecurityAlgorithmUpdate update) {
+            Context context, int phoneId, int subId, SecurityAlgorithmUpdate update) {
         Rlog.d(TAG, "Security algorithm update: subId = " + subId + " " + update);
 
         if (shouldIgnoreUpdate(update)) {
             return;
         }
 
+        if (!isEnabled()) {
+            Rlog.i(TAG, "Ignoring onSecurityAlgorithmUpdate. Notifier is disabled.");
+            return;
+        }
+
         try {
             mSerializedWorkQueue.execute(() -> {
-                SubscriptionState subState = mSubscriptionState.get(subId);
-                if (subState == null) {
-                    subState = new SubscriptionState();
-                    mSubscriptionState.put(subId, subState);
-                }
+                try {
+                    maybeUpdateSubscriptionMapping(context, phoneId, subId);
+                    SubscriptionState subState = mSubscriptionState.get(subId);
+                    if (subState == null) {
+                        subState = new SubscriptionState();
+                        mSubscriptionState.put(subId, subState);
+                    }
 
-                @CellularNetworkSecuritySafetySource.NullCipherState int nullCipherState =
-                        subState.update(update);
-                mSafetySource.setNullCipherState(context, subId, nullCipherState);
+                    @CellularNetworkSecuritySafetySource.NullCipherState int nullCipherState =
+                            subState.update(update);
+                    mSafetySource.setNullCipherState(context, subId, nullCipherState);
+                } catch (Throwable t) {
+                    Rlog.e(TAG, "Failed to execute onSecurityAlgorithmUpdate " + t.getMessage());
+                }
             });
         } catch (RejectedExecutionException e) {
-            Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage());
+            Rlog.e(TAG, "Failed to schedule onSecurityAlgorithmUpdate: " + e.getMessage());
         }
     }
 
     /**
+     * Set or update the current phoneId to subId mapping. When a new subId is mapped to a phoneId,
+     * we update the safety source to clear state of the old subId.
+     */
+    public void setSubscriptionMapping(Context context, int phoneId, int subId) {
+
+        if (!isEnabled()) {
+            Rlog.i(TAG, "Ignoring setSubscriptionMapping. Notifier is disabled.");
+        }
+
+        try {
+            mSerializedWorkQueue.execute(() -> {
+                try {
+                    maybeUpdateSubscriptionMapping(context, phoneId, subId);
+                } catch (Throwable t) {
+                    Rlog.e(TAG, "Failed to update subId mapping. phoneId: " + phoneId + " subId: "
+                            + subId + ". " + t.getMessage());
+                }
+            });
+
+        } catch (RejectedExecutionException e) {
+            Rlog.e(TAG, "Failed to schedule setSubscriptionMapping: " + e.getMessage());
+        }
+    }
+
+    private void maybeUpdateSubscriptionMapping(Context context, int phoneId, int subId) {
+        final Integer oldSubId = mActiveSubscriptions.put(phoneId, subId);
+        if (oldSubId == null || oldSubId == subId) {
+            return;
+        }
+
+        // Our subId was updated for this phone, we should clear this subId's state.
+        mSubscriptionState.remove(oldSubId);
+        mSafetySource.clearNullCipherState(context, oldSubId);
+    }
+
+
+    /**
      * Enables null cipher notification; {@code onSecurityAlgorithmUpdate} will start handling
      * security algorithm updates and send notifications to the user when required.
      */
@@ -285,7 +333,7 @@
     /** The state of security algorithms for a network connection. */
     private static final class ConnectionState {
         private static final ConnectionState UNKNOWN =
-                 new ConnectionState(SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_UNKNOWN);
+                new ConnectionState(SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_UNKNOWN);
 
         private final @SecurityAlgorithm int mEncryption;
         private final @SecurityAlgorithm int mIntegrity;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
index 2a66a5f..e06e4fe 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
@@ -23,6 +23,7 @@
 import android.os.Bundle;
 import android.os.CarrierAssociatedAppEntry;
 import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
@@ -34,9 +35,12 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.telephony.flags.Flags;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
@@ -59,6 +63,9 @@
     private static final int USER_ID = 12345;
     private static final String CALLING_PACKAGE = "phone";
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     // Mocked classes
     private Context mContext;
     private PackageManager mPackageManager;
@@ -79,6 +86,7 @@
 
     @Before
     public void setUp() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_HIDE_PREINSTALLED_CARRIER_APP_AT_MOST_ONCE);
         System.setProperty("dexmaker.dexcache",
                 InstrumentationRegistry.getTargetContext().getCacheDir().getPath());
         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
@@ -113,6 +121,7 @@
     public void testDisableCarrierAppsUntilPrivileged_EmptyList() {
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, new ArraySet<>(), ASSOCIATED_APPS, mContext);
+
         Mockito.verifyNoMoreInteractions(mPackageManager, mTelephonyManager);
     }
 
@@ -125,9 +134,11 @@
                         | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(null);
         ArraySet<String> systemCarrierAppsDisabledUntilUsed = new ArraySet<>();
         systemCarrierAppsDisabledUntilUsed.add("com.example.missing.app");
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, systemCarrierAppsDisabledUntilUsed, ASSOCIATED_APPS,
                 mContext);
+
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN));
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -147,8 +158,10 @@
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN));
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -173,15 +186,16 @@
         Mockito.when(mPackageManager
                 .getApplicationEnabledSetting(Mockito.anyString()))
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
-
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -199,15 +213,16 @@
         Mockito.when(mPackageManager
                 .getApplicationEnabledSetting(Mockito.anyString()))
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
-
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -231,8 +246,10 @@
                         | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -255,11 +272,12 @@
         Mockito.when(mPackageManager
                 .getApplicationEnabledSetting(Mockito.anyString()))
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
-
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -287,8 +305,10 @@
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
@@ -329,8 +349,10 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -371,8 +393,10 @@
                 .thenReturn(null);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
@@ -412,8 +436,10 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -440,8 +466,10 @@
                 .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -465,9 +493,11 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -476,7 +506,7 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Configured app has no privileges, and was uninstalled - should do nothing. */
+    /** Configured app has no privileges, and was explicitly disabled - should do nothing. */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Disabled() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -492,8 +522,10 @@
                 .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -502,7 +534,7 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Telephony is not initialized, and app was uninstalled - should do nothing. */
+    /** Telephony is not initialized, and app was explicitly disabled - should do nothing. */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NullPrivileges_Disabled() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -516,9 +548,11 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -527,7 +561,7 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Configured app has no privileges, and is explicitly installed - should do nothing. */
+    /** Configured app has no privileges, and was explicitly enabled - should do nothing. */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Enabled() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -543,8 +577,10 @@
                 .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -553,7 +589,7 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Telephony is not initialized, and app is explicitly installed - should do nothing. */
+    /** Telephony is not initialized, and app was explicitly enabled - should do nothing. */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NullPrivileges_Enabled() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -567,9 +603,11 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -578,7 +616,8 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Configured /data app has no privileges - should do nothing. */
+    /** Configured app has been installed as an update in /data and has no privileges
+     *  - should do nothing. */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_UpdatedApp() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -595,8 +634,10 @@
                 .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -620,9 +661,11 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -632,7 +675,8 @@
     }
 
     /**
-     * Configured app has no privileges, and is in the default state - should uninstalled.
+     * Configured app has no privileges, is in the default state and it was never uninstalled before
+     *  - should uninstalled.
      * Associated app is installed and should not be touched.
      */
     @Test @SmallTest
@@ -660,8 +704,10 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -675,8 +721,8 @@
     }
 
     /**
-     * Configured app has no privileges, and is in the default state along with associated app -
-     * should uninstall both.
+     * Configured app has no privileges, is in the default state along with associated app and never
+     * unstalled before - should uninstall both.
      */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Associated_Default()
@@ -703,8 +749,10 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -717,7 +765,7 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has already occurred - should only uninstall configured app.
+     * disabling has already occurred - should uninstall nothing.
      */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Associated_Default_HandledSdk()
@@ -747,15 +795,17 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Different associated app SDK than usual.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS,
                 makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT), mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
-        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_INSTALLED);
@@ -765,7 +815,7 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has already occurred - should only uninstall configured app.
+     * disabling has already occurred - should uninstall nothing.
      */
     @Test @SmallTest
     public void testDCAUP_NoPrivileges_Associated_Default_HandledSdk_AssociatedSdkUnspecified()
@@ -795,14 +845,16 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Using SDK_UNSPECIFIED for the associated app's addedInSdk.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
-        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_INSTALLED);
@@ -812,15 +864,14 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has not yet occurred on this SDK level - should uninstall both since the associated
-     * app's SDK matches.
+     * disabling has not yet occurred on any SDK level - should uninstall both since disabling never
+     * occurred before and the associated app's SDK matches.
      */
     @Test @SmallTest
     public void testDCAUP_NoPrivileges_Associated_Default_NewSdk_AssociatedSdkCurrent()
             throws Exception {
         Settings.Secure.putIntForUser(
-                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
-                Build.VERSION.SDK_INT - 1, USER_ID);
+                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, USER_ID);
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
@@ -843,10 +894,12 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Different associated app SDK than usual.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS,
                 makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT), mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -861,7 +914,7 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has not yet occurred on the current SDK - should only uninstall configured app
+     * disabling has not yet occurred on the current SDK - should uninstall nothing
      * since the associated app's SDK isn't specified but we've already run at least once.
      */
     @Test @SmallTest
@@ -892,14 +945,16 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Using SDK_UNSPECIFIED for the associated app's addedInSdk.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
-        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_INSTALLED);
@@ -909,7 +964,7 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has not yet occurred on the current SDK - should only uninstall configured app
+     * disabling has not yet occurred on the current SDK - should uninstall nothing
      * since the associated app's SDK doesn't match.
      */
     @Test @SmallTest
@@ -940,16 +995,18 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Different associated app SDK than usual.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS,
                 makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT - 1),
                 mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
-        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_INSTALLED);
@@ -959,8 +1016,8 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has not yet occurred on this SDK level - should uninstall both since the associated
-     * app's SDK is newer than the last evaluation.
+     * disabling has not yet occurred on this SDK level - should uninstall only associated
+     * app since the associated app's SDK is newer than the last evaluation.
      *
      * While this case is expected to feel somewhat strange, it effectively simulates skipping a
      * whole SDK level in a single OTA. For example, the device is on P. A new associated app is
@@ -996,16 +1053,18 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Different associated app SDK than usual.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS,
                 makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT - 1),
                 mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
-        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_INSTALLED);
@@ -1043,10 +1102,12 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Different associated app SDK than usual.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS,
                 makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT), mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -1089,9 +1150,11 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Using SDK_UNSPECIFIED for the associated app's addedInSdk.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -1104,7 +1167,10 @@
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
     }
 
-    /** Telephony is not initialized, and app is in the default state - should uninstall it. */
+    /**
+     * Telephony is not initialized, and app is in the default state and never uninstall before
+     * - should uninstall it.
+     **/
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NullPrivileges_Default() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -1118,16 +1184,51 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
     }
 
-    /** Configured app has no privileges, and is disabled until used or not installed - should do
+    /**
+     * Telephony is not initialized, and app is in the default state but uninstall before
+     * - should not uninstall again.
+     **/
+    @Test @SmallTest
+    public void testDisableCarrierAppsUntilPrivileged_NullPrivileges_Default_alreadyUninstalled()
+            throws Exception {
+        Settings.Secure.putIntForUser(
+                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
+                Build.VERSION.SDK_INT, USER_ID);
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = CARRIER_APP;
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
+                null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
+                ASSOCIATED_APPS, mContext);
+
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+    }
+
+    /**
+     * Configured app has no privileges, and is disabled until used or not installed - should do
      *  nothing.
      **/
     @Test @SmallTest
@@ -1146,8 +1247,10 @@
                 .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -1156,8 +1259,9 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Telephony is not initialized, and app is disabled until used or not installed - should do
-     *  nothing.
+    /**
+     * Telephony is not initialized, and app is disabled until used or not installed - should do
+     * nothing.
      **/
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NullPrivileges_DisabledUntilUsed()
@@ -1173,9 +1277,11 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
index 07482e0..e60e95b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.times;
@@ -31,6 +32,8 @@
 import android.app.DownloadManager;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ImsiEncryptionInfo;
@@ -38,18 +41,18 @@
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.text.TextUtils;
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.telephony.flags.FeatureFlags;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
 import org.mockito.Mockito;
 
 import java.security.PublicKey;
@@ -91,7 +94,7 @@
                     + "\"public-key\": \"" + CERT + "\"}]}";
 
     private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
-    private FeatureFlags mFeatureFlags;
+
     @Before
     public void setUp() throws Exception {
         logd("CarrierActionAgentTest +Setup!");
@@ -99,15 +102,18 @@
         mBundle = mContextFixture.getCarrierConfigBundle();
         when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(mBundle);
         when(mUserManager.isUserUnlocked()).thenReturn(true);
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(false);
         // Capture listener to emulate the carrier config change notification used later
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
-        mFeatureFlags = Mockito.mock(FeatureFlags.class);
-        mCarrierKeyDM = new CarrierKeyDownloadManager(mPhone, mFeatureFlags);
+        mCarrierKeyDM = new CarrierKeyDownloadManager(mPhone);
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
         mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0);
-
+        mConnectivityManager = (ConnectivityManager) mPhone.getContext().getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        Network network = Mockito.mock(Network.class);
+        when(mConnectivityManager.getActiveNetwork()).thenReturn(network);
         processAllMessages();
         logd("CarrierActionAgentTest -Setup!");
     }
@@ -331,8 +337,8 @@
         expectedCal.add(Calendar.DATE, 1);
         String dateExpected = dt.format(expectedCal.getTime());
 
-        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
-        when(mTelephonyManager.getSimCarrierId()).thenReturn(1);
+        when(mPhone.getOperatorNumeric()).thenReturn("310260");
+        when(mPhone.getCarrierId()).thenReturn(1);
         Intent mIntent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
         mIntent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId);
         mContext.sendBroadcast(mIntent);
@@ -355,11 +361,11 @@
         bundle.putInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT, 3);
         bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
 
-        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
-        when(mTelephonyManager.getSimCarrierId()).thenReturn(1);
+        when(mPhone.getOperatorNumeric()).thenReturn("310260");
+        when(mPhone.getCarrierId()).thenReturn(1);
         mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+                1, TelephonyManager.UNKNOWN_CARRIER_ID);
         processAllMessages();
         assertEquals("310260", mCarrierKeyDM.mMccMncForDownload);
         assertEquals(1, mCarrierKeyDM.mCarrierId);
@@ -369,6 +375,7 @@
     @SmallTest
     public void testCarrierConfigChangedWithUserLocked() {
         when(mUserManager.isUserUnlocked()).thenReturn(false);
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
         CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
         int slotId = mPhone.getPhoneId();
@@ -376,14 +383,14 @@
         bundle.putInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT, 3);
         bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
 
-        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
-        when(mTelephonyManager.getSimCarrierId()).thenReturn(1);
+        when(mPhone.getOperatorNumeric()).thenReturn("310260");
+        when(mPhone.getCarrierId()).thenReturn(1);
         mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+                1, TelephonyManager.UNKNOWN_CARRIER_ID);
         processAllMessages();
-        assertNull(mCarrierKeyDM.mMccMncForDownload);
-        assertEquals(0, mCarrierKeyDM.mCarrierId);
+        assertEquals("310260", mCarrierKeyDM.mMccMncForDownload);
+        assertEquals(1, mCarrierKeyDM.mCarrierId);
     }
 
     @Test
@@ -391,6 +398,7 @@
     public void testUserLockedAfterCarrierConfigChanged() {
         // User is locked at beginning
         when(mUserManager.isUserUnlocked()).thenReturn(false);
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
         CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
         int slotId = mPhone.getPhoneId();
@@ -399,17 +407,18 @@
         bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
 
         // Carrier config change received
-        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
-        when(mTelephonyManager.getSimCarrierId()).thenReturn(1);
+        when(mPhone.getOperatorNumeric()).thenReturn("310260");
+        when(mPhone.getCarrierId()).thenReturn(1);
         mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+                1, TelephonyManager.UNKNOWN_CARRIER_ID);
         processAllMessages();
 
         // User unlocked event received
-        Intent mIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
+        Intent mIntent = new Intent(Intent.ACTION_USER_PRESENT);
         mContext.sendBroadcast(mIntent);
         when(mUserManager.isUserUnlocked()).thenReturn(true);
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(false);
         processAllMessages();
 
         assertEquals("310260", mCarrierKeyDM.mMccMncForDownload);
@@ -432,11 +441,11 @@
 
         mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+                1, TelephonyManager.UNKNOWN_CARRIER_ID);
         processAllMessages();
-        assertNull(mCarrierKeyDM.mMccMncForDownload);
+        assertTrue(TextUtils.isEmpty(mCarrierKeyDM.mMccMncForDownload));
 
-        verify(mPhone).deleteCarrierInfoForImsiEncryption(0);
+        verify(mPhone).deleteCarrierInfoForImsiEncryption(1, "");
     }
 
     /**
@@ -453,8 +462,8 @@
         bundle.putInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT, 3);
         bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
 
-        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
-        when(mTelephonyManager.getSimCarrierId()).thenReturn(1);
+        when(mPhone.getOperatorNumeric()).thenReturn("310260");
+        when(mPhone.getCarrierId()).thenReturn(1);
         Intent mIntent = new Intent("com.android.internal.telephony.carrier_key_download_alarm");
         mIntent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
         mContext.sendBroadcast(mIntent);
@@ -493,4 +502,4 @@
         assertEquals(CarrierKeyDownloadManager
                 .cleanCertString("Comments before" + CERT + "Comments after"), CERT);
     }
-}
+}
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index 5e9ff46..ba08f8b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -1519,7 +1519,7 @@
 
     @Test
     public void testNrCapabilityChanged_firstRequest_incompleteCarrierConfig_changeNeeded() {
-        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+        when(mFeatureFlags.enableCarrierConfigN1ControlAttempt2()).thenReturn(true);
 
         mPhoneUT.mCi = mMockCi;
         PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
@@ -1550,7 +1550,7 @@
 
     @Test
     public void testNrCapabilityChanged_firstRequest_noChangeNeeded() {
-        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+        when(mFeatureFlags.enableCarrierConfigN1ControlAttempt2()).thenReturn(true);
 
         mPhoneUT.mCi = mMockCi;
         PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
@@ -1574,7 +1574,7 @@
 
     @Test
     public void testNrCapabilityChanged_firstRequest_needsChange() {
-        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+        when(mFeatureFlags.enableCarrierConfigN1ControlAttempt2()).thenReturn(true);
 
         mPhoneUT.mCi = mMockCi;
         PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
@@ -1598,7 +1598,7 @@
 
     @Test
     public void testNrCapabilityChanged_CarrierConfigChanges() {
-        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+        when(mFeatureFlags.enableCarrierConfigN1ControlAttempt2()).thenReturn(true);
 
         // Initialize the inner cache and set the modem to N1 mode = enabled/true
         testNrCapabilityChanged_firstRequest_needsChange();
@@ -1620,7 +1620,7 @@
 
     @Test
     public void testNrCapabilityChanged_CarrierConfigChanges_ErrorResponse() {
-        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+        when(mFeatureFlags.enableCarrierConfigN1ControlAttempt2()).thenReturn(true);
 
         mPhoneUT.mCi = mMockCi;
         for (int i = 0; i < 2; i++) {
@@ -1646,7 +1646,7 @@
 
     @Test
     public void testNrCapabilityChanged_firstRequest_ImsChanges() {
-        when(mFeatureFlags.enableCarrierConfigN1Control()).thenReturn(true);
+        when(mFeatureFlags.enableCarrierConfigN1ControlAttempt2()).thenReturn(true);
 
         mPhoneUT.mCi = mMockCi;
         Message passthroughMessage = mTestHandler.obtainMessage(0xC0FFEE);
@@ -3019,7 +3019,63 @@
         processAllMessages();
 
         verify(mNullCipherNotifier, times(1))
-                .onSecurityAlgorithmUpdate(eq(mContext), eq(0), eq(update));
+                .onSecurityAlgorithmUpdate(eq(mContext), eq(0), eq(0), eq(update));
+    }
+
+    @Test
+    public void testUpdateNullCipherNotifier_flagDisabled() {
+        when(mFeatureFlags.enableModemCipherTransparencyUnsolEvents()).thenReturn(false);
+        Phone phoneUT = makeNewPhoneUT();
+        phoneUT.sendMessage(
+                mPhoneUT.obtainMessage(
+                        Phone.EVENT_SUBSCRIPTIONS_CHANGED,
+                        new AsyncResult(null, null, null)));
+        processAllMessages();
+
+        verify(mNullCipherNotifier, never()).setSubscriptionMapping(any(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testUpdateNullCipherNotifier_activeSubscription() {
+        when(mFeatureFlags.enableModemCipherTransparencyUnsolEvents()).thenReturn(true);
+
+        int subId = 10;
+        SubscriptionInfoInternal subInfo = new SubscriptionInfoInternal.Builder().setSimSlotIndex(
+                0).setId(subId).build();
+        when(mSubscriptionManagerService.getSubscriptionInfoInternal(subId)).thenReturn(
+                subInfo);
+        doReturn(subId).when(mSubscriptionManagerService)
+                .getSubId(anyInt());
+        Phone phoneUT = makeNewPhoneUT();
+
+        phoneUT.sendMessage(
+                mPhoneUT.obtainMessage(
+                        Phone.EVENT_SUBSCRIPTIONS_CHANGED,
+                        new AsyncResult(null, null, null)));
+        processAllMessages();
+
+        verify(mNullCipherNotifier, times(1)).setSubscriptionMapping(eq(mContext), eq(0), eq(10));
+    }
+
+    @Test
+    public void testUpdateNullCipherNotifier_inactiveSubscription() {
+        when(mFeatureFlags.enableModemCipherTransparencyUnsolEvents()).thenReturn(true);
+        int subId = 1;
+        SubscriptionInfoInternal subInfo = new SubscriptionInfoInternal.Builder().setSimSlotIndex(
+                -1).setId(subId).build();
+        when(mSubscriptionManagerService.getSubscriptionInfoInternal(subId)).thenReturn(
+                subInfo);
+        doReturn(subId).when(mSubscriptionManagerService)
+                .getSubId(anyInt());
+        Phone phoneUT = makeNewPhoneUT();
+
+        phoneUT.sendMessage(
+                mPhoneUT.obtainMessage(
+                        Phone.EVENT_SUBSCRIPTIONS_CHANGED,
+                        new AsyncResult(null, null, null)));
+        processAllMessages();
+
+        verify(mNullCipherNotifier, times(1)).setSubscriptionMapping(eq(mContext), eq(0), eq(-1));
     }
 
     @Test
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 475c90b..75ef7ec 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -16,13 +16,26 @@
 
 package com.android.internal.telephony.metrics;
 
+import static android.telephony.SmsManager.RESULT_ERROR_NONE;
+import static android.telephony.SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_GPRS;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_GSM;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 
+import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
 import static com.android.internal.telephony.TelephonyStatsLog.GBA_EVENT__FAILED_REASON__FEATURE_NOT_READY;
 import static com.android.internal.telephony.TelephonyStatsLog.GBA_EVENT__FAILED_REASON__UNKNOWN;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_ERROR_GENERIC;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_SUCCESS;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP2;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP2;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_NORMAL;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_SMS_PP;
+import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR;
+import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_SUCCESS;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__ERROR;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__PROVISIONING_XML;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__CLIENT_PARAMS_SENT;
@@ -70,7 +83,9 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationServiceDescStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
+import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
+import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
@@ -235,6 +250,14 @@
     private SipTransportSession mSipTransportSession2;
     private SipTransportSession[] mSipTransportSession;
 
+    private IncomingSms mIncomingSms1;
+    private IncomingSms mIncomingSms2;
+    private IncomingSms[] mIncomingSms;
+
+    private OutgoingSms mOutgoingSms1;
+    private OutgoingSms mOutgoingSms2;
+    private OutgoingSms[] mOutgoingSms;
+
     private OutgoingShortCodeSms mOutgoingShortCodeSms1;
     private OutgoingShortCodeSms mOutgoingShortCodeSms2;
     private OutgoingShortCodeSms[] mOutgoingShortCodeSms;
@@ -946,6 +969,95 @@
         mSipTransportSession =
                 new SipTransportSession[] {mSipTransportSession1, mSipTransportSession2};
 
+        generateTestSmsData();
+        generateTestSatelliteData();
+    }
+
+    private void generateTestSmsData() {
+        mIncomingSms1 = new IncomingSms();
+        mIncomingSms1.smsFormat = INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP;
+        mIncomingSms1.smsTech = INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP;
+        mIncomingSms1.rat = TelephonyManager.NETWORK_TYPE_LTE;
+        mIncomingSms1.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_NORMAL;
+        mIncomingSms1.totalParts = 1;
+        mIncomingSms1.receivedParts = 1;
+        mIncomingSms1.blocked = false;
+        mIncomingSms1.error = INCOMING_SMS__ERROR__SMS_SUCCESS;
+        mIncomingSms1.isRoaming = false;
+        mIncomingSms1.simSlotIndex = 0;
+        mIncomingSms1.isMultiSim = false;
+        mIncomingSms1.isEsim = false;
+        mIncomingSms1.carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        mIncomingSms1.messageId = 0;
+        mIncomingSms1.count = 1;
+        mIncomingSms1.isManagedProfile = false;
+        mIncomingSms1.isNtn = false;
+
+        mIncomingSms2 = new IncomingSms();
+        mIncomingSms2.smsFormat = INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP2;
+        mIncomingSms2.smsTech = INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP2;
+        mIncomingSms2.rat = TelephonyManager.NETWORK_TYPE_CDMA;
+        mIncomingSms2.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_SMS_PP;
+        mIncomingSms2.totalParts = 2;
+        mIncomingSms2.receivedParts = 2;
+        mIncomingSms2.blocked = true;
+        mIncomingSms2.error = INCOMING_SMS__ERROR__SMS_ERROR_GENERIC;
+        mIncomingSms2.isRoaming = true;
+        mIncomingSms2.simSlotIndex = 1;
+        mIncomingSms2.isMultiSim = true;
+        mIncomingSms2.isEsim = true;
+        mIncomingSms2.carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        mIncomingSms2.messageId = 1;
+        mIncomingSms2.count = 2;
+        mIncomingSms2.isManagedProfile = true;
+        mIncomingSms2.isNtn = true;
+
+        mIncomingSms = new IncomingSms[] {mIncomingSms1, mIncomingSms2};
+
+        mOutgoingSms1 = new OutgoingSms();
+        mOutgoingSms1.smsFormat = INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP;
+        mOutgoingSms1.smsTech = INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP;
+        mOutgoingSms1.rat = TelephonyManager.NETWORK_TYPE_LTE;
+        mOutgoingSms1.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_SUCCESS;
+        mOutgoingSms1.errorCode = NO_ERROR_CODE;
+        mOutgoingSms1.isRoaming = false;
+        mOutgoingSms1.isFromDefaultApp = true;
+        mOutgoingSms1.simSlotIndex = 0;
+        mOutgoingSms1.isMultiSim = false;
+        mOutgoingSms1.carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        mOutgoingSms1.messageId = 0;
+        mOutgoingSms1.retryId = 0;
+        mOutgoingSms1.intervalMillis = 0;
+        mOutgoingSms1.count = 1;
+        mOutgoingSms1.sendErrorCode = RESULT_ERROR_NONE;
+        mOutgoingSms1.networkErrorCode = NO_ERROR_CODE;
+        mOutgoingSms1.isManagedProfile = false;
+        mOutgoingSms1.isEmergency = false;
+        mOutgoingSms1.isNtn = false;
+
+        mOutgoingSms2 = new OutgoingSms();
+        mOutgoingSms2.smsFormat = INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP2;
+        mOutgoingSms2.smsTech = INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP2;
+        mOutgoingSms2.rat = TelephonyManager.NETWORK_TYPE_CDMA;
+        mOutgoingSms2.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR;
+        mOutgoingSms2.errorCode = NO_ERROR_CODE;
+        mOutgoingSms2.isRoaming = true;
+        mOutgoingSms2.isFromDefaultApp = false;
+        mOutgoingSms2.simSlotIndex = 1;
+        mOutgoingSms2.isMultiSim = true;
+        mOutgoingSms2.carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        mOutgoingSms2.messageId = 1;
+        mOutgoingSms2.retryId = 1;
+        mOutgoingSms2.intervalMillis = 10;
+        mOutgoingSms2.count = 2;
+        mOutgoingSms2.sendErrorCode = RESULT_RIL_SMS_SEND_FAIL_RETRY;
+        mOutgoingSms2.networkErrorCode = NO_ERROR_CODE;
+        mOutgoingSms2.isManagedProfile = true;
+        mOutgoingSms2.isEmergency = true;
+        mOutgoingSms2.isNtn = true;
+
+        mOutgoingSms = new OutgoingSms[] {mOutgoingSms1, mOutgoingSms2};
+
         mOutgoingShortCodeSms1 = new OutgoingShortCodeSms();
         mOutgoingShortCodeSms1.category = 1;
         mOutgoingShortCodeSms1.xmlVersion = 30;
@@ -958,8 +1070,6 @@
 
         mOutgoingShortCodeSms = new OutgoingShortCodeSms[] {mOutgoingShortCodeSms1,
                 mOutgoingShortCodeSms2};
-
-        generateTestSatelliteData();
     }
 
     private void generateTestSatelliteData() {
@@ -1244,6 +1354,12 @@
         mSipTransportSession1 = null;
         mSipTransportSession2 = null;
         mSipTransportSession = null;
+        mIncomingSms = null;
+        mIncomingSms1 = null;
+        mIncomingSms2 = null;
+        mOutgoingSms = null;
+        mOutgoingSms1 = null;
+        mOutgoingSms2 = null;
         mOutgoingShortCodeSms1 = null;
         mOutgoingShortCodeSms2 = null;
         mOutgoingShortCodeSms = null;
@@ -3721,6 +3837,88 @@
     }
 
     @Test
+    public void addIncomingSms_emptyProto() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addIncomingSms(mIncomingSms1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // IncomingSms should be added successfully, changes should be saved.
+        verifyCurrentStateSavedToFileOnce();
+        IncomingSms[] expectedList = new IncomingSms[] {mIncomingSms1};
+        assertProtoArrayEquals(expectedList, mPersistAtomsStorage.getIncomingSms(0L));
+    }
+
+    @Test
+    public void addIncomingSms_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addIncomingSms(mIncomingSms1);
+        mPersistAtomsStorage.addIncomingSms(mIncomingSms2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // IncomingSms should be added successfully.
+        verifyCurrentStateSavedToFileOnce();
+        IncomingSms[] expectedList = new IncomingSms[] {mIncomingSms1, mIncomingSms2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList, mPersistAtomsStorage.getIncomingSms(0L));
+    }
+
+    @Test
+    public void getIncomingSms_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        // Pull interval less than minimum.
+        mPersistAtomsStorage.incTimeMillis(50L);
+        IncomingSms[] outgoingShortCodeSmsList = mPersistAtomsStorage.getIncomingSms(100L);
+        // Should be denied.
+        assertNull(outgoingShortCodeSmsList);
+    }
+
+    @Test
+    public void addOutgoingSms_emptyProto() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addOutgoingSms(mOutgoingSms1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // OutgoingSms should be added successfully, changes should be saved.
+        verifyCurrentStateSavedToFileOnce();
+        OutgoingSms[] expectedList = new OutgoingSms[] {mOutgoingSms1};
+        assertProtoArrayEquals(expectedList, mPersistAtomsStorage.getOutgoingSms(0L));
+    }
+
+    @Test
+    public void addOutgoingSms_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addOutgoingSms(mOutgoingSms1);
+        mPersistAtomsStorage.addOutgoingSms(mOutgoingSms2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // OutgoingSms should be added successfully.
+        verifyCurrentStateSavedToFileOnce();
+        OutgoingSms[] expectedList = new OutgoingSms[] {mOutgoingSms1, mOutgoingSms2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList, mPersistAtomsStorage.getOutgoingSms(0L));
+    }
+
+    @Test
+    public void getOutgoingSms_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        // Pull interval less than minimum.
+        mPersistAtomsStorage.incTimeMillis(50L);
+        OutgoingSms[] outgoingShortCodeSmsList = mPersistAtomsStorage.getOutgoingSms(100L);
+        // Should be denied.
+        assertNull(outgoingShortCodeSmsList);
+    }
+
+    @Test
     public void addOutgoingShortCodeSms_emptyProto() throws Exception {
         createEmptyTestFile();
 
@@ -4516,6 +4714,16 @@
         return SipTransportSession.parseFrom(MessageNano.toByteArray(source));
     }
 
+    private static IncomingSms copyOf(IncomingSms source)
+            throws Exception {
+        return IncomingSms.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static OutgoingSms copyOf(OutgoingSms source)
+            throws Exception {
+        return OutgoingSms.parseFrom(MessageNano.toByteArray(source));
+    }
+
     private static OutgoingShortCodeSms copyOf(OutgoingShortCodeSms source)
             throws Exception {
         return OutgoingShortCodeSms.parseFrom(MessageNano.toByteArray(source));
@@ -4949,6 +5157,64 @@
     }
 
     private static void assertHasStatsAndCount(
+            IncomingSms[] incomingSmsList,
+            @Nullable IncomingSms expectedIncomingSms, int expectedCount) {
+        assertNotNull(incomingSmsList);
+        int actualCount = -1;
+        for (IncomingSms incomingSms : incomingSmsList) {
+            if (incomingSms.smsFormat == expectedIncomingSms.smsFormat
+                    && incomingSms.smsTech == expectedIncomingSms.smsTech
+                    && incomingSms.rat == expectedIncomingSms.rat
+                    && incomingSms.smsType == expectedIncomingSms.smsType
+                    && incomingSms.totalParts == expectedIncomingSms.totalParts
+                    && incomingSms.receivedParts == expectedIncomingSms.receivedParts
+                    && incomingSms.blocked == expectedIncomingSms.blocked
+                    && incomingSms.error == expectedIncomingSms.error
+                    && incomingSms.isRoaming == expectedIncomingSms.isRoaming
+                    && incomingSms.simSlotIndex == expectedIncomingSms.simSlotIndex
+                    && incomingSms.isMultiSim == expectedIncomingSms.isMultiSim
+                    && incomingSms.isEsim == expectedIncomingSms.isEsim
+                    && incomingSms.carrierId == expectedIncomingSms.carrierId
+                    && incomingSms.messageId == expectedIncomingSms.messageId
+                    && incomingSms.isManagedProfile == expectedIncomingSms.isManagedProfile
+                    && incomingSms.isNtn == expectedIncomingSms.isNtn) {
+                actualCount = incomingSms.count;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStatsAndCount(
+            OutgoingSms[] outgoingSmsList,
+            @Nullable OutgoingSms expectedOutgoingSms, int expectedCount) {
+        assertNotNull(outgoingSmsList);
+        int actualCount = -1;
+        for (OutgoingSms outgoingSms : outgoingSmsList) {
+            if (outgoingSms.smsFormat == expectedOutgoingSms.smsFormat
+                    && outgoingSms.smsTech == expectedOutgoingSms.smsTech
+                    && outgoingSms.rat == expectedOutgoingSms.rat
+                    && outgoingSms.sendResult == expectedOutgoingSms.sendResult
+                    && outgoingSms.errorCode == expectedOutgoingSms.errorCode
+                    && outgoingSms.isRoaming == expectedOutgoingSms.isRoaming
+                    && outgoingSms.isFromDefaultApp == expectedOutgoingSms.isFromDefaultApp
+                    && outgoingSms.simSlotIndex == expectedOutgoingSms.simSlotIndex
+                    && outgoingSms.isMultiSim == expectedOutgoingSms.isMultiSim
+                    && outgoingSms.carrierId == expectedOutgoingSms.carrierId
+                    && outgoingSms.messageId == expectedOutgoingSms.messageId
+                    && outgoingSms.retryId == expectedOutgoingSms.retryId
+                    && outgoingSms.intervalMillis == expectedOutgoingSms.intervalMillis
+                    && outgoingSms.sendErrorCode == expectedOutgoingSms.sendErrorCode
+                    && outgoingSms.networkErrorCode == expectedOutgoingSms.networkErrorCode
+                    && outgoingSms.isManagedProfile == expectedOutgoingSms.isManagedProfile
+                    && outgoingSms.isEmergency == expectedOutgoingSms.isEmergency
+                    && outgoingSms.isNtn == expectedOutgoingSms.isNtn) {
+                actualCount = outgoingSms.count;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStatsAndCount(
             OutgoingShortCodeSms[] outgoingShortCodeSmsList,
             @Nullable OutgoingShortCodeSms expectedOutgoingShortCodeSms, int expectedCount) {
         assertNotNull(outgoingShortCodeSmsList);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
index 37ff69a..79f1928 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
@@ -89,6 +89,7 @@
             (int) TimeUnit.SECONDS.toMillis(180);
     private static final long TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS =
             TimeUnit.SECONDS.toMillis(60);
+    private static final Long TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE = TimeUnit.SECONDS.toMillis(10);
 
     private DatagramDispatcher mDatagramDispatcherUT;
     private TestDatagramDispatcher mTestDemoModeDatagramDispatcher;
@@ -409,6 +410,8 @@
                 true, mResultListener::offer);
 
         processAllMessages();
+        moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+        processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
@@ -484,6 +487,8 @@
                 true, mResultListener::offer);
 
         processAllMessages();
+        moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+        processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID),
@@ -597,6 +602,11 @@
         mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
                 true, mIntegerConsumer);
         processAllMessages();
+        moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+        processAllMessages();
+
+        verify(mMockDatagramController).pollPendingSatelliteDatagrams(anyInt(), any());
+
         waitForIntegerConsumerResult(1);
         assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS,
                 (int) mIntegerConsumerResult.get(0));
@@ -609,6 +619,11 @@
         mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
                 true, mIntegerConsumer);
         processAllMessages();
+        moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+        processAllMessages();
+
+        verify(mMockDatagramController, times(2)).pollPendingSatelliteDatagrams(anyInt(), any());
+
         waitForIntegerConsumerResult(1);
         assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS,
                 (int) mIntegerConsumerResult.get(0));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
index 3a42881..5a1a8f7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
@@ -323,10 +323,10 @@
         // Checks invalid case only as SatelliteController does not exist in unit test
         mTestDemoModeDatagramReceiver.setDemoMode(true);
         mTestDemoModeDatagramReceiver.setDeviceAlignedWithSatellite(true);
-        when(mMockDatagramController.getDemoModeDatagram()).thenReturn(mDatagram);
+        when(mMockDatagramController.popDemoModeDatagram()).thenReturn(mDatagram);
         mTestDemoModeDatagramReceiver.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
         processAllMessages();
-        verify(mMockDatagramController, times(1)).getDemoModeDatagram();
+        verify(mMockDatagramController, times(1)).popDemoModeDatagram();
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING),
@@ -353,10 +353,10 @@
         mTestDemoModeDatagramReceiver.setDemoMode(true);
         mTestDemoModeDatagramReceiver.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN);
         mTestDemoModeDatagramReceiver.setDeviceAlignedWithSatellite(false);
-        when(mMockDatagramController.getDemoModeDatagram()).thenReturn(mDatagram);
+        when(mMockDatagramController.popDemoModeDatagram()).thenReturn(mDatagram);
         mTestDemoModeDatagramReceiver.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
         processAllMessages();
-        verify(mMockDatagramController, never()).getDemoModeDatagram();
+        verify(mMockDatagramController, never()).popDemoModeDatagram();
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING),
diff --git a/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java
index 76118c4..77759a0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java
@@ -36,6 +36,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceIssue;
 import android.util.Singleton;
 
 import com.android.internal.R;
@@ -50,6 +51,7 @@
 import org.mockito.ArgumentCaptor;
 
 import java.time.Instant;
+import java.util.List;
 
 public final class CellularNetworkSecuritySafetySourceTest extends TelephonyTest {
 
@@ -76,6 +78,8 @@
 
         mContextFixture.putResource(R.string.scCellularNetworkSecurityTitle, "fake");
         mContextFixture.putResource(R.string.scCellularNetworkSecuritySummary, "fake");
+        mContextFixture.putResource(R.string.scCellularNetworkSecurityLearnMore,
+                "https://support.google.com/android?p=cellular_security");
         mContextFixture.putResource(R.string.scNullCipherIssueNonEncryptedTitle, "fake %1$s");
         mContextFixture.putResource(R.string.scNullCipherIssueNonEncryptedSummary, "fake");
         mContextFixture.putResource(R.string.scNullCipherIssueEncryptedTitle, "fake %1$s");
@@ -172,6 +176,26 @@
     }
 
     @Test
+    public void clearNullCipherState() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_NOTIFY_ENCRYPTED);
+        mSafetySource.clearNullCipherState(mContext, 0);
+
+        // Once for enable, once for encrypted state, and once for clearing state
+        verify(mSafetyCenterManagerWrapper, times(3)).setSafetySourceData(data.capture());
+
+        // initial check that our encrypted state update created an issue
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).hasSize(1);
+
+        // assert that our last call to clear state results in no issues sent to SC
+        assertThat(data.getAllValues().get(2).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(2).getIssues()).isEmpty();
+    }
+
+    @Test
     public void disableIdentifierDisclosueIssue_nullData() {
         // We must first enable before disabling, since a standalone call to disable may result in
         // a no-op when the default for a new notifier is to be disabled.
@@ -245,4 +269,24 @@
         assertThat(data.getAllValues().get(3).getStatus()).isNotNull();
         assertThat(data.getAllValues().get(3).getIssues()).hasSize(2);
     }
+
+    @Test
+    public void learnMoreLinkEmpty_learnMoreIsHidden() {
+        mContextFixture.putResource(R.string.scCellularNetworkSecurityLearnMore, "");
+
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED);
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+        mSafetySource.setIdentifierDisclosure(mContext, 0, 12, Instant.now(), Instant.now());
+
+        verify(mSafetyCenterManagerWrapper, times(4)).setSafetySourceData(data.capture());
+        List<SafetySourceIssue.Action> actions = data.getAllValues().get(
+                3).getIssues().getFirst().getActions();
+
+        // we only see the action that takes you to the settings page
+        assertThat(actions).hasSize(1);
+        assertThat(actions.getFirst().getId()).isEqualTo("cellular_security_settings");
+    }
 }
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java
index 0bb7b76..6a4d2fb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java
@@ -45,6 +45,7 @@
 public class NullCipherNotifierTest {
 
     private static final int SUB_ID = 3425;
+    private static final int PHONE_ID = 999;
     private static final List<Integer> NON_TRANSPORT_LAYER_EVENTS =
             List.of(SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP,
                     SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP_SOS,
@@ -149,9 +150,11 @@
     @Test
     public void onSecurityAlgorithmUpdate_enabled_unprotectedEmergency_noSafetySourceUpdate() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         notifier.onSecurityAlgorithmUpdate(
                 mContext,
+                PHONE_ID,
                 SUB_ID,
                 new SecurityAlgorithmUpdate(
                         SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G,
@@ -163,6 +166,24 @@
     }
 
     @Test
+    public void onSecurityAlgorithmUpdate_disabled_noSafetySourceUpdate() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.disable(mContext);
+
+        notifier.onSecurityAlgorithmUpdate(
+                mContext,
+                PHONE_ID,
+                SUB_ID,
+                new SecurityAlgorithmUpdate(
+                        SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G,
+                        SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                        SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                        /* isUnprotectedEmergency= */ false));
+
+        verify(mSafetySource, never()).setNullCipherState(any(), anyInt(), anyInt());
+    }
+
+    @Test
     public void onSecurityAlgorithmUpdate_enabled_nonTransportLayerEvent_noSafetySourceUpdate() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
 
@@ -170,6 +191,7 @@
             clearInvocations(mSafetySource);
             notifier.onSecurityAlgorithmUpdate(
                     mContext,
+                    PHONE_ID,
                     SUB_ID,
                     new SecurityAlgorithmUpdate(
                             connectionEvent,
@@ -185,12 +207,14 @@
     @Test
     public void onUpdate_enabled_transportLayerEvent_encryptionNullCipher_notifyNonEncrypted() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
             for (int encryptionAlgorithm : NULL_CIPHERS) {
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -214,12 +238,14 @@
     @Test
     public void onUpdate_enabled_transportLayerEvent_integrityNullCipher_notifyNonEncrypted() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
             for (int integrityAlgorithm : NULL_CIPHERS) {
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -243,12 +269,14 @@
     @Test
     public void onUpdate_enabled_transportLayerEvent_encryptionNonNullCipher_encrypted() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
             for (int encryptionAlgorithm : NON_NULL_CIPHERS) {
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -272,12 +300,14 @@
     @Test
     public void onUpdate_enabled_transportLayerEvent_integrityNonNullCipher_encrypted() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
             for (int integrityAlgorithm : NON_NULL_CIPHERS) {
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -301,11 +331,13 @@
     @Test
     public void onUpdate_enabled_transportLayerEvent_encryptionNonNullCipher_notifyEncrypted() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
             for (int encryptionAlgorithm : NON_NULL_CIPHERS) {
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -316,6 +348,7 @@
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -345,6 +378,7 @@
             for (int integrityAlgorithm : NON_NULL_CIPHERS) {
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -355,6 +389,7 @@
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -374,4 +409,88 @@
             }
         }
     }
+
+    @Test
+    public void onSecurityAlgorithmUpdate_updateSubscriptionId_clearsOldId() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
+
+        int connectionEvent = SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G;
+        int encryptionAlgorithm = SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2;
+        int subId2 = 1337;
+
+        notifier.onSecurityAlgorithmUpdate(
+                mContext,
+                PHONE_ID,
+                SUB_ID,
+                getWellEncryptedUpdate());
+
+        notifier.setSubscriptionMapping(mContext, PHONE_ID, subId2);
+
+        verify(
+                mSafetySource,
+                times(1).description(
+                        "Connection event: " + connectionEvent
+                                + " Encryption algorithm: " + encryptionAlgorithm))
+                .setNullCipherState(
+                        eq(mContext),
+                        eq(SUB_ID),
+                        eq(NULL_CIPHER_STATE_ENCRYPTED));
+
+        verify(mSafetySource, times(1)).clearNullCipherState(eq(mContext), eq(SUB_ID));
+    }
+
+    @Test
+    public void onSecurityAlgorithmUpdate_newSubIdOnAlgoUpdate_clearsOldId() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
+
+        int connectionEvent = SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G;
+        int encryptionAlgorithm = SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2;
+        int subId2 = 1337;
+
+        notifier.onSecurityAlgorithmUpdate(
+                mContext,
+                PHONE_ID,
+                SUB_ID,
+                getWellEncryptedUpdate());
+
+        notifier.onSecurityAlgorithmUpdate(
+                mContext,
+                PHONE_ID,
+                subId2,
+                getWellEncryptedUpdate());
+
+        verify(
+                mSafetySource,
+                times(1).description(
+                        "SubId: " + SUB_ID + "Connection event: " + connectionEvent
+                                + " Encryption algorithm: " + encryptionAlgorithm))
+                .setNullCipherState(
+                        eq(mContext),
+                        eq(SUB_ID),
+                        eq(NULL_CIPHER_STATE_ENCRYPTED));
+
+        // The update with subId2 should clear subId1 data since they have the same phone id
+        verify(mSafetySource, times(1)).clearNullCipherState(eq(mContext), eq(SUB_ID));
+
+        verify(
+                mSafetySource,
+                times(1).description(
+                        "SubId: " + SUB_ID + "Connection event: " + connectionEvent
+                                + " Encryption algorithm: " + encryptionAlgorithm))
+                .setNullCipherState(
+                        eq(mContext),
+                        eq(subId2),
+                        eq(NULL_CIPHER_STATE_ENCRYPTED));
+
+    }
+
+    private SecurityAlgorithmUpdate getWellEncryptedUpdate() {
+        return new SecurityAlgorithmUpdate(
+                SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G,
+                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                /* isUnprotectedEmergency= */ false);
+    }
 }