Add new Cellular Network Security page


Test: m & atest CellularSecurityNotificationsDividerControllerTest CellularSecurityNotificationsPreferenceControllerTest CellularSecurityEncryptionDividerControllerTest CellularSecurityPreferenceControllerTest
Bug: b/318428717
Change-Id: I4a6ec5f47beb36bd455e04c2e6c4cea0ba65110f
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index aed51f3..bc23990 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -628,6 +628,17 @@
                 android:value="true" />
         </activity>
 
+        <activity android:name="Settings$CellularSecuritySettingsActivity"
+                  android:label="@string/cellular_security_settings_title"
+                  android:exported="true">
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.CELLULAR_NETWORK_SECURITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+              android:value="com.android.settings.network.telephony.CellularSecuritySettingsFragment"/>
+        </activity>
+
         <activity android:name="Settings$SatelliteSettingActivity"
                   android:label="@string/satellite_setting"
                   android:exported="true"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 80063b6..1e02496 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6183,6 +6183,29 @@
     <!-- Title for adaptive connectivity main switch preferences. [CHAR LIMIT=50] -->
     <string name="adaptive_connectivity_main_switch_title">Use adaptive connectivity</string>
 
+    <!-- Cellular security related strings -->
+    <!-- Title of Cellular security tile in Network & Internet settings page. [CHAR LIMIT=60]-->
+    <string name="cellular_security_title">Cellular network security</string>
+    <!-- Summary of Cellular security tile in Network & Internet settings page. [CHAR LIMIT=NONE]-->
+    <string name="cellular_security_summary">Network type, encryption, notification controls</string>
+
+    <!-- Title of cellular security settings page [CHAR LIMIT=60]-->
+    <string name="cellular_security_settings_title">Cellular network security</string>
+    <!-- Title of Cellular security notifications section [CHAR LIMIT=60]-->
+    <string name="cellular_security_notifications">Notifications</string>
+    <!-- Title of Cellular security notifications toggle [CHAR LIMIT=60]-->
+    <string name="cellular_security_notifications_controller_title">Security notifications</string>
+    <!-- Summary of Cellular security notifications toggle [CHAR LIMIT=NONE]-->
+    <string name="cellular_security_notifications_controller_summary">Receive notifications in case the cellular network you are connected to is insecure due to lack of encryption, or if the cellular network records your unique decive or SIM identifiers (IMEI &amp; IMSI)</string>
+
+    <!--Cellular encryption -->
+    <!--Cellular encryption title [CHAR LIMIT=60] -->
+    <string name="cellular_security_settings_encryption_title">Encryption</string>
+    <!-- Title of Cellular security Network generations divider [CHAR LIMIT=30]-->
+    <string name="cellular_security_network_generations_title">Network generations</string>
+    <!-- Summary of Cellular security Network generations divider [CHAR LIMIT=NONE]-->
+    <string name="cellular_security_network_generations_summary">You can configure each installed SIM card to only connect to networks that support 3G, 4G, and 5G. The SIM will not connect to older, insecure 2G networks. This setting may limit your connectivity in case the only available network is 2G. In case of an emergency, 2G may be used.</string>
+
     <!-- Title of preference group for credential storage settings [CHAR LIMIT=30] -->
     <string name="credentials_title">Credential storage</string>
     <!-- Title of preference to install certificates [CHAR LIMIT=30] -->
diff --git a/res/xml/cellular_security.xml b/res/xml/cellular_security.xml
new file mode 100644
index 0000000..e5fee15
--- /dev/null
+++ b/res/xml/cellular_security.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:key="cellular_security_settings_screen"
+    android:title="@string/cellular_security_settings_title">
+    <PreferenceCategory
+        android:key="cellular_security_notifications_category"
+        android:title="@string/cellular_security_notifications"
+        settings:controller="com.android.settings.network.CellularSecurityNotificationsDividerController">
+        <SwitchPreferenceCompat
+           android:key="cellular_security_notifications"
+           android:title="@string/cellular_security_notifications_controller_title"
+           android:summary="@string/cellular_security_notifications_controller_summary"
+           settings:controller=
+                "com.android.settings.network.CellularSecurityNotificationsPreferenceController"/>
+    </PreferenceCategory>
+    <PreferenceCategory
+      android:title="@string/cellular_security_settings_encryption_title"
+      settings:controller="com.android.settings.network.CellularSecurityEncryptionDividerController">
+        <SwitchPreferenceCompat
+            android:key="require_cellular_encryption"
+            android:title="@string/require_cellular_encryption_title"
+            android:summary="@string/require_cellular_encryption_summary"
+            settings:controller=
+                "com.android.settings.network.telephony.NullAlgorithmsPreferenceController"/>
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/res/xml/more_security_privacy_settings.xml b/res/xml/more_security_privacy_settings.xml
index 92c3fa7..3e11db2 100644
--- a/res/xml/more_security_privacy_settings.xml
+++ b/res/xml/more_security_privacy_settings.xml
@@ -118,6 +118,12 @@
             android:summary="@string/content_capture_summary"
             settings:controller="com.android.settings.privacy.EnableContentCaptureWithServiceSettingsPreferenceController"/>
 
+        <Preference
+            android:key="cellular_security_settings_privacy"
+            android:title="@string/cellular_security_title"
+            android:summary="@string/cellular_security_summary"
+            android:fragment="com.android.settings.network.telephony.CellularSecuritySettingsFragment"
+            settings:searchable="false"/>
     </PreferenceCategory>
 
     <!-- Security section. -->
diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml
index 04f248e..2a08aae 100644
--- a/res/xml/network_provider_internet.xml
+++ b/res/xml/network_provider_internet.xml
@@ -109,4 +109,11 @@
         android:summary="@string/summary_placeholder"
         android:order="25"
         settings:controller="com.android.settings.network.AdaptiveConnectivityPreferenceController"/>
+
+    <Preference
+        android:key="cellular_security_network_internet"
+        android:title="@string/cellular_security_title"
+        android:summary="@string/cellular_security_summary"
+        android:order="30"
+        settings:controller="com.android.settings.network.CellularSecurityPreferenceController"/>
 </PreferenceScreen>
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 2f6f04a..4c2ee67 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -348,6 +348,7 @@
         /* empty */
     }
 
+    public static class CellularSecuritySettingsActivity extends SettingsActivity { /* empty */ }
     public static class SatelliteSettingActivity extends SettingsActivity { /* empty */ }
     public static class ApnSettingsActivity extends SettingsActivity { /* empty */ }
     public static class WifiCallingSettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index e08e856..8b3dbf4 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -144,6 +144,7 @@
 import com.android.settings.network.NetworkProviderSettings;
 import com.android.settings.network.apn.ApnEditor;
 import com.android.settings.network.apn.ApnSettings;
+import com.android.settings.network.telephony.CellularSecuritySettingsFragment;
 import com.android.settings.network.telephony.MobileNetworkSettings;
 import com.android.settings.network.telephony.NetworkSelectSettings;
 import com.android.settings.network.telephony.SatelliteSetting;
@@ -388,6 +389,7 @@
             ScreenTimeoutSettings.class.getName(),
             ResetNetwork.class.getName(),
             VibrationIntensitySettingsFragment.class.getName(),
+            CellularSecuritySettingsFragment.class.getName(),
     };
 
     public static final String[] SETTINGS_FOR_RESTRICTED = {
diff --git a/src/com/android/settings/network/CellularSecurityPreferenceController.java b/src/com/android/settings/network/CellularSecurityPreferenceController.java
new file mode 100644
index 0000000..237e704
--- /dev/null
+++ b/src/com/android/settings/network/CellularSecurityPreferenceController.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.safetycenter.SafetyCenterManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.telephony.flags.Flags;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.network.telephony.CellularSecuritySettingsFragment;
+
+/**
+ * {@link BasePreferenceController} for accessing Cellular Security settings from Network &
+ * Internet Settings menu.
+ */
+public class CellularSecurityPreferenceController extends BasePreferenceController {
+
+    private static final String LOG_TAG = "CellularSecurityPreferenceController";
+
+    private @Nullable TelephonyManager mTelephonyManager;
+
+    /**
+     * Class constructor of "Cellular Security" preference.
+     *
+     * @param context of settings
+     * @param prefKey     assigned within UI entry of XML file
+     */
+    public CellularSecurityPreferenceController(@NonNull Context context, @NonNull String prefKey) {
+        super(context, prefKey);
+        mTelephonyManager = context.getSystemService(TelephonyManager.class);
+    }
+
+    @Override
+    public void displayPreference(@NonNull PreferenceScreen screen) {
+        super.displayPreference(screen);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        if (!Flags.enableIdentifierDisclosureTransparencyUnsolEvents()
+                || !Flags.enableModemCipherTransparencyUnsolEvents()
+                || !Flags.enableIdentifierDisclosureTransparency()
+                || !Flags.enableModemCipherTransparency()) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        if (mTelephonyManager == null) {
+            Log.w(LOG_TAG, "Telephony manager not yet initialized");
+            mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        }
+
+        boolean isNullCipherDisablementAvailable = false;
+        boolean areCellSecNotificationsAvailable = false;
+        try {
+            mTelephonyManager.isNullCipherAndIntegrityPreferenceEnabled();
+            isNullCipherDisablementAvailable = true; // true because it doesn't throw an exception,
+                                                     // we don't want the value of
+                                                     // isNullCipherAndIntegrityEnabled()
+        } catch (UnsupportedOperationException e) {
+            Log.i(LOG_TAG, "Null cipher enablement is unsupported, hiding divider: "
+                    + e.getMessage());
+        } catch (Exception e) {
+            Log.e(LOG_TAG,
+                    "Failed isNullCipherAndIntegrityEnabled. Setting availability to "
+                            + "CONDITIONALLY_UNAVAILABLE. Exception: "
+                            + e.getMessage());
+        }
+
+        try {
+            // Must call both APIs, as we can't use the combined toggle if both aren't available
+            areNotificationsEnabled();
+            areCellSecNotificationsAvailable = true; // true because it doesn't throw an exception
+                                                     // and we don't want the value of
+                                                     // areNotificationsEnabled()
+        } catch (UnsupportedOperationException e) {
+            Log.i(LOG_TAG, "Cellular security notifications are unsupported, hiding divider: "
+                    + e.getMessage());
+        }
+
+        if (isNullCipherDisablementAvailable || areCellSecNotificationsAvailable) {
+            return AVAILABLE;
+        } else {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(@NonNull Preference preference) {
+        if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
+            return super.handlePreferenceTreeClick(preference);
+        }
+        boolean isSafetyCenterSupported = isSafetyCenterSupported();
+        if (isSafetyCenterSupported) {
+            Intent safetyCenterIntent = new Intent(Intent.ACTION_SAFETY_CENTER);
+            safetyCenterIntent.putExtra(SafetyCenterManager.EXTRA_SAFETY_SOURCES_GROUP_ID,
+                    "AndroidCellularNetworkSecuritySources");
+            mContext.startActivity(safetyCenterIntent);
+        } else {
+            final Bundle bundle = new Bundle();
+            bundle.putString(CellularSecuritySettingsFragment.KEY_CELLULAR_SECURITY_PREFERENCE, "");
+
+            new SubSettingLauncher(mContext)
+                     .setDestination(CellularSecuritySettingsFragment.class.getName())
+                     .setArguments(bundle)
+                     .setSourceMetricsCategory(SettingsEnums.CELLULAR_SECURITY_SETTINGS)
+                     .launch();
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    protected boolean isSafetyCenterSupported() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            return false;
+        }
+        SafetyCenterManager safetyCenterManager = mContext.getSystemService(
+                SafetyCenterManager.class);
+        if (safetyCenterManager == null) {
+            return false;
+        }
+        return safetyCenterManager.isSafetyCenterEnabled();
+    }
+
+    @VisibleForTesting
+    protected boolean areNotificationsEnabled() {
+        if (mTelephonyManager == null) {
+            Log.w(LOG_TAG, "Telephony manager not yet initialized");
+            mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        }
+
+        return mTelephonyManager.isNullCipherNotificationsEnabled()
+            && mTelephonyManager.isCellularIdentifierDisclosureNotificationsEnabled();
+    }
+}
diff --git a/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerController.java b/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerController.java
new file mode 100644
index 0000000..6d31c72
--- /dev/null
+++ b/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerController.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
+import com.android.settings.core.BasePreferenceController;
+
+/**
+ * {@link BasePreferenceController} for visibility of Encryption divider on Cellular Security
+ * settings page.
+ */
+public class CellularSecurityEncryptionDividerController extends
+                BasePreferenceController {
+
+    private static final String LOG_TAG = "CellularSecurityEncryptionDividerController";
+
+    private TelephonyManager mTelephonyManager;
+
+    protected final FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
+
+    /**
+     * Class constructor of "Cellular Security" preference.
+     *
+     * @param context of settings
+     * @param prefKey assigned within UI entry of XML file
+     */
+    public CellularSecurityEncryptionDividerController(
+            @NonNull Context context, @NonNull String prefKey) {
+        super(context, prefKey);
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+    }
+
+    /**
+     * Initialization.
+     */
+    public CellularSecurityEncryptionDividerController init() {
+        return this;
+    }
+
+    @Override
+    public void displayPreference(@NonNull PreferenceScreen screen) {
+        super.displayPreference(screen);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        if (mTelephonyManager == null) {
+            Log.w(LOG_TAG,
+                    "Telephony manager not yet initialized. Marking availability as "
+                            + "CONDITIONALLY_UNAVAILABLE");
+            mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+            return CONDITIONALLY_UNAVAILABLE;
+        }
+
+        try {
+            mTelephonyManager.isNullCipherAndIntegrityPreferenceEnabled();
+        } catch (UnsupportedOperationException e) {
+            Log.i(LOG_TAG, "Null cipher enablement is unsupported, hiding divider: "
+                    + e.getMessage());
+            return UNSUPPORTED_ON_DEVICE;
+        } catch (Exception e) {
+            Log.e(LOG_TAG,
+                    "Failed isNullCipherAndIntegrityEnabled. Setting availability to "
+                            + "CONDITIONALLY_UNAVAILABLE. Exception: "
+                            + e.getMessage());
+            return CONDITIONALLY_UNAVAILABLE;
+        }
+        return AVAILABLE;
+    }
+}
diff --git a/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerController.java b/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerController.java
new file mode 100644
index 0000000..bbe954c
--- /dev/null
+++ b/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerController.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony;
+
+import android.content.Context;
+import android.os.Build;
+import android.safetycenter.SafetyCenterManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.telephony.flags.Flags;
+import com.android.settings.core.BasePreferenceController;
+
+/**
+ * {@link BasePreferenceController} for visibility of Notifications divider on Cellular Security
+ * settings page.
+ */
+public class CellularSecurityNotificationsDividerController extends
+                BasePreferenceController {
+
+    private static final String LOG_TAG = "CellularSecurityNotificationsDividerController";
+
+    private TelephonyManager mTelephonyManager;
+    @VisibleForTesting
+    protected SafetyCenterManager mSafetyCenterManager;
+
+    /**
+     * Class constructor of "Cellular Security" preference.
+     *
+     * @param context of settings
+     * @param prefKey assigned within UI entry of XML file
+     */
+    public CellularSecurityNotificationsDividerController(
+            @NonNull Context context, @NonNull String prefKey) {
+        super(context, prefKey);
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class);
+    }
+
+    /**
+     * Initialization.
+     */
+    public CellularSecurityNotificationsDividerController init() {
+        return this;
+    }
+
+    @Override
+    public void displayPreference(@NonNull PreferenceScreen screen) {
+        super.displayPreference(screen);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        if (!Flags.enableIdentifierDisclosureTransparencyUnsolEvents()
+                || !Flags.enableModemCipherTransparencyUnsolEvents()
+                || !Flags.enableIdentifierDisclosureTransparency()
+                || !Flags.enableModemCipherTransparency()) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        if (!isSafetyCenterSupported()) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        if (mTelephonyManager == null) {
+            Log.w(LOG_TAG, "Telephony manager not yet initialized");
+            mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        }
+        // Checking for hardware support, i.e. IRadio AIDL version must be >= 2.2
+        try {
+            // Must call both APIs, as we can't use the combined toggle if both aren't available
+            areNotificationsEnabled();
+        } catch (UnsupportedOperationException e) {
+            Log.i(LOG_TAG, "Cellular security notifications are unsupported, hiding divider: "
+                    + e.getMessage());
+            return UNSUPPORTED_ON_DEVICE;
+        }
+
+        return AVAILABLE;
+    }
+
+    @VisibleForTesting
+    protected boolean areNotificationsEnabled() {
+        return mTelephonyManager.isNullCipherNotificationsEnabled()
+            && mTelephonyManager.isCellularIdentifierDisclosureNotificationsEnabled();
+    }
+
+    protected boolean isSafetyCenterSupported() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            return false;
+        }
+        mSafetyCenterManager = mContext.getSystemService(
+                SafetyCenterManager.class);
+        if (mSafetyCenterManager == null) {
+            return false;
+        }
+        return mSafetyCenterManager.isSafetyCenterEnabled();
+    }
+}
diff --git a/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceController.java b/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceController.java
new file mode 100644
index 0000000..520e7c5
--- /dev/null
+++ b/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceController.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony;
+
+import android.content.Context;
+import android.os.Build;
+import android.safetycenter.SafetyCenterManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * {@link TelephonyTogglePreferenceController} for accessing Cellular Security settings through
+ * Safety Center.
+ */
+public class CellularSecurityNotificationsPreferenceController extends
+                TelephonyTogglePreferenceController {
+
+    private static final String LOG_TAG = "CellularSecurityNotificationsPreferenceController";
+
+    private TelephonyManager mTelephonyManager;
+    @VisibleForTesting
+    protected SafetyCenterManager mSafetyCenterManager;
+
+    /**
+     * Class constructor of "Cellular Security" preference.
+     *
+     * @param context of settings
+     * @param prefKey assigned within UI entry of XML file
+     */
+    public CellularSecurityNotificationsPreferenceController(
+            @NonNull Context context, @NonNull String prefKey) {
+        super(context, prefKey);
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class);
+    }
+
+    /**
+     * Initialization based on a given subscription id.
+     *
+     * @param subId is the subscription id
+     * @return this instance after initialization
+     */
+    public CellularSecurityNotificationsPreferenceController init(@NonNull int subId) {
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(subId);
+        return this;
+    }
+
+    @Override
+    public void displayPreference(@NonNull PreferenceScreen screen) {
+        super.displayPreference(screen);
+    }
+
+    @Override
+    public int getAvailabilityStatus(int subId) {
+        if (!isSafetyCenterSupported()) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+
+        if (!areFlagsEnabled()) {
+            return UNSUPPORTED_ON_DEVICE;
+        }
+        if (mTelephonyManager == null) {
+            Log.w(LOG_TAG, "Telephony manager not yet initialized");
+            mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        }
+
+        // Checking for hardware support, i.e. IRadio AIDL version must be >= 2.2
+        try {
+            areNotificationsEnabled();
+        } catch (UnsupportedOperationException e) {
+            Log.i(LOG_TAG, "Cellular security notifications are unsupported: " + e.getMessage());
+            return UNSUPPORTED_ON_DEVICE;
+        }
+
+        return AVAILABLE;
+    }
+
+    /**
+     * Return {@code true} if cellular security notifications are on
+     *
+     * <p><b>NOTE:</b> This method returns the active state of the preference controller and is not
+     * the parameter passed into {@link #setChecked(boolean)}, which is instead the requested future
+     * state.
+     */
+    @Override
+    public boolean isChecked() {
+        if (!areFlagsEnabled()) {
+            return false;
+        }
+
+        try {
+            // Note: the default behavior for this toggle is disabled (as the underlying
+            // TelephonyManager APIs are disabled by default)
+            return areNotificationsEnabled();
+        } catch (Exception e) {
+            Log.e(LOG_TAG,
+                    "Failed isNullCipherNotificationsEnabled and "
+                            + "isCellularIdentifierDisclosureNotificationsEnabled."
+                            + "Defaulting toggle to checked = true. Exception: "
+                            + e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * Called when a user preference changes on the toggle. We pass this info on to the Telephony
+     * Framework so that the modem can be updated with the user's preference.
+     *
+     * <p>See {@link com.android.settings.core.TogglePreferenceController#setChecked(boolean)} for
+     * details.
+     *
+     * @param isChecked The toggle value that we're being requested to enforce. A value of {@code
+     *                  true} denotes that both (1) null cipher/integrity notifications, and
+     *                  (2) IMSI disclosure notifications will be enabled by the modem after this
+     *                  function completes, if they are not already.
+     */
+    @Override
+    public boolean setChecked(boolean isChecked) {
+        if (isChecked) {
+            Log.i(LOG_TAG, "Enabling cellular security notifications.");
+        } else {
+            Log.i(LOG_TAG, "Disabling cellular security notifications.");
+        }
+
+        // Check flag status
+        if (!areFlagsEnabled()) {
+            return false;
+        }
+
+        try {
+            setNotifications(isChecked);
+        } catch (Exception e) {
+            Log.e(LOG_TAG,
+                    "Failed setCellularIdentifierDisclosureNotificationEnabled or "
+                            + " setNullCipherNotificationsEnabled. Setting not updated. Exception: "
+                            + e.getMessage());
+            // Reset to defaults so we don't end up in an inconsistent state
+            setNotifications(!isChecked);
+            return false;
+        }
+        return true;
+    }
+
+    private void setNotifications(boolean isChecked) {
+        mTelephonyManager.setEnableCellularIdentifierDisclosureNotifications(isChecked);
+        mTelephonyManager.setNullCipherNotificationsEnabled(isChecked);
+    }
+
+    private boolean areNotificationsEnabled() {
+        return mTelephonyManager.isNullCipherNotificationsEnabled()
+            && mTelephonyManager.isCellularIdentifierDisclosureNotificationsEnabled();
+    }
+
+    private boolean areFlagsEnabled() {
+        if (!Flags.enableIdentifierDisclosureTransparencyUnsolEvents()
+                || !Flags.enableModemCipherTransparencyUnsolEvents()
+                || !Flags.enableIdentifierDisclosureTransparency()
+                || !Flags.enableModemCipherTransparency()) {
+            return false;
+        }
+        return true;
+    }
+
+    protected boolean isSafetyCenterSupported() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            return false;
+        }
+        mSafetyCenterManager = mContext.getSystemService(
+                SafetyCenterManager.class);
+        if (mSafetyCenterManager == null) {
+            return false;
+        }
+        return mSafetyCenterManager.isSafetyCenterEnabled();
+    }
+}
diff --git a/src/com/android/settings/network/telephony/CellularSecuritySettingsFragment.java b/src/com/android/settings/network/telephony/CellularSecuritySettingsFragment.java
new file mode 100644
index 0000000..3e37352
--- /dev/null
+++ b/src/com/android/settings/network/telephony/CellularSecuritySettingsFragment.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.network.telephony;
+
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/**
+ * Cellular Security features (insecure network notifications, network security controls, etc)
+ */
+@SearchIndexable
+public class CellularSecuritySettingsFragment extends DashboardFragment {
+
+    private static final String TAG = "CellularSecuritySettingsFragment";
+
+    public static final String KEY_CELLULAR_SECURITY_PREFERENCE = "cellular_security";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.CELLULAR_SECURITY_SETTINGS;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.cellular_security;
+    }
+
+    @Override
+    public void onCreatePreferences(Bundle bundle, String rootKey) {
+        super.onCreatePreferences(bundle, rootKey);
+        setPreferencesFromResource(R.xml.cellular_security, rootKey);
+    }
+
+    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider(R.xml.cellular_security);
+}
diff --git a/tests/unit/src/com/android/settings/network/CellularSecurityPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/CellularSecurityPreferenceControllerTest.java
new file mode 100644
index 0000000..59e10c3
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/CellularSecurityPreferenceControllerTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.safetycenter.SafetyCenterManager;
+import android.telephony.TelephonyManager;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.telephony.flags.Flags;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public final class CellularSecurityPreferenceControllerTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    private Preference mPreference;
+    private PreferenceScreen mPreferenceScreen;
+
+    private static final String PREF_KEY = "cellular_security_pref_controller_test";
+    private Context mContext;
+    private CellularSecurityPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        // Tests must be skipped if these conditions aren't met as they cannot be mocked
+        Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU);
+        SafetyCenterManager mSafetyCenterManager = InstrumentationRegistry.getInstrumentation()
+                .getContext().getSystemService(SafetyCenterManager.class);
+        Assume.assumeTrue(mSafetyCenterManager != null);
+        Assume.assumeTrue(mSafetyCenterManager.isSafetyCenterEnabled());
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+
+        doNothing().when(mContext).startActivity(any(Intent.class));
+
+        mController = new CellularSecurityPreferenceController(mContext, PREF_KEY);
+
+        mPreference = spy(new Preference(mContext));
+        mPreference.setKey(PREF_KEY);
+        mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+        mPreferenceScreen.addPreference(mPreference);
+    }
+
+    @Test
+    public void getAvailabilityStatus_hardwareSupported_shouldReturnTrue() {
+        // Enable telephony API flags for testing
+        enableFlags(true);
+
+        // Hardware support is enabled
+        doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled();
+        doReturn(true).when(mTelephonyManager)
+              .isCellularIdentifierDisclosureNotificationsEnabled();
+        doReturn(true).when(mTelephonyManager).isNullCipherAndIntegrityPreferenceEnabled();
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+
+        // Disable null cipher toggle API, should still be available
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isNullCipherAndIntegrityPreferenceEnabled();
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+
+        // Enable null cipher toggle API, disable notifications API, should still be available
+        doReturn(true).when(mTelephonyManager).isNullCipherAndIntegrityPreferenceEnabled();
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isNullCipherNotificationsEnabled();
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isCellularIdentifierDisclosureNotificationsEnabled();
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_noHardwareSupport_shouldReturnFalse() {
+        // Enable telephony API flags for testing
+        enableFlags(true);
+
+        // Hardware support is disabled
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isNullCipherNotificationsEnabled();
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isCellularIdentifierDisclosureNotificationsEnabled();
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isNullCipherAndIntegrityPreferenceEnabled();
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_flagsDisabled_shouldReturnFalse() {
+        // Both flags disabled
+        enableFlags(false);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+
+        // One flag is disabled
+        mSetFlagsRule.disableFlags(
+                Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void handlePreferenceTreeClick_safetyCenterSupported_shouldRedirectToSafetyCenter() {
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+        boolean prefHandled = mController.handlePreferenceTreeClick(mPreference);
+
+        assertThat(prefHandled).isTrue();
+        verify(mContext).startActivity(intentCaptor.capture());
+        assertThat(intentCaptor.getValue().getAction()).isEqualTo(Intent.ACTION_SAFETY_CENTER);
+    }
+
+    private void enableFlags(boolean enabled) {
+        if (enabled) {
+            mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.enableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY);
+            mSetFlagsRule.enableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY);
+        } else {
+            mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.disableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY);
+            mSetFlagsRule.disableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY);
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerControllerTest.java
new file mode 100644
index 0000000..f542209
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityEncryptionDividerControllerTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class CellularSecurityEncryptionDividerControllerTest {
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private Preference mPreference;
+    private PreferenceScreen mPreferenceScreen;
+
+    private static final String PREF_KEY = "cellular_security_encryption_divider_test";
+    private Context mContext;
+    private CellularSecurityEncryptionDividerController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+
+        mController = new CellularSecurityEncryptionDividerController(mContext, PREF_KEY);
+
+        mPreference = spy(new Preference(mContext));
+        mPreference.setKey(PREF_KEY);
+        mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+        mPreferenceScreen.addPreference(mPreference);
+    }
+
+    @Test
+    public void getAvailabilityStatus_hardwareSupported_shouldReturnAvailable() {
+        doReturn(true).when(mTelephonyManager).isNullCipherAndIntegrityPreferenceEnabled();
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_noHardwareSupport_shouldReturnUnsupported() {
+        // Hardware support is disabled
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isNullCipherAndIntegrityPreferenceEnabled();
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerControllerTest.java
new file mode 100644
index 0000000..4e2351f
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsDividerControllerTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Build;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.safetycenter.SafetyCenterManager;
+import android.telephony.TelephonyManager;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.telephony.flags.Flags;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class CellularSecurityNotificationsDividerControllerTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    private Preference mPreference;
+    private PreferenceScreen mPreferenceScreen;
+
+    private static final String PREF_KEY = "cellular_security_notifications_divider_test";
+    private Context mContext;
+    private CellularSecurityNotificationsDividerController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        // Tests must be skipped if these conditions aren't met as they cannot be mocked
+        Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU);
+        SafetyCenterManager mSafetyCenterManager = InstrumentationRegistry.getInstrumentation()
+                .getContext().getSystemService(SafetyCenterManager.class);
+        Assume.assumeTrue(mSafetyCenterManager != null);
+        Assume.assumeTrue(mSafetyCenterManager.isSafetyCenterEnabled());
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+
+        mController = new CellularSecurityNotificationsDividerController(mContext, PREF_KEY);
+
+        mPreference = spy(new Preference(mContext));
+        mPreference.setKey(PREF_KEY);
+        mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+        mPreferenceScreen.addPreference(mPreference);
+    }
+
+    @Test
+    public void getAvailabilityStatus_hardwareSupported_shouldReturnTrue() {
+        // Enable telephony API flags for testing
+        enableFlags(true);
+
+        // Hardware support is enabled
+        doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled();
+        doReturn(true).when(mTelephonyManager)
+              .isCellularIdentifierDisclosureNotificationsEnabled();
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_noHardwareSupport_shouldReturnFalse() {
+        // Enable telephony API flags for testing
+        enableFlags(true);
+
+        // Hardware support is disabled
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isNullCipherNotificationsEnabled();
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isCellularIdentifierDisclosureNotificationsEnabled();
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_flagsDisabled_shouldReturnFalse() {
+        // Both flags disabled
+        enableFlags(false);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+
+        // One flag is disabled
+        mSetFlagsRule.disableFlags(
+                Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    private void enableFlags(boolean enabled) {
+        if (enabled) {
+            mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.enableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY);
+            mSetFlagsRule.enableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY);
+        } else {
+            mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.disableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY);
+            mSetFlagsRule.disableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY);
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceControllerTest.java
new file mode 100644
index 0000000..8a72bd5
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/telephony/CellularSecurityNotificationsPreferenceControllerTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Build;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.safetycenter.SafetyCenterManager;
+import android.telephony.TelephonyManager;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.telephony.flags.Flags;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class CellularSecurityNotificationsPreferenceControllerTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    private Preference mPreference;
+    private PreferenceScreen mPreferenceScreen;
+
+    private static final String PREF_KEY = "cellular_security_notifications_pref_controller_test";
+    private Context mContext;
+    private CellularSecurityNotificationsPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        // Tests must be skipped if these conditions aren't met as they cannot be mocked
+        Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU);
+        SafetyCenterManager mSafetyCenterManager = InstrumentationRegistry.getInstrumentation()
+                .getContext().getSystemService(SafetyCenterManager.class);
+        Assume.assumeTrue(mSafetyCenterManager != null);
+        Assume.assumeTrue(mSafetyCenterManager.isSafetyCenterEnabled());
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+
+        mController = new CellularSecurityNotificationsPreferenceController(mContext, PREF_KEY);
+
+        mPreference = spy(new Preference(mContext));
+        mPreference.setKey(PREF_KEY);
+        mPreferenceScreen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+        mPreferenceScreen.addPreference(mPreference);
+    }
+
+    @Test
+    public void getAvailabilityStatus_hardwareSupported_shouldReturnTrue() {
+        // All flags enabled
+        enableFlags(true);
+
+        // Hardware support is enabled
+        doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled();
+        doReturn(true).when(mTelephonyManager)
+              .isCellularIdentifierDisclosureNotificationsEnabled();
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_noHardwareSupport_shouldReturnFalse() {
+        // All flags enabled
+        enableFlags(true);
+
+        // Hardware support is disabled
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isNullCipherNotificationsEnabled();
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isCellularIdentifierDisclosureNotificationsEnabled();
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_flagsDisabled_shouldReturnFalse() {
+        // All flags disabled
+        enableFlags(false);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+
+        // One flag is disabled
+        enableFlags(true);
+        mSetFlagsRule.disableFlags(
+                Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS);
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void setChecked_flagsDisabled_shouldReturnFalse() {
+        // Flags disabled
+        enableFlags(false);
+
+        // Hardware support is enabled
+        doNothing().when(mTelephonyManager).setNullCipherNotificationsEnabled(true);
+        doNothing().when(mTelephonyManager)
+              .setEnableCellularIdentifierDisclosureNotifications(true);
+        assertThat(mController.setChecked(false)).isFalse();
+        assertThat(mController.setChecked(true)).isFalse();
+
+        // Enable 1 flag, make sure it still fails
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS);
+        assertThat(mController.setChecked(false)).isFalse();
+        assertThat(mController.setChecked(true)).isFalse();
+    }
+
+    @Test
+    public void setChecked_hardwareDisabled_shouldReturnFalse() {
+        // Flags disabled
+        enableFlags(false);
+
+        // Hardware support is enabled
+        doNothing().when(mTelephonyManager).setNullCipherNotificationsEnabled(true);
+        doNothing().when(mTelephonyManager)
+                .setEnableCellularIdentifierDisclosureNotifications(true);
+        assertThat(mController.setChecked(true)).isFalse();
+
+        // Hardware support is enabled, called with false
+        doNothing().when(mTelephonyManager).setNullCipherNotificationsEnabled(false);
+        doNothing().when(mTelephonyManager)
+                .setEnableCellularIdentifierDisclosureNotifications(false);
+        assertThat(mController.setChecked(false)).isFalse();
+    }
+
+    @Test
+    public void setChecked_shouldReturnTrue() {
+        enableFlags(true);
+
+        // Hardware support is enabled, enabling the feature
+        doNothing().when(mTelephonyManager).setNullCipherNotificationsEnabled(true);
+        doNothing().when(mTelephonyManager)
+              .setEnableCellularIdentifierDisclosureNotifications(true);
+        assertThat(mController.setChecked(true)).isTrue();
+
+        // Hardware support is enabled, disabling the feature
+        doNothing().when(mTelephonyManager).setNullCipherNotificationsEnabled(false);
+        doNothing().when(mTelephonyManager)
+              .setEnableCellularIdentifierDisclosureNotifications(false);
+        assertThat(mController.setChecked(false)).isTrue();
+    }
+
+    @Test
+    public void isChecked_shouldReturnTrue() {
+        // Hardware support is enabled
+        doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled();
+        doReturn(true).when(mTelephonyManager)
+              .isCellularIdentifierDisclosureNotificationsEnabled();
+        assertThat(mController.isChecked()).isTrue();
+    }
+
+    @Test
+    public void isChecked_flagsDisabled_shouldReturnFalse() {
+        enableFlags(false);
+
+        // Hardware support is enabled
+        doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled();
+        doReturn(true).when(mTelephonyManager)
+              .isCellularIdentifierDisclosureNotificationsEnabled();
+
+        assertThat(mController.isChecked()).isFalse();
+    }
+
+    @Test
+    public void isChecked_hardwareUnsupported_shouldReturnFalse() {
+        enableFlags(true);
+
+        // Hardware support is disabled
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isNullCipherNotificationsEnabled();
+        doThrow(new UnsupportedOperationException("test")).when(mTelephonyManager)
+              .isCellularIdentifierDisclosureNotificationsEnabled();
+
+        assertThat(mController.isChecked()).isFalse();
+    }
+
+    @Test
+    public void isChecked_notificationsDisabled_shouldReturnFalse() {
+        enableFlags(true);
+
+        // Hardware support is enabled, but APIs are disabled
+        doReturn(false).when(mTelephonyManager).isNullCipherNotificationsEnabled();
+        doReturn(false).when(mTelephonyManager)
+              .isCellularIdentifierDisclosureNotificationsEnabled();
+        assertThat(mController.isChecked()).isFalse();
+
+        // Enable 1 API, should still return false
+        doReturn(true).when(mTelephonyManager).isNullCipherNotificationsEnabled();
+        assertThat(mController.isChecked()).isFalse();
+    }
+
+    private void enableFlags(boolean enabled) {
+        if (enabled) {
+            mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.enableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY);
+            mSetFlagsRule.enableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY);
+        } else {
+            mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.disableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS);
+            mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY);
+            mSetFlagsRule.disableFlags(
+                    Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY);
+        }
+    }
+}