Merge "[ToA] Move IO to background thread" into main
diff --git a/aconfig/settings_panel_flag_declarations.aconfig b/aconfig/settings_panel_flag_declarations.aconfig
deleted file mode 100644
index efab83e..0000000
--- a/aconfig/settings_panel_flag_declarations.aconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-package: "com.android.settings.flags"
-container: "system"
-
-flag {
-  name: "enable_volume_plus_quick_settings"
-  namespace: "pixel_cross_device_control"
-  description: "Gates whether to enable VolumePlus quick settings panel."
-  bug: "309052662"
-}
diff --git a/aconfig/settings_threadnetwork_flag_declarations.aconfig b/aconfig/settings_threadnetwork_flag_declarations.aconfig
new file mode 100644
index 0000000..e5448a9
--- /dev/null
+++ b/aconfig/settings_threadnetwork_flag_declarations.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.settings.flags"
+container: "system"
+
+flag {
+    name: "thread_settings_enabled"
+    namespace: "thread_network"
+    description: "Controls whether the Thread Settings UX is displayed"
+    bug: "329384658"
+}
diff --git a/res/drawable/color_contrast_preview_icon_edit_background.xml b/res/drawable/color_contrast_preview_icon_edit_background.xml
index 14c5f3c..61fe4fe 100644
--- a/res/drawable/color_contrast_preview_icon_edit_background.xml
+++ b/res/drawable/color_contrast_preview_icon_edit_background.xml
@@ -17,6 +17,6 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
        android:shape="rectangle" >
-    <solid android:color="?androidprv:attr/materialColorPrimary"/>
-    <corners android:radius="20dp" />
+    <solid android:color="?androidprv:attr/materialColorPrimaryContainer"/>
+    <corners android:radius="24dp" />
 </shape>
\ No newline at end of file
diff --git a/res/drawable/color_contrast_preview_icon_group_background.xml b/res/drawable/color_contrast_preview_icon_group_background.xml
index b8554c1..46cd40d 100644
--- a/res/drawable/color_contrast_preview_icon_group_background.xml
+++ b/res/drawable/color_contrast_preview_icon_group_background.xml
@@ -17,6 +17,6 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
        android:shape="oval" >
-    <solid android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+    <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest"/>
     <size android:width="36dp" android:height="36dp" />
 </shape>
\ No newline at end of file
diff --git a/res/drawable/ic_edit_24dp.xml b/res/drawable/ic_edit_24dp.xml
index c9dbfc3..e663b52 100644
--- a/res/drawable/ic_edit_24dp.xml
+++ b/res/drawable/ic_edit_24dp.xml
@@ -21,6 +21,6 @@
         android:viewportHeight="24"
         android:tint="?attr/colorControlNormal">
 <path
-    android:fillColor="?androidprv:attr/materialColorOnPrimary"
+    android:fillColor="?androidprv:attr/materialColorOnPrimaryContainer"
     android:pathData="M20.41,4.94l-1.35,-1.35c-0.78,-0.78 -2.05,-0.78 -2.83,0L3,16.82L3,21h4.18L20.41,7.77c0.79,-0.78 0.79,-2.05 0,-2.83zM6.41,19.06L5,19v-1.36l9.82,-9.82 1.41,1.41 -9.82,9.83z"/>
 </vector>
diff --git a/res/drawable/ic_star_24dp.xml b/res/drawable/ic_star_24dp.xml
index 38535e6..667db94 100644
--- a/res/drawable/ic_star_24dp.xml
+++ b/res/drawable/ic_star_24dp.xml
@@ -22,5 +22,5 @@
         android:tint="?attr/colorControlNormal">
     <path
         android:fillColor="?androidprv:attr/materialColorOnPrimary"
-        android:pathData="M14.43,10l-2.43,-8l-2.43,8l-7.57,0l6.18,4.41l-2.35,7.59l6.17,-4.69l6.18,4.69l-2.35,-7.59l6.17,-4.41z"/>
+        android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/>
 </vector>
diff --git a/res/layout/accessibility_color_contrast_preview.xml b/res/layout/accessibility_color_contrast_preview.xml
index 2646709..44f7584 100644
--- a/res/layout/accessibility_color_contrast_preview.xml
+++ b/res/layout/accessibility_color_contrast_preview.xml
@@ -104,6 +104,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginLeft="10dp"
+        android:paddingHorizontal="4dp"
         android:textColor="?androidprv:attr/materialColorOnSurface"
         android:background="@drawable/color_contrast_preview_tag_background"
         android:textSize="11sp"
@@ -164,7 +165,9 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:src="@drawable/ic_article_filled_24dp"
-        android:padding="6dp"
+        android:paddingStart="8dp"
+        android:paddingEnd="6dp"
+        android:paddingVertical="6dp"
         app:layout_constraintStart_toStartOf="@+id/email_title"
         app:layout_constraintTop_toBottomOf="@+id/email_body" />
 
@@ -172,7 +175,7 @@
         android:id="@+id/email_attachment"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:paddingRight="8dp"
+        android:paddingEnd="10dp"
         android:textColor="?androidprv:attr/materialColorOnTertiaryContainer"
         android:textSize="12sp"
         app:layout_constraintStart_toEndOf="@+id/ic_article_filled"
diff --git a/res/layout/accessibility_color_contrast_selector.xml b/res/layout/accessibility_color_contrast_selector.xml
index f7ba28b..38bcf7a 100644
--- a/res/layout/accessibility_color_contrast_selector.xml
+++ b/res/layout/accessibility_color_contrast_selector.xml
@@ -20,8 +20,7 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:orientation="vertical"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+    android:layout_height="wrap_content">
 
     <FrameLayout
         android:layout_width="match_parent"
@@ -36,6 +35,7 @@
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:layout_marginBottom="16dp"
         android:orientation="horizontal">
 
         <Space
diff --git a/res/layout/private_space_education_screen.xml b/res/layout/private_space_education_screen.xml
index 8fb486e..6b65103 100644
--- a/res/layout/private_space_education_screen.xml
+++ b/res/layout/private_space_education_screen.xml
@@ -93,10 +93,19 @@
                 android:layout_height="18dp"
                 android:src="@drawable/ic_info_outline_24dp" />
             <TextView
+                android:id="@+id/info"
                 style="@style/PrivateSpaceBulletPointTextFontStyle"
                 android:textSize = "14sp"
                 android:layout_toRightOf="@+id/infoIcon"
                 android:text="@string/private_space_apps_permission_text"/>
+            <TextView
+                android:id="@+id/learn_more"
+                style="@style/PrivateSpaceSetupSubHeaderStyle"
+                android:layout_below="@id/info"
+                android:layout_alignLeft="@+id/info"
+                android:paddingTop="24dp"
+                android:paddingLeft="16dp"
+                android:text="@string/private_space_learn_more_text"/>
         </RelativeLayout>
     </LinearLayout>
     </ScrollView>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 939bba2..409805a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1317,6 +1317,9 @@
     <string name="private_space_install_apps_text"><b>Install apps</b>\nYour private space has its own Play Store so you can install apps easily.</string>
     <!-- This is info text to help explain in private space setup screen that the permissions granted to private space apps will not be shown in settings when private space is locked. [CHAR LIMIT=NONE] -->
     <string name="private_space_apps_permission_text">Apps in your private space won\'t appear in permission manager, privacy dashboard, and other settings when your private space is locked.\n\nYour private space can\'t be moved to a new device. You\'ll need to set up another private space if you want to use it on another device.\n\nAnyone that connects your device to a computer or installs harmful apps on your device may be able to access your private space.</string>
+    <!-- Private space footer link content description [CHAR LIMIT=40] -->
+    <string name="private_space_learn_more_text">Learn more about private space</string>
+    <string name="private_space_learn_more_url" translatable="false">https://support.google.com/android?p=private_space</string>
     <!-- Text shown at the bottom in private space auto advancing  screens. [CHAR LIMIT=60] -->
     <string name="private_space_setting_up_text">Setting up private space\u2026</string>
     <!-- Title for private space setup in auto advancing screen informing private space notifications are hidden when locked. [CHAR LIMIT=NONE] -->
@@ -4989,7 +4992,7 @@
     <!-- Title for the accessibility preference to high contrast text. [CHAR LIMIT=35] -->
     <string name="accessibility_toggle_maximize_text_contrast_preference_title">Maximize text contrast</string>
     <!-- Summary for the accessibility preference to high contrast text. [CHAR LIMIT=NONE] -->
-    <string name="accessibility_toggle_maximize_text_contrast_preference_summary">Change text color to black or white to increase contrast with the background.</string>
+    <string name="accessibility_toggle_maximize_text_contrast_preference_summary">Add a black or white background around text to increase contrast</string>
     <!-- Title for the accessibility preference to auto update screen magnification. [CHAR LIMIT=35] -->
     <string name="accessibility_toggle_screen_magnification_auto_update_preference_title">Auto
         update screen magnification</string>
@@ -12000,6 +12003,9 @@
     <string name="confirm_format_ext4_text">16K developer option is supported with ext4 filesystem. Device will be wiped and filesystem will be changed to ext4 after confirmation.</string>
     <!-- Toast on failure to reformat data to ext4 -->
     <string name="format_ext4_failure_toast">Failed to reformat and wipe the data partiton to ext4.</string>
+    <!-- Dialog to OEM unlock the device before using 16K developer option -->
+    <string name="confirm_oem_unlock_for_16k_title">OEM unlock required</string>
+    <string name="confirm_oem_unlock_for_16k_text">Device needs to be OEM unlocked before using 16K developer option. OEM unlock will also require formatting userdata. Please OEM unlock the device and try again.</string>
 
     <!-- DSU Loader. Do not translate. -->
 
diff --git a/res/values/themes_suw.xml b/res/values/themes_suw.xml
index 71d9fcb..9efac28 100644
--- a/res/values/themes_suw.xml
+++ b/res/values/themes_suw.xml
@@ -231,7 +231,7 @@
     <style name="SuwAlertDialogThemeCompat" parent="@style/Theme.AppCompat.Dialog.Alert">
         <!-- Referenced SudThemeGlifV3 style -->
         <item name="android:textAllCaps">false</item>
-
+        <item name="android:colorBackground">@android:color/system_neutral1_900</item>
         <item name="android:windowSoftInputMode">adjustResize</item>
         <!-- copied from Theme.DeviceDefault.Dialog.Alert -->
         <item name="colorAccent">@*android:color/accent_device_default_dark</item>
@@ -243,7 +243,7 @@
     <style name="SuwAlertDialogThemeCompat.Light" parent="@style/Theme.AppCompat.Light.Dialog.Alert">
         <!-- Referenced SudThemeGlifV3.Light style -->
         <item name="android:textAllCaps">false</item>
-
+        <item name="android:colorBackground">@android:color/system_neutral1_50</item>
         <item name="android:windowSoftInputMode">adjustResize</item>
         <!-- copied from Theme.DeviceDefault.Light.Dialog.Alert -->
         <item name="colorAccent">@*android:color/accent_device_default_light</item>
diff --git a/res/xml/private_space_settings.xml b/res/xml/private_space_settings.xml
index 93c016b..86537cc 100644
--- a/res/xml/private_space_settings.xml
+++ b/res/xml/private_space_settings.xml
@@ -81,10 +81,12 @@
 
     </PreferenceCategory>
 
-    <com.android.settings.accessibility.AccessibilityFooterPreference
+    <com.android.settingslib.widget.FooterPreference
         android:key="private_space_footer"
         android:title="@string/private_space_apps_permission_text"
         android:selectable="false"
-        settings:searchable="false"/>
+        settings:searchable="false"
+        settings:controller="com.android.settings.privatespace.PrivateSpaceFooterPreferenceController"/>
+
 
 </PreferenceScreen>
diff --git a/src/com/android/settings/RegulatoryInfoDisplayActivity.kt b/src/com/android/settings/RegulatoryInfoDisplayActivity.kt
index fdf66c3..6b5ccc7 100644
--- a/src/com/android/settings/RegulatoryInfoDisplayActivity.kt
+++ b/src/com/android/settings/RegulatoryInfoDisplayActivity.kt
@@ -23,6 +23,7 @@
 import android.widget.TextView
 import androidx.appcompat.app.AlertDialog
 import com.android.settings.deviceinfo.regulatory.RegulatoryInfo.getRegulatoryInfo
+import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
 
 /**
  * [Activity] that displays regulatory information for the "Regulatory information"
@@ -53,8 +54,8 @@
             return
         }
 
-        val regulatoryText = resources.getText(R.string.regulatory_info_text)
-        if (regulatoryText.isNotEmpty()) {
+        val regulatoryText = getRegulatoryText()
+        if (!regulatoryText.isNullOrEmpty()) {
             builder.setMessage(regulatoryText)
             val dialog = builder.show()
             // we have to show the dialog first, or the setGravity() call will throw a NPE
@@ -64,4 +65,10 @@
             finish()
         }
     }
+
+    private fun getRegulatoryText(): CharSequence? {
+        val regulatoryInfoText = resources.getText(R.string.regulatory_info_text)
+        if (regulatoryInfoText.isNotBlank()) return regulatoryInfoText
+        return featureFactory.hardwareInfoFeatureProvider?.countryIfOriginLabel
+    }
 }
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java b/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java
index 52adc4d..f1aaa53 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPickerPrivate.java
@@ -32,6 +32,8 @@
 
     /** Returns whether the user is handled by this fragment. */
     public static boolean isUserHandledByFragment(UserManager userManager) {
-        return android.os.Flags.allowPrivateProfile() && userManager.isPrivateProfile();
+        return android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                && userManager.isPrivateProfile();
     }
 }
diff --git a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt
index f5c0a87..1c01750 100644
--- a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt
+++ b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt
@@ -34,9 +34,9 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.preference.Preference
 import androidx.preference.PreferenceScreen
-import com.android.net.thread.platform.flags.Flags
 import com.android.settings.R
 import com.android.settings.core.TogglePreferenceController
+import com.android.settings.flags.Flags
 import java.util.concurrent.Executor
 
 /** Controller for the "Thread" toggle in "Connected devices > Connection preferences".  */
@@ -110,7 +110,7 @@
     }
 
     override fun getAvailabilityStatus(): Int {
-        return if (!Flags.threadEnabledPlatform()) {
+        return if (!Flags.threadSettingsEnabled()) {
             CONDITIONALLY_UNAVAILABLE
         } else if (!isThreadSupportedOnDevice) {
             UNSUPPORTED_ON_DEVICE
diff --git a/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt b/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
index 5abde31..0055463 100644
--- a/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
+++ b/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
@@ -164,6 +164,7 @@
 
     private fun shouldSkipProfile(userManager : UserManager, userHandle: UserHandle): Boolean {
         if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()
                 && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) {
             return (userManager.isQuietModeEnabled(userHandle)
                     && userManager.getUserProperties(userHandle).showInQuietMode
diff --git a/src/com/android/settings/development/Enable16KOemUnlockDialog.java b/src/com/android/settings/development/Enable16KOemUnlockDialog.java
new file mode 100644
index 0000000..65690df
--- /dev/null
+++ b/src/com/android/settings/development/Enable16KOemUnlockDialog.java
@@ -0,0 +1,77 @@
+/*
+ * 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.development;
+
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+/** Dialog when user interacts 16K pages developer option and device is not OEM unlocked */
+public class Enable16KOemUnlockDialog extends InstrumentedDialogFragment
+        implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+
+    public static final String TAG = "Enable16KOemUnlockDialog";
+
+    /** This method is used to prompt user to do OEM unlock before using 16k */
+    public static void show(@NonNull Fragment hostFragment) {
+        final FragmentManager manager = hostFragment.getActivity().getSupportFragmentManager();
+        Fragment existingFragment = manager.findFragmentByTag(TAG);
+        if (existingFragment == null) {
+            existingFragment = new Enable16KOemUnlockDialog();
+        }
+
+        if (existingFragment instanceof Enable16KOemUnlockDialog) {
+            existingFragment.setTargetFragment(hostFragment, 0 /* requestCode */);
+            ((Enable16KOemUnlockDialog) existingFragment).show(manager, TAG);
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.DIALOG_ENABLE_16K_PAGES;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        return new AlertDialog.Builder(getActivity())
+                .setTitle(R.string.confirm_oem_unlock_for_16k_title)
+                .setMessage(R.string.confirm_oem_unlock_for_16k_text)
+                .setPositiveButton(android.R.string.ok, this /* onClickListener */)
+                .create();
+    }
+
+    @Override
+    public void onClick(@NonNull DialogInterface dialog, int buttonId) {
+        // Do nothing. OEM unlock has to be done by user
+    }
+
+    @Override
+    public void onDismiss(@NonNull DialogInterface dialog) {
+        super.onDismiss(dialog);
+    }
+}
diff --git a/src/com/android/settings/development/Enable16kPagesPreferenceController.java b/src/com/android/settings/development/Enable16kPagesPreferenceController.java
index 7049e79..bed5c04 100644
--- a/src/com/android/settings/development/Enable16kPagesPreferenceController.java
+++ b/src/com/android/settings/development/Enable16kPagesPreferenceController.java
@@ -28,7 +28,10 @@
 import android.os.UpdateEngine;
 import android.os.UpdateEngineStable;
 import android.os.UpdateEngineStableCallback;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
+import android.service.oemlock.OemLockManager;
 import android.util.Log;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
@@ -116,6 +119,12 @@
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         mEnable16k = (Boolean) newValue;
+        // Prompt user to do oem unlock first
+        if (!isDeviceOEMUnlocked()) {
+            Enable16KOemUnlockDialog.show(mFragment);
+            return false;
+        }
+
         if (isDataf2fs()) {
             EnableExt4WarningDialog.show(mFragment, this);
             return false;
@@ -418,4 +427,30 @@
 
         return false;
     }
+
+    private boolean isDeviceOEMUnlocked() {
+        // OEM unlock is checked for bootloader, carrier and user. Check all three to ensure
+        // that device is unlocked and it is also allowed by user as well as carrier
+        final OemLockManager oemLockManager = mContext.getSystemService(OemLockManager.class);
+        final UserManager userManager = mContext.getSystemService(UserManager.class);
+        if (oemLockManager == null || userManager == null) {
+            Log.e(TAG, "Required services not found on device to check for OEM unlock state.");
+            return false;
+        }
+
+        // If either of device or carrier is not allowed to unlock, return false
+        if (!oemLockManager.isDeviceOemUnlocked()
+                || !oemLockManager.isOemUnlockAllowedByCarrier()) {
+            Log.e(TAG, "Device is not OEM unlocked or it is not allowed by carrier");
+            return false;
+        }
+
+        final UserHandle userHandle = UserHandle.of(UserHandle.myUserId());
+        if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_FACTORY_RESET, userHandle)) {
+            Log.e(TAG, "Factory reset is not allowed for user.");
+            return false;
+        }
+
+        return true;
+    }
 }
diff --git a/src/com/android/settings/deviceinfo/hardwareinfo/HardwareInfoFeatureProvider.kt b/src/com/android/settings/deviceinfo/hardwareinfo/HardwareInfoFeatureProvider.kt
index 400ece9..e9866d7 100644
--- a/src/com/android/settings/deviceinfo/hardwareinfo/HardwareInfoFeatureProvider.kt
+++ b/src/com/android/settings/deviceinfo/hardwareinfo/HardwareInfoFeatureProvider.kt
@@ -23,4 +23,9 @@
      * Returns the manufactured year
      */
     val manufacturedYear: String?
-}
\ No newline at end of file
+
+    /**
+     * The country of origin label.
+     */
+    val countryIfOriginLabel: String
+}
diff --git a/src/com/android/settings/deviceinfo/hardwareinfo/HardwareInfoFeatureProviderImpl.kt b/src/com/android/settings/deviceinfo/hardwareinfo/HardwareInfoFeatureProviderImpl.kt
deleted file mode 100644
index 54a112b..0000000
--- a/src/com/android/settings/deviceinfo/hardwareinfo/HardwareInfoFeatureProviderImpl.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.settings.deviceinfo.hardwareinfo
-
-/**
- * Feature provider for hardware info
- */
-object HardwareInfoFeatureProviderImpl : HardwareInfoFeatureProvider {
-    override val manufacturedYear: String?
-        get() = null
-}
\ No newline at end of file
diff --git a/src/com/android/settings/deviceinfo/hardwareinfo/ManufacturedYearPreferenceController.kt b/src/com/android/settings/deviceinfo/hardwareinfo/ManufacturedYearPreferenceController.kt
index 92d7733..9d1a826 100644
--- a/src/com/android/settings/deviceinfo/hardwareinfo/ManufacturedYearPreferenceController.kt
+++ b/src/com/android/settings/deviceinfo/hardwareinfo/ManufacturedYearPreferenceController.kt
@@ -22,7 +22,8 @@
 /** Preference controller for Manufactured Year. */
 class ManufacturedYearPreferenceController(context: Context, preferenceKey: String) :
     BasePreferenceController(context, preferenceKey) {
-    private val year: String? = featureFactory.hardwareInfoFeatureProvider.manufacturedYear
+
+    private val year: String? = featureFactory.hardwareInfoFeatureProvider?.manufacturedYear
 
     override fun getAvailabilityStatus(): Int =
         if (!year.isNullOrEmpty()) AVAILABLE else UNSUPPORTED_ON_DEVICE
diff --git a/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfo.kt b/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfo.kt
index e26e061..2982e47 100644
--- a/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfo.kt
+++ b/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfo.kt
@@ -23,8 +23,7 @@
 import androidx.annotation.DrawableRes
 import androidx.annotation.VisibleForTesting
 import com.android.settings.R
-
-
+import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
 
 /** To load Regulatory Info from device. */
 object RegulatoryInfo {
@@ -38,10 +37,10 @@
 
     /** Gets the regulatory drawable. */
     fun Context.getRegulatoryInfo(): Drawable? {
-        val sku = getSku()
+        val sku = getSku().lowercase()
         if (sku.isNotBlank()) {
             // When hardware coo property exists, use regulatory_info_<sku>_<coo> resource if valid.
-            val coo = getCoo()
+            val coo = getCoo().lowercase()
             if (coo.isNotBlank()) {
                 getRegulatoryInfo("${REGULATORY_INFO_RESOURCE}_${sku}_$coo")?.let { return it }
             }
@@ -51,9 +50,9 @@
         return getRegulatoryInfo(REGULATORY_INFO_RESOURCE)
     }
 
-    private fun getCoo(): String = SystemProperties.get(KEY_COO).lowercase()
+    fun getCoo(): String = SystemProperties.get(KEY_COO)
 
-    private fun getSku(): String = SystemProperties.get(KEY_SKU).lowercase()
+    fun getSku(): String = SystemProperties.get(KEY_SKU)
 
     private fun Context.getRegulatoryInfo(fileName: String): Drawable? {
         val overlayPackageName =
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index 51dce26..5e6d7d5 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -556,9 +556,11 @@
                 String.format(
                         "getBatterySinceLastFullChargeUsageData() size=%d time=%d/ms",
                         batteryHistoryMap.size(), (System.currentTimeMillis() - start)));
-
         final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageData =
-                DataProcessor.getBatteryUsageData(context, batteryHistoryMap);
+                DataProcessor.getBatteryUsageData(
+                        context,
+                        new UserIdsSeries(context, /* mainUserOnly= */ false),
+                        batteryHistoryMap);
         if (batteryUsageData == null) {
             return null;
         }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
index fb5b9a1..d1bf0d2 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -84,12 +84,12 @@
     }
 
     @VisibleForTesting
-    static void loadAppUsageData(final Context context) {
+    static void loadAppUsageData(final Context context, final UserIdsSeries userIdsSeries) {
         final long start = System.currentTimeMillis();
         final Map<Long, UsageEvents> appUsageEvents =
                 sFakeAppUsageEventsSupplier != null
                         ? sFakeAppUsageEventsSupplier.get()
-                        : DataProcessor.getAppUsageEvents(context);
+                        : DataProcessor.getAppUsageEvents(context, userIdsSeries);
         if (appUsageEvents == null) {
             Log.w(TAG, "loadAppUsageData() returns null");
             return;
@@ -113,13 +113,15 @@
         DatabaseUtils.sendAppUsageEventData(context, appUsageEventList);
     }
 
-    private static void preprocessBatteryUsageSlots(final Context context) {
+    private static void preprocessBatteryUsageSlots(
+            final Context context, final UserIdsSeries userIdsSeries) {
         final long start = System.currentTimeMillis();
         final Handler handler = new Handler(Looper.getMainLooper());
         final BatteryLevelData batteryLevelData =
                 DataProcessManager.getBatteryLevelData(
                         context,
                         handler,
+                        userIdsSeries,
                         /* isFromPeriodJob= */ true,
                         batteryDiffDataMap -> {
                             DatabaseUtils.sendBatteryUsageSlotData(
@@ -162,8 +164,12 @@
             loadBatteryStatsData(context, isFullChargeStart);
             if (!isFullChargeStart) {
                 // No app usage data or battery diff data at this time.
-                loadAppUsageData(context);
-                preprocessBatteryUsageSlots(context);
+                final UserIdsSeries userIdsSeries =
+                        new UserIdsSeries(context, /* mainUserOnly= */ true);
+                if (!userIdsSeries.isCurrentUserLocked()) {
+                    loadAppUsageData(context, userIdsSeries);
+                    preprocessBatteryUsageSlots(context, userIdsSeries);
+                }
             }
             Log.d(
                     TAG,
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
index b3bcb47..3e8df61 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
@@ -21,8 +21,6 @@
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.UserHandle;
-import android.os.UserManager;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -30,7 +28,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.settings.Utils;
 
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -82,7 +79,7 @@
     private final long mLastFullChargeTimestamp;
     private final Context mContext;
     private final Handler mHandler;
-    private final UserManager mUserManager;
+    private final UserIdsSeries mUserIdsSeries;
     private final OnBatteryDiffDataMapLoadedListener mCallbackFunction;
     private final List<AppUsageEvent> mAppUsageEventList = new ArrayList<>();
     private final List<BatteryEvent> mBatteryEventList = new ArrayList<>();
@@ -123,6 +120,7 @@
     DataProcessManager(
             Context context,
             Handler handler,
+            final UserIdsSeries userIdsSeries,
             final long rawStartTimestamp,
             final long lastFullChargeTimestamp,
             @NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction,
@@ -130,7 +128,7 @@
             @NonNull final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
         mContext = context.getApplicationContext();
         mHandler = handler;
-        mUserManager = mContext.getSystemService(UserManager.class);
+        mUserIdsSeries = userIdsSeries;
         mRawStartTimestamp = rawStartTimestamp;
         mLastFullChargeTimestamp = lastFullChargeTimestamp;
         mCallbackFunction = callbackFunction;
@@ -142,10 +140,11 @@
     DataProcessManager(
             Context context,
             Handler handler,
+            final UserIdsSeries userIdsSeries,
             @NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction) {
         mContext = context.getApplicationContext();
         mHandler = handler;
-        mUserManager = mContext.getSystemService(UserManager.class);
+        mUserIdsSeries = userIdsSeries;
         mCallbackFunction = callbackFunction;
         mRawStartTimestamp = 0L;
         mLastFullChargeTimestamp = 0L;
@@ -175,10 +174,18 @@
                 // Loads the latest app usage list from the service.
                 loadCurrentAppUsageList();
                 // Loads existing battery usage slots from database.
-                loadBatteryUsageSlotList();
+                if (mUserIdsSeries.isMainUserProfileOnly()) {
+                    loadBatteryUsageSlotList();
+                } else {
+                    mIsBatteryUsageSlotLoaded = true;
+                }
             }
             // Loads app usage list from database.
-            loadDatabaseAppUsageList();
+            if (mUserIdsSeries.isMainUserProfileOnly()) {
+                loadDatabaseAppUsageList();
+            } else {
+                mIsDatabaseAppUsageLoaded = true;
+            }
             // Loads the battery event list from database.
             loadPowerConnectionBatteryEventList();
         } else {
@@ -264,6 +271,7 @@
     private void loadCurrentAppUsageList() {
         new AsyncTask<Void, Void, List<AppUsageEvent>>() {
             @Override
+            @Nullable
             protected List<AppUsageEvent> doInBackground(Void... voids) {
                 if (!shouldLoadAppUsageData()) {
                     Log.d(TAG, "not loadCurrentAppUsageList");
@@ -271,33 +279,21 @@
                 }
                 final long startTime = System.currentTimeMillis();
                 // Loads the current battery usage data from the battery stats service.
-                final int currentUserId = getCurrentUserId();
-                final int workProfileUserId = getWorkProfileUserId();
-                final UsageEvents usageEventsForCurrentUser =
-                        DataProcessor.getAppUsageEventsForUser(
-                                mContext, currentUserId, mRawStartTimestamp);
-                // If fail to load usage events for current user, return null directly and screen-on
-                // time will not be shown in the UI.
-                if (usageEventsForCurrentUser == null) {
-                    Log.w(TAG, "usageEventsForCurrentUser is null");
-                    return null;
-                }
-                UsageEvents usageEventsForWorkProfile = null;
-                if (workProfileUserId != Integer.MIN_VALUE) {
-                    usageEventsForWorkProfile =
-                            DataProcessor.getAppUsageEventsForUser(
-                                    mContext, workProfileUserId, mRawStartTimestamp);
-                } else {
-                    Log.d(TAG, "there is no work profile");
-                }
-
                 final Map<Long, UsageEvents> usageEventsMap = new ArrayMap<>();
-                usageEventsMap.put(Long.valueOf(currentUserId), usageEventsForCurrentUser);
-                if (usageEventsForWorkProfile != null) {
-                    Log.d(TAG, "usageEventsForWorkProfile is null");
-                    usageEventsMap.put(Long.valueOf(workProfileUserId), usageEventsForWorkProfile);
+                for (int userId : mUserIdsSeries.getVisibleUserIds()) {
+                    final UsageEvents usageEventsForCurrentUser =
+                            DataProcessor.getCurrentAppUsageEventsForUser(
+                                    mContext, mUserIdsSeries, userId, mRawStartTimestamp);
+                    if (usageEventsForCurrentUser == null) {
+                        // If fail to load usage events for any user, return null directly and
+                        // screen-on time will not be shown in the UI.
+                        if (userId == mUserIdsSeries.getCurrentUserId()) {
+                            return null;
+                        }
+                    } else {
+                        usageEventsMap.put(Long.valueOf(userId), usageEventsForCurrentUser);
+                    }
                 }
-
                 final List<AppUsageEvent> appUsageEventList =
                         DataProcessor.generateAppUsageEventListFromUsageEvents(
                                 mContext, usageEventsMap);
@@ -337,7 +333,7 @@
                         DatabaseUtils.getAppUsageEventForUsers(
                                 mContext,
                                 Calendar.getInstance(),
-                                getCurrentUserIds(),
+                                mUserIdsSeries.getVisibleUserIds(),
                                 mRawStartTimestamp);
                 Log.d(
                         TAG,
@@ -435,6 +431,7 @@
                 final Map<Long, BatteryDiffData> batteryDiffDataMap =
                         DataProcessor.getBatteryDiffDataMapFromStatsService(
                                 mContext,
+                                mUserIdsSeries,
                                 mRawStartTimestamp,
                                 getSystemAppsPackageNames(),
                                 getSystemAppsUids());
@@ -514,6 +511,7 @@
                 batteryDiffDataMap.putAll(
                         DataProcessor.getBatteryDiffDataMap(
                                 mContext,
+                                mUserIdsSeries,
                                 mHourlyBatteryLevelsPerDay,
                                 mBatteryHistoryMap,
                                 mAppUsagePeriodMap,
@@ -546,9 +544,8 @@
         if (!mShowScreenOnTime) {
             return false;
         }
-        final int currentUserId = getCurrentUserId();
         // If current user is locked, no need to load app usage data from service or database.
-        if (mUserManager == null || !mUserManager.isUserUnlocked(currentUserId)) {
+        if (mUserIdsSeries.isCurrentUserLocked()) {
             Log.d(TAG, "shouldLoadAppUsageData: false, current user is locked");
             mShowScreenOnTime = false;
             return false;
@@ -556,26 +553,6 @@
         return true;
     }
 
-    // Returns the list of current user id and work profile id if exists.
-    private List<Integer> getCurrentUserIds() {
-        final List<Integer> userIds = new ArrayList<>();
-        userIds.add(getCurrentUserId());
-        final int workProfileUserId = getWorkProfileUserId();
-        if (workProfileUserId != Integer.MIN_VALUE) {
-            userIds.add(workProfileUserId);
-        }
-        return userIds;
-    }
-
-    private int getCurrentUserId() {
-        return mContext.getUserId();
-    }
-
-    private int getWorkProfileUserId() {
-        final UserHandle userHandle = Utils.getManagedProfile(mUserManager);
-        return userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
-    }
-
     private synchronized Set<String> getSystemAppsPackageNames() {
         if (mSystemAppsPackageNames == null) {
             mSystemAppsPackageNames = DataProcessor.getSystemAppsPackageNames(mContext);
@@ -599,6 +576,7 @@
     public static BatteryLevelData getBatteryLevelData(
             Context context,
             @Nullable Handler handler,
+            final UserIdsSeries userIdsSeries,
             final boolean isFromPeriodJob,
             final OnBatteryDiffDataMapLoadedListener onBatteryUsageMapLoadedListener) {
         final long start = System.currentTimeMillis();
@@ -610,13 +588,14 @@
                         lastFullChargeTime,
                         DatabaseUtils.BATTERY_LEVEL_RECORD_EVENTS);
         final long startTimestamp =
-                batteryLevelRecordEvents.isEmpty()
+                (batteryLevelRecordEvents.isEmpty() || !userIdsSeries.isMainUserProfileOnly())
                         ? lastFullChargeTime
                         : batteryLevelRecordEvents.get(0).getTimestamp();
         final BatteryLevelData batteryLevelData =
                 getPeriodBatteryLevelData(
                         context,
                         handler,
+                        userIdsSeries,
                         startTimestamp,
                         lastFullChargeTime,
                         isFromPeriodJob,
@@ -636,6 +615,7 @@
     private static BatteryLevelData getPeriodBatteryLevelData(
             Context context,
             @Nullable Handler handler,
+            final UserIdsSeries userIdsSeries,
             final long startTimestamp,
             final long lastFullChargeTime,
             final boolean isFromPeriodJob,
@@ -663,7 +643,9 @@
                                 lastFullChargeTime);
         if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
             Log.d(TAG, "batteryHistoryMap is null in getPeriodBatteryLevelData()");
-            new DataProcessManager(context, handler, onBatteryDiffDataMapLoadedListener).start();
+            new DataProcessManager(
+                            context, handler, userIdsSeries, onBatteryDiffDataMapLoadedListener)
+                    .start();
             return null;
         }
 
@@ -675,7 +657,9 @@
                 DataProcessor.getLevelDataThroughProcessedHistoryMap(
                         context, processedBatteryHistoryMap);
         if (batteryLevelData == null) {
-            new DataProcessManager(context, handler, onBatteryDiffDataMapLoadedListener).start();
+            new DataProcessManager(
+                            context, handler, userIdsSeries, onBatteryDiffDataMapLoadedListener)
+                    .start();
             Log.d(TAG, "getBatteryLevelData() returns null");
             return null;
         }
@@ -684,6 +668,7 @@
         new DataProcessManager(
                         context,
                         handler,
+                        userIdsSeries,
                         startTimestamp,
                         lastFullChargeTime,
                         onBatteryDiffDataMapLoadedListener,
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
index 2ef12f1..da8481d 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -28,7 +28,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.os.BatteryConsumer;
 import android.os.BatteryStatsManager;
 import android.os.BatteryUsageStats;
@@ -52,7 +51,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.PowerProfile;
-import com.android.settings.Utils;
 import com.android.settings.fuelgauge.BatteryUtils;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -134,6 +132,7 @@
     @Nullable
     public static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageData(
             Context context,
+            UserIdsSeries userIdsSeries,
             @Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
         if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
             Log.d(TAG, "getBatteryLevelData() returns null");
@@ -161,6 +160,7 @@
                         context,
                         getBatteryDiffDataMap(
                                 context,
+                                userIdsSeries,
                                 batteryLevelData.getHourlyBatteryLevelsPerDay(),
                                 processedBatteryHistoryMap,
                                 /* appUsagePeriodMap= */ null,
@@ -183,24 +183,21 @@
 
     /** Gets the {@link UsageEvents} from system service for all unlocked users. */
     @Nullable
-    public static Map<Long, UsageEvents> getAppUsageEvents(Context context) {
+    public static Map<Long, UsageEvents> getAppUsageEvents(
+            Context context, UserIdsSeries userIdsSeries) {
         final long start = System.currentTimeMillis();
         context = DatabaseUtils.getParentContext(context);
         if (context == null) {
             return null;
         }
         final Map<Long, UsageEvents> resultMap = new ArrayMap();
-        final UserManager userManager = context.getSystemService(UserManager.class);
-        if (userManager == null) {
-            return null;
-        }
         final long sixDaysAgoTimestamp =
                 DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance());
-        for (final UserInfo user : userManager.getAliveUsers()) {
+        for (final int userId : userIdsSeries.getVisibleUserIds()) {
             final UsageEvents events =
-                    getAppUsageEventsForUser(context, userManager, user.id, sixDaysAgoTimestamp);
+                    getAppUsageEventsForUser(context, userIdsSeries, userId, sixDaysAgoTimestamp);
             if (events != null) {
-                resultMap.put(Long.valueOf(user.id), events);
+                resultMap.put(Long.valueOf(userId), events);
             }
         }
         final long elapsedTime = System.currentTimeMillis() - start;
@@ -212,22 +209,21 @@
 
     /** Gets the {@link UsageEvents} from system service for the specific user. */
     @Nullable
-    public static UsageEvents getAppUsageEventsForUser(
-            Context context, final int userID, final long startTimestampOfLevelData) {
+    public static UsageEvents getCurrentAppUsageEventsForUser(
+            Context context,
+            final UserIdsSeries userIdsSeries,
+            final int userID,
+            final long startTimestampOfLevelData) {
         final long start = System.currentTimeMillis();
         context = DatabaseUtils.getParentContext(context);
         if (context == null) {
             return null;
         }
-        final UserManager userManager = context.getSystemService(UserManager.class);
-        if (userManager == null) {
-            return null;
-        }
         final long sixDaysAgoTimestamp =
                 DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance());
         final long earliestTimestamp = Math.max(sixDaysAgoTimestamp, startTimestampOfLevelData);
         final UsageEvents events =
-                getAppUsageEventsForUser(context, userManager, userID, earliestTimestamp);
+                getAppUsageEventsForUser(context, userIdsSeries, userID, earliestTimestamp);
         final long elapsedTime = System.currentTimeMillis() - start;
         Log.d(
                 TAG,
@@ -521,6 +517,7 @@
 
     static Map<Long, BatteryDiffData> getBatteryDiffDataMap(
             Context context,
+            final UserIdsSeries userIdsSeries,
             final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
             final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
             final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
@@ -528,11 +525,6 @@
             final @NonNull Set<String> systemAppsPackageNames,
             final @NonNull Set<Integer> systemAppsUids) {
         final Map<Long, BatteryDiffData> batteryDiffDataMap = new ArrayMap<>();
-        final int currentUserId = context.getUserId();
-        final UserHandle userHandle =
-                Utils.getManagedProfile(context.getSystemService(UserManager.class));
-        final int workProfileUserId =
-                userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
         // Each time slot usage diff data =
         //     sum(Math.abs(timestamp[i+1] data - timestamp[i] data));
         // since we want to aggregate every hour usage diff data into a single time slot.
@@ -569,8 +561,7 @@
                                 endTimestamp,
                                 startBatteryLevel,
                                 endBatteryLevel,
-                                currentUserId,
-                                workProfileUserId,
+                                userIdsSeries,
                                 slotDuration,
                                 systemAppsPackageNames,
                                 systemAppsUids,
@@ -629,6 +620,7 @@
     @Nullable
     static BatteryDiffData generateBatteryDiffData(
             final Context context,
+            final UserIdsSeries userIdsSeries,
             final long startTimestamp,
             final List<BatteryHistEntry> batteryHistEntryList,
             final @NonNull Set<String> systemAppsPackageNames,
@@ -650,15 +642,9 @@
                     systemAppsUids,
                     /* isAccumulated= */ false);
         }
-        final int currentUserId = context.getUserId();
-        final UserHandle userHandle =
-                Utils.getManagedProfile(context.getSystemService(UserManager.class));
-        final int workProfileUserId =
-                userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
-
         for (BatteryHistEntry entry : batteryHistEntryList) {
             final boolean isFromOtherUsers =
-                    isConsumedFromOtherUsers(currentUserId, workProfileUserId, entry);
+                    isConsumedFromOtherUsers(userIdsSeries, entry);
             // Not show other users' battery usage data.
             if (isFromOtherUsers) {
                 continue;
@@ -905,6 +891,7 @@
 
     static Map<Long, BatteryDiffData> getBatteryDiffDataMapFromStatsService(
             final Context context,
+            final UserIdsSeries userIdsSeries,
             final long startTimestamp,
             @NonNull final Set<String> systemAppsPackageNames,
             @NonNull final Set<Integer> systemAppsUids) {
@@ -913,6 +900,7 @@
                 startTimestamp,
                 generateBatteryDiffData(
                         context,
+                        userIdsSeries,
                         startTimestamp,
                         getBatteryHistListFromFromStatsService(context),
                         systemAppsPackageNames,
@@ -1034,14 +1022,14 @@
     @Nullable
     private static UsageEvents getAppUsageEventsForUser(
             Context context,
-            final UserManager userManager,
+            final UserIdsSeries userIdsSeries,
             final int userID,
             final long earliestTimestamp) {
         final String callingPackage = context.getPackageName();
         final long now = System.currentTimeMillis();
         // When the user is not unlocked, UsageStatsManager will return null, so bypass the
         // following data loading logics directly.
-        if (!userManager.isUserUnlocked(userID)) {
+        if (userIdsSeries.isUserLocked(userID)) {
             Log.w(TAG, "fail to load app usage event for user :" + userID + " because locked");
             return null;
         }
@@ -1331,8 +1319,7 @@
             final long endTimestamp,
             final int startBatteryLevel,
             final int endBatteryLevel,
-            final int currentUserId,
-            final int workProfileUserId,
+            final UserIdsSeries userIdsSeries,
             final long slotDuration,
             final Set<String> systemAppsPackageNames,
             final Set<Integer> systemAppsUids,
@@ -1342,8 +1329,7 @@
         if (appUsageMap != null) {
             final List<AppUsagePeriod> flatAppUsagePeriodList = new ArrayList<>();
             for (final long userId : appUsageMap.keySet()) {
-                if ((userId != currentUserId && userId != workProfileUserId)
-                        || appUsageMap.get(userId) == null) {
+                if (userIdsSeries.isFromOtherUsers(userId) || appUsageMap.get(userId) == null) {
                     continue;
                 }
                 for (final String packageName : appUsageMap.get(userId).keySet()) {
@@ -1405,8 +1391,7 @@
 
             // Not show other users' battery usage data.
             final boolean isFromOtherUsers =
-                    isConsumedFromOtherUsers(
-                            currentUserId, workProfileUserId, selectedBatteryEntry);
+                    isConsumedFromOtherUsers(userIdsSeries, selectedBatteryEntry);
             if (isFromOtherUsers) {
                 continue;
             }
@@ -1593,12 +1578,10 @@
     }
 
     private static boolean isConsumedFromOtherUsers(
-            final int currentUserId,
-            final int workProfileUserId,
+            final UserIdsSeries userIdsSeries,
             final BatteryHistEntry batteryHistEntry) {
         return isUidConsumer(batteryHistEntry.mConsumerType)
-                && batteryHistEntry.mUserId != currentUserId
-                && batteryHistEntry.mUserId != workProfileUserId;
+                && userIdsSeries.isFromOtherUsers(batteryHistEntry.mUserId);
     }
 
     @Nullable
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
index 29839e9..f5ed06d 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
@@ -492,6 +492,7 @@
                     return DataProcessManager.getBatteryLevelData(
                             getContext(),
                             mHandler,
+                            new UserIdsSeries(getContext(), /* mainUserOnly= */ false),
                             /* isFromPeriodJob= */ false,
                             PowerUsageAdvanced.this::onBatteryDiffDataMapUpdate);
                 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/UserIdsSeries.java b/src/com/android/settings/fuelgauge/batteryusage/UserIdsSeries.java
new file mode 100644
index 0000000..3dc311e
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/UserIdsSeries.java
@@ -0,0 +1,91 @@
+/*
+ * 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.fuelgauge.batteryusage;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class UserIdsSeries {
+    private final UserManager mUserManager;
+    private final int mCurrentUserId;
+    private final List<Integer> mVisibleUserIds = new ArrayList<>();
+
+    @Nullable private UserInfo mPrivateUser = null;
+    @Nullable private UserInfo mManagedProfileUser = null;
+
+    UserIdsSeries(final Context context, final boolean mainUserOnly) {
+        mUserManager = context.getSystemService(UserManager.class);
+        mCurrentUserId = context.getUserId();
+        List<UserInfo> aliveUsers =
+                mUserManager != null ? mUserManager.getAliveUsers() : new ArrayList<>();
+
+        if (mainUserOnly) {
+            aliveUsers.stream()
+                    .filter(UserInfo::isMain)
+                    .forEach(userInfo -> mVisibleUserIds.add(userInfo.id));
+            return;
+        }
+
+        for (UserInfo userInfo : aliveUsers) {
+            if (!mUserManager.isSameProfileGroup(mCurrentUserId, userInfo.id)) {
+                continue;
+            }
+            if (!userInfo.isQuietModeEnabled() || userInfo.isManagedProfile()) {
+                mVisibleUserIds.add(userInfo.id);
+            }
+            if (userInfo.isPrivateProfile()) {
+                mPrivateUser = userInfo;
+            }
+            if (userInfo.isManagedProfile()) {
+                mManagedProfileUser = userInfo;
+            }
+        }
+    }
+
+    int getCurrentUserId() {
+        return mCurrentUserId;
+    }
+
+    List<Integer> getVisibleUserIds() {
+        return mVisibleUserIds;
+    }
+
+    boolean isCurrentUserLocked() {
+        return isUserLocked(mCurrentUserId);
+    }
+
+    boolean isUserLocked(int userId) {
+        return mUserManager == null || !mUserManager.isUserUnlocked(userId);
+    }
+
+    boolean isFromOtherUsers(long userId) {
+        return !mVisibleUserIds.contains((int) userId);
+    }
+
+    boolean isMainUserProfileOnly() {
+        return mUserManager != null
+                && mUserManager.isMainUser()
+                && mPrivateUser == null
+                && mManagedProfileUser == null;
+    }
+}
diff --git a/src/com/android/settings/location/LocationForPrivateProfilePreferenceController.java b/src/com/android/settings/location/LocationForPrivateProfilePreferenceController.java
index a7be7a5..d68cf72 100644
--- a/src/com/android/settings/location/LocationForPrivateProfilePreferenceController.java
+++ b/src/com/android/settings/location/LocationForPrivateProfilePreferenceController.java
@@ -68,6 +68,7 @@
     @Override
     public int getAvailabilityStatus() {
         if (!android.os.Flags.allowPrivateProfile()
+                || !android.multiuser.Flags.enablePrivateSpaceFeatures()
                 || !android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()
                 || !isPrivateProfileAvailable()) {
             return CONDITIONALLY_UNAVAILABLE;
diff --git a/src/com/android/settings/network/telephony/NetworkSelectRepository.kt b/src/com/android/settings/network/telephony/NetworkSelectRepository.kt
new file mode 100644
index 0000000..1f5fbc2
--- /dev/null
+++ b/src/com/android/settings/network/telephony/NetworkSelectRepository.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.AccessNetworkConstants
+import android.telephony.NetworkRegistrationInfo
+import android.telephony.TelephonyManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class NetworkSelectRepository(context: Context, subId: Int) {
+    private val telephonyManager =
+        context.getSystemService(TelephonyManager::class.java)!!.createForSubscriptionId(subId)
+
+    data class NetworkRegistrationAndForbiddenInfo(
+        val networkList: List<NetworkRegistrationInfo>,
+        val forbiddenPlmns: List<String>,
+    )
+
+    /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */
+    fun launchUpdateNetworkRegistrationInfo(
+        lifecycleOwner: LifecycleOwner,
+        action: (NetworkRegistrationAndForbiddenInfo) -> Unit,
+    ) {
+        lifecycleOwner.lifecycleScope.launch {
+            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                withContext(Dispatchers.Default) {
+                    getNetworkRegistrationInfo()
+                }?.let(action)
+            }
+        }
+    }
+
+    fun getNetworkRegistrationInfo(): NetworkRegistrationAndForbiddenInfo? {
+        if (telephonyManager.dataState != TelephonyManager.DATA_CONNECTED) return null
+        // Try to get the network registration states
+        val serviceState = telephonyManager.serviceState ?: return null
+        val networkList = serviceState.getNetworkRegistrationInfoListForTransportType(
+            AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+        )
+        if (networkList.isEmpty()) return null
+        // Due to the aggregation of cell between carriers, it's possible to get CellIdentity
+        // containing forbidden PLMN.
+        // Getting current network from ServiceState is no longer a good idea.
+        // Add an additional rule to avoid from showing forbidden PLMN to the user.
+        return NetworkRegistrationAndForbiddenInfo(networkList, getForbiddenPlmns())
+    }
+
+    /**
+     * Update forbidden PLMNs from the USIM App
+     */
+    private fun getForbiddenPlmns(): List<String> {
+        return telephonyManager.forbiddenPlmns?.toList() ?: emptyList()
+    }
+}
diff --git a/src/com/android/settings/network/telephony/NetworkSelectSettings.java b/src/com/android/settings/network/telephony/NetworkSelectSettings.java
index eb89d9e..19bc390 100644
--- a/src/com/android/settings/network/telephony/NetworkSelectSettings.java
+++ b/src/com/android/settings/network/telephony/NetworkSelectSettings.java
@@ -24,12 +24,10 @@
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.provider.Settings;
-import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.NetworkRegistrationInfo;
-import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -52,13 +50,11 @@
 import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanCellInfos;
 import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanComplete;
 import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanError;
-import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanResult;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.utils.ThreadUtils;
 
 import kotlin.Unit;
-import kotlin.jvm.functions.Function1;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -101,6 +97,8 @@
     private NetworkScanRepository mNetworkScanRepository;
     private boolean mUpdateScanResult = false;
 
+    private NetworkSelectRepository mNetworkSelectRepository;
+
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -138,6 +136,7 @@
         mCarrierConfigManager.registerCarrierConfigChangeListener(mNetworkScanExecutor,
                 mCarrierConfigChangeListener);
         mNetworkScanRepository = new NetworkScanRepository(context, mSubId);
+        mNetworkSelectRepository = new NetworkSelectRepository(context, mSubId);
     }
 
     @Keep
@@ -202,35 +201,37 @@
         mProgressHeader = setPinnedHeaderView(
                 com.android.settingslib.widget.progressbar.R.layout.progress_header
         ).findViewById(com.android.settingslib.widget.progressbar.R.id.progress_bar_animation);
-        forceUpdateConnectedPreferenceCategory();
+        mNetworkSelectRepository.launchUpdateNetworkRegistrationInfo(
+                getViewLifecycleOwner(),
+                (info) -> {
+                    forceUpdateConnectedPreferenceCategory(info);
+                    return Unit.INSTANCE;
+                });
         launchNetworkScan();
     }
 
     private void launchNetworkScan() {
-        mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(), new Function1<>() {
-            @Override
-            public Unit invoke(@NonNull NetworkScanResult networkScanResult) {
-                if (!mUpdateScanResult) {
-                    // Not update UI if not in scan mode.
-                    return Unit.INSTANCE;
-                }
-                if (networkScanResult instanceof NetworkScanCellInfos networkScanCellInfos) {
-                    scanResultHandler(networkScanCellInfos.getCellInfos());
-                    return Unit.INSTANCE;
-                }
-                if (!isPreferenceScreenEnabled()) {
-                    clearPreferenceSummary();
-                    enablePreferenceScreen(true);
-                } else if (networkScanResult instanceof NetworkScanComplete
-                        && mCellInfoList == null) {
-                    // In case the scan timeout before getting any results
-                    addMessagePreference(R.string.empty_networks_list);
-                } else if (networkScanResult instanceof NetworkScanError) {
-                    addMessagePreference(R.string.network_query_error);
-                }
-
+        mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(), (networkScanResult) -> {
+            if (!mUpdateScanResult) {
+                // Not update UI if not in scan mode.
                 return Unit.INSTANCE;
             }
+            if (networkScanResult instanceof NetworkScanCellInfos networkScanCellInfos) {
+                scanResultHandler(networkScanCellInfos.getCellInfos());
+                return Unit.INSTANCE;
+            }
+            if (!isPreferenceScreenEnabled()) {
+                clearPreferenceSummary();
+                enablePreferenceScreen(true);
+            } else if (networkScanResult instanceof NetworkScanComplete
+                    && mCellInfoList == null) {
+                // In case the scan timeout before getting any results
+                addMessagePreference(R.string.empty_networks_list);
+            } else if (networkScanResult instanceof NetworkScanError) {
+                addMessagePreference(R.string.network_query_error);
+            }
+
+            return Unit.INSTANCE;
         });
     }
 
@@ -238,7 +239,6 @@
     public void onStart() {
         super.onStart();
 
-        updateForbiddenPlmns();
         setProgressBarVisible(true);
         mUpdateScanResult = true;
     }
@@ -477,45 +477,26 @@
      * - If the device has no data, we will remove the connected network operators list from the
      * screen.
      */
-    private void forceUpdateConnectedPreferenceCategory() {
-        if (mTelephonyManager.getDataState() == mTelephonyManager.DATA_CONNECTED) {
-            // Try to get the network registration states
-            final ServiceState ss = mTelephonyManager.getServiceState();
-            if (ss == null) {
-                return;
+    private void forceUpdateConnectedPreferenceCategory(
+            NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo info) {
+        for (NetworkRegistrationInfo regInfo : info.getNetworkList()) {
+            final CellIdentity cellIdentity = regInfo.getCellIdentity();
+            if (cellIdentity == null) {
+                continue;
             }
-            final List<NetworkRegistrationInfo> networkList =
-                    ss.getNetworkRegistrationInfoListForTransportType(
-                            AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-            if (networkList == null || networkList.size() == 0) {
-                return;
+            final NetworkOperatorPreference pref = new NetworkOperatorPreference(
+                    getPrefContext(), info.getForbiddenPlmns(), mShow4GForLTE);
+            pref.updateCell(null, cellIdentity);
+            if (pref.isForbiddenNetwork()) {
+                continue;
             }
-            // Due to the aggregation of cell between carriers, it's possible to get CellIdentity
-            // containing forbidden PLMN.
-            // Getting current network from ServiceState is no longer a good idea.
-            // Add an additional rule to avoid from showing forbidden PLMN to the user.
-            if (mForbiddenPlmns == null) {
-                updateForbiddenPlmns();
-            }
-            for (NetworkRegistrationInfo regInfo : networkList) {
-                final CellIdentity cellIdentity = regInfo.getCellIdentity();
-                if (cellIdentity == null) {
-                    continue;
-                }
-                final NetworkOperatorPreference pref = new NetworkOperatorPreference(
-                        getPrefContext(), mForbiddenPlmns, mShow4GForLTE);
-                pref.updateCell(null, cellIdentity);
-                if (pref.isForbiddenNetwork()) {
-                    continue;
-                }
-                pref.setSummary(R.string.network_connected);
-                // Update the signal strength icon, since the default signalStrength value
-                // would be zero
-                // (it would be quite confusing why the connected network has no signal)
-                pref.setIcon(SignalStrength.NUM_SIGNAL_STRENGTH_BINS - 1);
-                mPreferenceCategory.addPreference(pref);
-                break;
-            }
+            pref.setSummary(R.string.network_connected);
+            // Update the signal strength icon, since the default signalStrength value
+            // would be zero
+            // (it would be quite confusing why the connected network has no signal)
+            pref.setIcon(SignalStrength.NUM_SIGNAL_STRENGTH_BINS - 1);
+            mPreferenceCategory.addPreference(pref);
+            break;
         }
     }
 
diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
index 1da6a96..b0a39ec 100644
--- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt
+++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
@@ -26,6 +26,7 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.filterNot
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -55,7 +56,7 @@
 
 fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsChangedFlow().map {
     SubscriptionUtil.getFormattedPhoneNumber(this, subscriptionInfo)
-}.flowOn(Dispatchers.Default)
+}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)
 
 fun Context.subscriptionsChangedFlow() = callbackFlow {
     val subscriptionManager = requireSubscriptionManager()
diff --git a/src/com/android/settings/overlay/FeatureFactory.kt b/src/com/android/settings/overlay/FeatureFactory.kt
index 2c4a295..53ad8ba 100644
--- a/src/com/android/settings/overlay/FeatureFactory.kt
+++ b/src/com/android/settings/overlay/FeatureFactory.kt
@@ -68,7 +68,7 @@
     /**
      * Retrieves implementation for Hardware Info feature.
      */
-    abstract val hardwareInfoFeatureProvider: HardwareInfoFeatureProvider
+    open val hardwareInfoFeatureProvider: HardwareInfoFeatureProvider? = null
 
     /** Implementation for [SupportFeatureProvider]. */
     open val supportFeatureProvider: SupportFeatureProvider? = null
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.kt b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
index e1519b3..1770209 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.kt
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
@@ -45,8 +45,6 @@
 import com.android.settings.dashboard.DashboardFeatureProviderImpl
 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider
 import com.android.settings.dashboard.suggestions.SuggestionFeatureProviderImpl
-import com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFeatureProvider
-import com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFeatureProviderImpl
 import com.android.settings.display.DisplayFeatureProvider
 import com.android.settings.display.DisplayFeatureProviderImpl
 import com.android.settings.enterprise.EnterprisePrivacyFeatureProviderImpl
@@ -81,9 +79,6 @@
         ContextualCardFeatureProviderImpl(appContext)
     }
 
-    override val hardwareInfoFeatureProvider: HardwareInfoFeatureProvider =
-        HardwareInfoFeatureProviderImpl
-
     override val metricsFeatureProvider by lazy { SettingsMetricsFeatureProvider() }
 
     override val powerUsageFeatureProvider by lazy { PowerUsageFeatureProviderImpl(appContext) }
diff --git a/src/com/android/settings/privatespace/PrivateSpaceEducation.java b/src/com/android/settings/privatespace/PrivateSpaceEducation.java
index cf22895..a21aac3 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceEducation.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceEducation.java
@@ -19,10 +19,12 @@
 import android.app.Activity;
 import android.app.settings.SettingsEnums;
 import android.os.Bundle;
+import android.text.util.Linkify;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 import androidx.navigation.fragment.NavHostFragment;
@@ -34,6 +36,8 @@
 import com.google.android.setupcompat.template.FooterButton;
 import com.google.android.setupdesign.GlifLayout;
 
+import java.util.regex.Pattern;
+
 /** Fragment educating about the usage of Private Space. */
 public class PrivateSpaceEducation extends InstrumentedFragment {
     private static final String TAG = "PrivateSpaceEducation";
@@ -66,6 +70,13 @@
                         .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
                         .build());
 
+        TextView infoTextView = rootView.findViewById(R.id.learn_more);
+        Pattern pattern = Pattern.compile(infoTextView.getText().toString());
+        Linkify.addLinks(
+                infoTextView,
+                pattern,
+                getContext().getString(R.string.private_space_learn_more_url));
+
         return rootView;
     }
 
diff --git a/src/com/android/settings/privatespace/PrivateSpaceFooterPreferenceController.java b/src/com/android/settings/privatespace/PrivateSpaceFooterPreferenceController.java
new file mode 100644
index 0000000..71139ba
--- /dev/null
+++ b/src/com/android/settings/privatespace/PrivateSpaceFooterPreferenceController.java
@@ -0,0 +1,65 @@
+/*
+ * 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.privatespace;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.HelpUtils;
+import com.android.settingslib.widget.FooterPreference;
+
+/** Preference controller for private space settings footer. */
+public class PrivateSpaceFooterPreferenceController extends BasePreferenceController {
+    public PrivateSpaceFooterPreferenceController(
+            @NonNull Context context, @NonNull String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public void displayPreference(@NonNull PreferenceScreen screen) {
+        super.displayPreference(screen);
+        FooterPreference preference = screen.findPreference(getPreferenceKey());
+        setupFooter(preference);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return android.multiuser.Flags.enablePrivateSpaceFeatures()
+                ? AVAILABLE
+                : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @VisibleForTesting
+    void setupFooter(FooterPreference preference) {
+        final String helpUri = mContext.getString(R.string.private_space_learn_more_url);
+        if (!TextUtils.isEmpty(helpUri) && preference != null) {
+            preference.setLearnMoreAction(
+                    v -> {
+                        mContext.startActivity(
+                                HelpUtils.getHelpIntent(
+                                        mContext, helpUri, /* backupContext= */ ""));
+                    });
+            preference.setLearnMoreText(mContext.getString(R.string.private_space_learn_more_text));
+        }
+    }
+}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceGaiaEducationFragment.java b/src/com/android/settings/privatespace/PrivateSpaceGaiaEducationFragment.java
index 0fa0483..6c26186 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceGaiaEducationFragment.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceGaiaEducationFragment.java
@@ -46,7 +46,8 @@
 
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
-        if (android.os.Flags.allowPrivateProfile()) {
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             super.onCreate(savedInstanceState);
         }
     }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java
index 723a138..41379b3 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java
@@ -52,6 +52,7 @@
 public final class BatteryUsageDataLoaderTest {
 
     private Context mContext;
+    @Mock private UserIdsSeries mUserIdsSeries;
     @Mock private ContentResolver mMockContentResolver;
     @Mock private BatteryStatsManager mBatteryStatsManager;
     @Mock private PackageManager mPackageManager;
@@ -120,7 +121,7 @@
         BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
         BatteryUsageDataLoader.sFakeUsageEventsListSupplier = () -> AppUsageEventList;
 
-        BatteryUsageDataLoader.loadAppUsageData(mContext);
+        BatteryUsageDataLoader.loadAppUsageData(mContext, mUserIdsSeries);
 
         verify(mMockContentResolver).bulkInsert(any(), any());
         verify(mMockContentResolver).notifyChange(any(), any());
@@ -130,7 +131,7 @@
     public void loadAppUsageData_nullAppUsageEvents_notInsertDataIntoProvider() {
         BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> null;
 
-        BatteryUsageDataLoader.loadAppUsageData(mContext);
+        BatteryUsageDataLoader.loadAppUsageData(mContext, mUserIdsSeries);
 
         verifyNoMoreInteractions(mMockContentResolver);
     }
@@ -140,7 +141,7 @@
         BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
         BatteryUsageDataLoader.sFakeUsageEventsListSupplier = () -> null;
 
-        BatteryUsageDataLoader.loadAppUsageData(mContext);
+        BatteryUsageDataLoader.loadAppUsageData(mContext, mUserIdsSeries);
 
         verifyNoMoreInteractions(mMockContentResolver);
     }
@@ -150,7 +151,7 @@
         BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
         BatteryUsageDataLoader.sFakeUsageEventsListSupplier = () -> new ArrayList<>();
 
-        BatteryUsageDataLoader.loadAppUsageData(mContext);
+        BatteryUsageDataLoader.loadAppUsageData(mContext, mUserIdsSeries);
 
         verifyNoMoreInteractions(mMockContentResolver);
     }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
index 6227790..b025db8 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
@@ -70,6 +70,7 @@
     private Context mContext;
     private DataProcessManager mDataProcessManager;
 
+    @Mock private UserIdsSeries mUserIdsSeries;
     @Mock private IUsageStatsManager mUsageStatsManager;
     @Mock private UserManager mUserManager;
     @Mock private BatteryStatsManager mBatteryStatsManager;
@@ -95,11 +96,13 @@
         doReturn(mIntent).when(mContext).registerReceiver(any(), any());
         doReturn(100).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_SCALE), anyInt());
         doReturn(66).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_LEVEL), anyInt());
+        doReturn(true).when(mUserIdsSeries).isMainUserProfileOnly();
 
         mDataProcessManager =
                 new DataProcessManager(
                         mContext,
                         /* handler= */ null,
+                        mUserIdsSeries,
                         /* rawStartTimestamp= */ 0L,
                         /* lastFullChargeTimestamp= */ 0L,
                         /* callbackFunction= */ null,
@@ -117,7 +120,11 @@
     @LooperMode(LooperMode.Mode.LEGACY)
     public void constructor_noLevelData() {
         final DataProcessManager dataProcessManager =
-                new DataProcessManager(mContext, /* handler= */ null, /* callbackFunction= */ null);
+                new DataProcessManager(
+                        mContext,
+                        /* handler= */ null,
+                        mUserIdsSeries,
+                        /* callbackFunction= */ null);
         assertThat(dataProcessManager.getShowScreenOnTime()).isFalse();
     }
 
@@ -180,6 +187,7 @@
         doReturn(1).when(mContext).getUserId();
         // No work profile.
         doReturn(new ArrayList<>()).when(mUserManager).getUserProfiles();
+        doReturn(new ArrayList<>(List.of(1))).when(mUserIdsSeries).getVisibleUserIds();
 
         // Fake database usage data.
         final MatrixCursor cursor =
@@ -239,6 +247,7 @@
                 new DataProcessManager(
                         mContext,
                         /* handler= */ null,
+                        mUserIdsSeries,
                         /* rawStartTimestamp= */ 2L,
                         /* lastFullChargeTimestamp= */ 1L,
                         /* callbackFunction= */ null,
@@ -301,7 +310,7 @@
         doReturn(getUsageEvents(events))
                 .when(mUsageStatsManager)
                 .queryEventsForUser(anyLong(), anyLong(), anyInt(), any());
-        doReturn(false).when(mUserManager).isUserUnlocked(anyInt());
+        doReturn(true).when(mUserIdsSeries).isCurrentUserLocked();
         final MatrixCursor cursor =
                 new MatrixCursor(
                         new String[] {
@@ -327,6 +336,7 @@
                         DataProcessManager.getBatteryLevelData(
                                 mContext,
                                 /* handler= */ null,
+                                mUserIdsSeries,
                                 /* isFromPeriodJob= */ false,
                                 /* asyncResponseDelegate= */ null))
                 .isNull();
@@ -334,6 +344,7 @@
                         DataProcessManager.getBatteryLevelData(
                                 mContext,
                                 /* handler= */ null,
+                                mUserIdsSeries,
                                 /* isFromPeriodJob= */ true,
                                 /* asyncResponseDelegate= */ null))
                 .isNull();
@@ -355,6 +366,7 @@
                 DataProcessManager.getBatteryLevelData(
                         mContext,
                         /* handler= */ null,
+                        mUserIdsSeries,
                         /* isFromPeriodJob= */ false,
                         /* asyncResponseDelegate= */ null);
 
@@ -383,6 +395,7 @@
                 DataProcessManager.getBatteryLevelData(
                         mContext,
                         /* handler= */ null,
+                        mUserIdsSeries,
                         /* isFromPeriodJob= */ false,
                         /* asyncResponseDelegate= */ null);
 
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
index 7a67240..2897343 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
@@ -80,6 +80,7 @@
     @Mock private Intent mIntent;
     @Mock private BatteryUsageStats mBatteryUsageStats;
     @Mock private UserManager mUserManager;
+    @Mock private UserIdsSeries mUserIdsSeries;
     @Mock private IUsageStatsManager mUsageStatsManager;
     @Mock private BatteryEntry mMockBatteryEntry1;
     @Mock private BatteryEntry mMockBatteryEntry2;
@@ -95,6 +96,7 @@
         mContext = spy(RuntimeEnvironment.application);
         mFeatureFactory = FakeFeatureFactory.setupForTest();
         mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider;
+        doReturn(true).when(mUserIdsSeries).isMainUserProfileOnly();
 
         DataProcessor.sTestSystemAppsPackageNames = Set.of();
         DataProcessor.sUsageStatsManager = mUsageStatsManager;
@@ -118,8 +120,10 @@
         doReturn(mUsageEvents1)
                 .when(mUsageStatsManager)
                 .queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString());
+        doReturn(new ArrayList<>(List.of(0))).when(mUserIdsSeries).getVisibleUserIds();
 
-        final Map<Long, UsageEvents> resultMap = DataProcessor.getAppUsageEvents(mContext);
+        final Map<Long, UsageEvents> resultMap =
+                DataProcessor.getAppUsageEvents(mContext, mUserIdsSeries);
 
         assertThat(resultMap).hasSize(1);
         assertThat(resultMap.get(Long.valueOf(userInfo.id))).isEqualTo(mUsageEvents1);
@@ -134,7 +138,8 @@
         // Test locked user.
         doReturn(false).when(mUserManager).isUserUnlocked(userInfo.id);
 
-        final Map<Long, UsageEvents> resultMap = DataProcessor.getAppUsageEvents(mContext);
+        final Map<Long, UsageEvents> resultMap =
+                DataProcessor.getAppUsageEvents(mContext, mUserIdsSeries);
 
         assertThat(resultMap).isNull();
     }
@@ -150,7 +155,8 @@
                 .when(mUsageStatsManager)
                 .queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString());
 
-        final Map<Long, UsageEvents> resultMap = DataProcessor.getAppUsageEvents(mContext);
+        final Map<Long, UsageEvents> resultMap =
+                DataProcessor.getAppUsageEvents(mContext, mUserIdsSeries);
 
         assertThat(resultMap).isNull();
     }
@@ -163,7 +169,8 @@
                 .when(mUsageStatsManager)
                 .queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString());
 
-        assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId, 0))
+        assertThat(DataProcessor.getCurrentAppUsageEventsForUser(
+                mContext, mUserIdsSeries, userId, 0))
                 .isEqualTo(mUsageEvents1);
     }
 
@@ -173,7 +180,9 @@
         // Test locked user.
         doReturn(false).when(mUserManager).isUserUnlocked(userId);
 
-        assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId, 0)).isNull();
+        assertThat(DataProcessor.getCurrentAppUsageEventsForUser(
+                mContext, mUserIdsSeries, userId, 0))
+                .isNull();
     }
 
     @Test
@@ -184,7 +193,9 @@
                 .when(mUsageStatsManager)
                 .queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString());
 
-        assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId, 0)).isNull();
+        assertThat(DataProcessor.getCurrentAppUsageEventsForUser(
+                mContext, mUserIdsSeries, userId, 0))
+                .isNull();
     }
 
     @Test
@@ -852,6 +863,7 @@
         assertThat(
                         DataProcessor.getBatteryDiffDataMap(
                                 mContext,
+                                mUserIdsSeries,
                                 hourlyBatteryLevelsPerDay,
                                 new HashMap<>(),
                                 /* appUsagePeriodMap= */ null,
@@ -938,6 +950,7 @@
         Map<Long, BatteryDiffData> batteryDiffDataMap =
                 DataProcessor.getBatteryDiffDataMap(
                         mContext,
+                        mUserIdsSeries,
                         batteryLevelData.getHourlyBatteryLevelsPerDay(),
                         batteryHistoryMap,
                         appUsagePeriodMap,
@@ -1154,6 +1167,7 @@
                         mContext,
                         DataProcessor.getBatteryDiffDataMap(
                                 mContext,
+                                mUserIdsSeries,
                                 batteryLevelData.getHourlyBatteryLevelsPerDay(),
                                 batteryHistoryMap,
                                 appUsagePeriodMap,
@@ -1271,6 +1285,10 @@
                 };
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
         final int currentUserId = mContext.getUserId();
+        doReturn(false).when(mUserIdsSeries).isFromOtherUsers(currentUserId);
+        doReturn(true).when(mUserIdsSeries).isFromOtherUsers(currentUserId + 1);
+        doReturn(true).when(mUserIdsSeries).isFromOtherUsers(currentUserId + 2);
+
         // Adds the index = 0 data.
         Map<String, BatteryHistEntry> entryMap = new HashMap<>();
         BatteryHistEntry entry =
@@ -1431,6 +1449,7 @@
                         mContext,
                         DataProcessor.getBatteryDiffDataMap(
                                 mContext,
+                                mUserIdsSeries,
                                 batteryLevelData.getHourlyBatteryLevelsPerDay(),
                                 batteryHistoryMap,
                                 /* appUsagePeriodMap= */ null,
@@ -1546,6 +1565,7 @@
                         mContext,
                         DataProcessor.getBatteryDiffDataMap(
                                 mContext,
+                                mUserIdsSeries,
                                 batteryLevelData.getHourlyBatteryLevelsPerDay(),
                                 batteryHistoryMap,
                                 appUsagePeriodMap,
@@ -1701,6 +1721,7 @@
                         mContext,
                         DataProcessor.getBatteryDiffDataMap(
                                 mContext,
+                                mUserIdsSeries,
                                 batteryLevelData.getHourlyBatteryLevelsPerDay(),
                                 batteryHistoryMap,
                                 /* appUsagePeriodMap= */ null,
@@ -1851,6 +1872,7 @@
                         mContext,
                         DataProcessor.getBatteryDiffDataMap(
                                 mContext,
+                                mUserIdsSeries,
                                 batteryLevelData.getHourlyBatteryLevelsPerDay(),
                                 batteryHistoryMap,
                                 /* appUsagePeriodMap= */ null,
@@ -1873,6 +1895,7 @@
         final BatteryDiffData batteryDiffData =
                 DataProcessor.generateBatteryDiffData(
                         mContext,
+                        mUserIdsSeries,
                         System.currentTimeMillis(),
                         DataProcessor.convertToBatteryHistEntry(null, mBatteryUsageStats),
                         /* systemAppsPackageNames= */ Set.of(),
@@ -1933,6 +1956,7 @@
         final BatteryDiffData batteryDiffData =
                 DataProcessor.generateBatteryDiffData(
                         mContext,
+                        mUserIdsSeries,
                         System.currentTimeMillis(),
                         DataProcessor.convertToBatteryHistEntry(
                                 batteryEntryList, mBatteryUsageStats),
diff --git a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
index f49cc68..38683d0 100644
--- a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
@@ -32,8 +32,6 @@
 import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
 import com.android.settings.dashboard.DashboardFeatureProvider;
 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
-import com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFeatureProvider;
-import com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFeatureProviderImpl;
 import com.android.settings.display.DisplayFeatureProvider;
 import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
 import com.android.settings.fuelgauge.BatterySettingsFeatureProvider;
@@ -299,11 +297,6 @@
     }
 
     @Override
-    public HardwareInfoFeatureProvider getHardwareInfoFeatureProvider() {
-        return HardwareInfoFeatureProviderImpl.INSTANCE;
-    }
-
-    @Override
     public AdvancedVpnFeatureProvider getAdvancedVpnFeatureProvider() {
         return mAdvancedVpnFeatureProvider;
     }
diff --git a/tests/screenshot/assets/robolectric/fp_enroll_confirmation.png b/tests/screenshot/assets/robolectric/fp_enroll_confirmation.png
index db9009b..fe3353e 100644
--- a/tests/screenshot/assets/robolectric/fp_enroll_confirmation.png
+++ b/tests/screenshot/assets/robolectric/fp_enroll_confirmation.png
Binary files differ
diff --git a/tests/screenshot/assets/robolectric/fp_enroll_enrolling.png b/tests/screenshot/assets/robolectric/fp_enroll_enrolling.png
index 1299f2a..7a3b8da 100644
--- a/tests/screenshot/assets/robolectric/fp_enroll_enrolling.png
+++ b/tests/screenshot/assets/robolectric/fp_enroll_enrolling.png
Binary files differ
diff --git a/tests/screenshot/assets/robolectric/fp_enroll_find_sensor.png b/tests/screenshot/assets/robolectric/fp_enroll_find_sensor.png
index a252e5e..e92d606 100644
--- a/tests/screenshot/assets/robolectric/fp_enroll_find_sensor.png
+++ b/tests/screenshot/assets/robolectric/fp_enroll_find_sensor.png
Binary files differ
diff --git a/tests/screenshot/assets/robolectric/fp_enroll_intro.png b/tests/screenshot/assets/robolectric/fp_enroll_intro.png
index 3cd9f4a..a0c7556 100644
--- a/tests/screenshot/assets/robolectric/fp_enroll_intro.png
+++ b/tests/screenshot/assets/robolectric/fp_enroll_intro.png
Binary files differ
diff --git a/tests/spa_unit/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfoTest.kt b/tests/spa_unit/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfoTest.kt
index f1e18fc..7486e78 100644
--- a/tests/spa_unit/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfoTest.kt
+++ b/tests/spa_unit/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfoTest.kt
@@ -35,14 +35,12 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.MockitoSession
-import org.mockito.Spy
 import org.mockito.quality.Strictness
 
 @RunWith(AndroidJUnit4::class)
 class RegulatoryInfoTest {
     private lateinit var mockSession: MockitoSession
 
-    @Spy
     private val context: Context = ApplicationProvider.getApplicationContext()
 
     @Before
@@ -98,8 +96,31 @@
         assertDrawableSameAs(regulatoryInfo, R.drawable.regulatory_info_sku)
     }
 
+    @Test
+    fun getCoo() {
+        doReturn(COO).`when` { SystemProperties.get(KEY_COO) }
+
+        val coo = RegulatoryInfo.getCoo()
+
+        assertThat(coo).isEqualTo(COO)
+    }
+
+    @Test
+    fun getSku() {
+        doReturn(SKU).`when` { SystemProperties.get(KEY_SKU) }
+
+        val coo = RegulatoryInfo.getSku()
+
+        assertThat(coo).isEqualTo(SKU)
+    }
+
     private fun assertDrawableSameAs(drawable: Drawable?, @DrawableRes resId: Int) {
         val expected = context.getDrawable(resId)!!.toBitmap()
         assertThat(drawable!!.toBitmap().sameAs(expected)).isTrue()
     }
+
+    private companion object {
+        const val SKU = "ABC"
+        const val COO = "CN"
+    }
 }
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt
new file mode 100644
index 0000000..4137de4
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.AccessNetworkConstants
+import android.telephony.NetworkRegistrationInfo
+import android.telephony.ServiceState
+import android.telephony.TelephonyManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.network.telephony.scan.NetworkScanRepositoryTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class NetworkSelectRepositoryTest {
+
+    private val mockServiceState = mock<ServiceState> {
+        on {
+            getNetworkRegistrationInfoListForTransportType(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+            )
+        } doReturn NetworkRegistrationInfos
+    }
+
+    private val mockTelephonyManager = mock<TelephonyManager> {
+        on { createForSubscriptionId(SUB_ID) } doReturn mock
+        on { dataState } doReturn TelephonyManager.DATA_CONNECTED
+        on { serviceState } doReturn mockServiceState
+    }
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+    }
+
+    private val repository = NetworkSelectRepository(context, SUB_ID)
+
+    @Test
+    fun getNetworkRegistrationInfo_notConnected_returnNull() {
+        mockTelephonyManager.stub {
+            on { dataState } doReturn TelephonyManager.DATA_DISCONNECTED
+        }
+
+        val info = repository.getNetworkRegistrationInfo()
+
+        assertThat(info).isNull()
+    }
+
+    @Test
+    fun getNetworkRegistrationInfo_nullServiceState_returnNull() {
+        mockTelephonyManager.stub {
+            on { serviceState } doReturn null
+        }
+
+        val info = repository.getNetworkRegistrationInfo()
+
+        assertThat(info).isNull()
+    }
+
+    @Test
+    fun getNetworkRegistrationInfo_emptyNetworkList_returnNull() {
+        mockServiceState.stub {
+            on {
+                getNetworkRegistrationInfoListForTransportType(
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+                )
+            } doReturn emptyList()
+        }
+
+        val info = repository.getNetworkRegistrationInfo()
+
+        assertThat(info).isNull()
+    }
+
+    @Test
+    fun getNetworkRegistrationInfo_hasNetworkList_returnInfo() {
+        mockServiceState.stub {
+            on {
+                getNetworkRegistrationInfoListForTransportType(
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+                )
+            } doReturn NetworkRegistrationInfos
+        }
+        mockTelephonyManager.stub {
+            on { forbiddenPlmns } doReturn arrayOf(FORBIDDEN_PLMN)
+        }
+
+        val info = repository.getNetworkRegistrationInfo()
+
+        assertThat(info).isEqualTo(
+            NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo(
+                networkList = NetworkRegistrationInfos,
+                forbiddenPlmns = listOf(FORBIDDEN_PLMN),
+            )
+        )
+    }
+
+    private companion object {
+        const val SUB_ID = 1
+        val NetworkRegistrationInfos = listOf(NetworkRegistrationInfo.Builder().build())
+        const val FORBIDDEN_PLMN = "Forbidden PLMN"
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
index 80b3175..b394eb2 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
@@ -141,10 +141,27 @@
         assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_1)
     }
 
+    @Test
+    fun phoneNumberFlow() = runBlocking {
+        mockSubscriptionManager.stub {
+            on { getPhoneNumber(SUB_ID_1) } doReturn NUMBER_1
+        }
+        val subInfo = SubscriptionInfo.Builder().apply {
+            setId(SUB_ID_1)
+            setMcc(MCC)
+        }.build()
+
+        val phoneNumber = context.phoneNumberFlow(subInfo).firstWithTimeoutOrNull()
+
+        assertThat(phoneNumber).isEqualTo(NUMBER_1)
+    }
+
     private companion object {
         const val SUB_ID_1 = 1
         const val SUB_ID_2 = 2
         val GROUP_UUID = UUID.randomUUID().toString()
         const val SIM_SLOT_INDEX = 1
+        const val NUMBER_1 = "000000001"
+        const val MCC = "310"
     }
 }
diff --git a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt
index 44a5037..43270c2 100644
--- a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt
@@ -18,19 +18,29 @@
 
 import android.content.Context
 import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
+import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settings.R
 import com.android.settings.network.SimOnboardingService
+import com.android.settingslib.spa.testutils.waitUntilExists
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
 import org.mockito.kotlin.stub
 import org.mockito.kotlin.verify
 
@@ -39,7 +49,20 @@
     @get:Rule
     val composeTestRule = createComposeRule()
 
-    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val mockSubscriptionManager = mock<SubscriptionManager> {
+        on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer {
+            val listener = it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener
+            listener.onSubscriptionsChanged()
+        }
+        on { getPhoneNumber(SUB_ID_1) } doReturn NUMBER_1
+        on { getPhoneNumber(SUB_ID_2) } doReturn NUMBER_2
+        on { getPhoneNumber(SUB_ID_3) } doReturn NUMBER_3
+    }
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager
+    }
+
     private var mockSimOnboardingService = mock<SimOnboardingService> {
         on { targetSubId }.doReturn(-1)
         on { targetSubInfo }.doReturn(null)
@@ -85,7 +108,7 @@
         composeTestRule.onNodeWithText(context.getString(R.string.sim_onboarding_next))
             .performClick()
 
-        verify(nextAction)
+        verify(nextAction)()
     }
 
     @Test
@@ -97,7 +120,7 @@
         composeTestRule.onNodeWithText(context.getString(R.string.cancel))
             .performClick()
 
-        verify(cancelAction)
+        verify(cancelAction)()
     }
 
     @Test
@@ -120,15 +143,20 @@
         }
 
         composeTestRule.setContent {
-            SimOnboardingLabelSimImpl(nextAction, cancelAction, mockSimOnboardingService)
+            CompositionLocalProvider(
+                LocalContext provides context,
+                LocalLifecycleOwner provides TestLifecycleOwner(),
+            ) {
+                SimOnboardingLabelSimImpl(nextAction, cancelAction, mockSimOnboardingService)
+            }
         }
 
         composeTestRule.onNodeWithText(DISPLAY_NAME_1).assertIsDisplayed()
-        composeTestRule.onNodeWithText(NUMBER_1).assertIsDisplayed()
+        composeTestRule.waitUntilExists(hasText(NUMBER_1))
         composeTestRule.onNodeWithText(DISPLAY_NAME_2).assertIsDisplayed()
-        composeTestRule.onNodeWithText(NUMBER_2).assertIsDisplayed()
+        composeTestRule.waitUntilExists(hasText(NUMBER_2))
         composeTestRule.onNodeWithText(DISPLAY_NAME_3).assertIsDisplayed()
-        composeTestRule.onNodeWithText(NUMBER_3).assertIsDisplayed()
+        composeTestRule.waitUntilExists(hasText(NUMBER_3))
     }
 
     @Test
@@ -173,24 +201,25 @@
         const val NUMBER_1 = "000000001"
         const val NUMBER_2 = "000000002"
         const val NUMBER_3 = "000000003"
+        const val MCC = "310"
         const val PRIMARY_SIM_ASK_EVERY_TIME = -1
 
         val SUB_INFO_1: SubscriptionInfo = SubscriptionInfo.Builder().apply {
             setId(SUB_ID_1)
             setDisplayName(DISPLAY_NAME_1)
-            setNumber(NUMBER_1)
+            setMcc(MCC)
         }.build()
 
         val SUB_INFO_2: SubscriptionInfo = SubscriptionInfo.Builder().apply {
             setId(SUB_ID_2)
             setDisplayName(DISPLAY_NAME_2)
-            setNumber(NUMBER_2)
+            setMcc(MCC)
         }.build()
 
         val SUB_INFO_3: SubscriptionInfo = SubscriptionInfo.Builder().apply {
             setId(SUB_ID_3)
             setDisplayName(DISPLAY_NAME_3)
-            setNumber(NUMBER_3)
+            setMcc(MCC)
         }.build()
     }
 }
diff --git a/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt
index 644095d..976096c 100644
--- a/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt
+++ b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt
@@ -33,13 +33,13 @@
 import androidx.preference.SwitchPreference
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.net.thread.platform.flags.Flags
 import com.android.settings.R
 import com.android.settings.core.BasePreferenceController.AVAILABLE
 import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE
 import com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING
 import com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE
 import com.android.settings.connecteddevice.threadnetwork.ThreadNetworkPreferenceController.BaseThreadNetworkController
+import com.android.settings.flags.Flags
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -68,7 +68,7 @@
 
     @Before
     fun setUp() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_ENABLED_PLATFORM)
+        mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED)
         context = spy(ApplicationProvider.getApplicationContext<Context>())
         executor = ContextCompat.getMainExecutor(context)
         fakeThreadNetworkController = FakeThreadNetworkController(executor)
@@ -96,7 +96,7 @@
 
     @Test
     fun availabilityStatus_flagDisabled_returnsConditionallyUnavailable() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_THREAD_ENABLED_PLATFORM)
+        mSetFlagsRule.disableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED)
         assertThat(controller.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE)
     }
 
diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceFooterPreferenceControllerTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceFooterPreferenceControllerTest.java
new file mode 100644
index 0000000..30215da
--- /dev/null
+++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceFooterPreferenceControllerTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.privatespace;
+
+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.verify;
+
+import android.content.Context;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settingslib.widget.FooterPreference;
+
+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 PrivateSpaceFooterPreferenceControllerTest {
+    @Mock private Context mContext;
+    @Mock FooterPreference mFooterPreference;
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    private PrivateSpaceFooterPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = ApplicationProvider.getApplicationContext();
+        final String preferenceKey = "private_space_footer";
+
+        mController = new PrivateSpaceFooterPreferenceController(mContext, preferenceKey);
+    }
+
+    @Test
+    public void getAvailabilityStatus_whenFlagsEnabled_returnsAvailable() {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_whenFlagsDisabled_returnsUnsupportedOnDevice() {
+        mSetFlagsRule.disableFlags(android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void setUpFooter_setsLearnMoreTextAndAction() {
+        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+
+        mController.setupFooter(mFooterPreference);
+        verify(mFooterPreference).setLearnMoreAction(any());
+        verify(mFooterPreference).setLearnMoreText("Learn more about private space");
+    }
+}
diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
index 4f17a3a..29758de 100644
--- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -32,8 +32,6 @@
 import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
 import com.android.settings.dashboard.DashboardFeatureProvider;
 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
-import com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFeatureProvider;
-import com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFeatureProviderImpl;
 import com.android.settings.display.DisplayFeatureProvider;
 import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
 import com.android.settings.fuelgauge.BatterySettingsFeatureProvider;
@@ -300,11 +298,6 @@
     }
 
     @Override
-    public HardwareInfoFeatureProvider getHardwareInfoFeatureProvider() {
-        return HardwareInfoFeatureProviderImpl.INSTANCE;
-    }
-
-    @Override
     public AdvancedVpnFeatureProvider getAdvancedVpnFeatureProvider() {
         return mAdvancedVpnFeatureProvider;
     }