Merge "Show other users in storage settings"
diff --git a/Android.bp b/Android.bp
index 9ddadaf..f980bfa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -90,8 +90,11 @@
         "WifiTrackerLib",
         "SettingsLibActivityEmbedding",
         "Settings-change-ids",
+        "androidx.room_room-runtime",
     ],
 
+    plugins: ["androidx.room_room-compiler-plugin"],
+
     libs: [
         "telephony-common",
         "ims-common",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f5da15f..a3a6fcf 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -6,6 +6,11 @@
 
     <original-package android:name="com.android.settings"/>
 
+    <!-- Permissions for reading or writing battery-related data. -->
+    <permission
+        android:name="com.android.settings.BATTERY_DATA"
+        android:protectionLevel="signature|privileged"/>
+
     <uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
     <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
@@ -2961,6 +2966,21 @@
                        android:value="@string/menu_key_battery"/>
         </activity>
 
+        <provider
+            android:name=".fuelgauge.batteryusage.BatteryUsageContentProvider"
+            android:enabled="true"
+            android:exported="true"
+            android:authorities="${applicationId}.battery.usage.provider"
+            android:permission="com.android.settings.BATTERY_DATA"/>
+
+        <receiver android:name=".fuelgauge.batteryusage.BatteryUsageBroadcastReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.settings.battery.action.FETCH_BATTERY_USAGE_DATA"/>
+                <action android:name="com.android.settings.battery.action.CLEAR_BATTERY_CACHE_DATA"/>
+            </intent-filter>
+        </receiver>
+
         <activity
             android:name="Settings$BatterySaverSettingsActivity"
             android:label="@string/battery_saver"
diff --git a/res/layout/search_bar_two_pane_version.xml b/res/layout/search_bar_two_pane_version.xml
index 9ce220c..f98985c 100644
--- a/res/layout/search_bar_two_pane_version.xml
+++ b/res/layout/search_bar_two_pane_version.xml
@@ -38,6 +38,6 @@
             android:layout_height="wrap_content"
             android:paddingStart="@dimen/search_bar_title_padding_start_regular_two_pane"
             android:layout_gravity="start"
-            android:text="@string/search_menu"/>
+            android:text="@string/search_settings"/>
     </Toolbar>
 </com.google.android.material.card.MaterialCardView>
\ No newline at end of file
diff --git a/res/values-am/arrays.xml b/res/values-am/arrays.xml
index 6305184..9240b57 100644
--- a/res/values-am/arrays.xml
+++ b/res/values-am/arrays.xml
@@ -183,17 +183,17 @@
     <item msgid="346101114322879720">"ማሳወቂያዎችን ድረስ"</item>
     <item msgid="4760681822601767255">"ካሜራ"</item>
     <item msgid="2172823594140104317">"ኦዲዮ ቅረጽ"</item>
-    <item msgid="5612873260709742213">"ድምጽ አጫውት"</item>
+    <item msgid="5612873260709742213">"ድምፅ አጫውት"</item>
     <item msgid="2027206403725749996">"ቅንጥብ መለያ አንብብ"</item>
     <item msgid="5643742956725663156">"ቅንጥብ መለያ ቀይር"</item>
     <item msgid="7362845549479684378">"የሚዲያ አዝራሮች"</item>
     <item msgid="3843484466100107397">"የድምጽ ትኩረት"</item>
-    <item msgid="617344340943430125">"ዋናው ድምጽ መቆጣጠሪያ"</item>
+    <item msgid="617344340943430125">"ዋናው ድምፅ መቆጣጠሪያ"</item>
     <item msgid="1249691739381713634">"የድምጽ መጠን"</item>
-    <item msgid="6485000384018554920">"የጥሪ ድምጽ መጠን"</item>
+    <item msgid="6485000384018554920">"የጥሪ ድምፅ መጠን"</item>
     <item msgid="3378000878531336372">"የማህደረ መረጃ መጠን"</item>
     <item msgid="5272927168355895681">"የማንቂያ ድምፅ መጠን"</item>
-    <item msgid="4422070755065530548">"የማሳወቂያ ድምጽ መጠን"</item>
+    <item msgid="4422070755065530548">"የማሳወቂያ ድምፅ መጠን"</item>
     <item msgid="3250654589277825306">"የብሉቱዝ ድምፅ መጠን"</item>
     <item msgid="4212187233638382465">"እንደነቃ አቆይ"</item>
     <item msgid="5099026183238335900">"አካባቢን ይቆጣጠሩ"</item>
@@ -250,7 +250,7 @@
     <item msgid="8267704990417682222">"ማሳወቂያዎችን ይድረሱ"</item>
     <item msgid="3180676986290096851">"ካሜራ"</item>
     <item msgid="9174072114281872917">"ኦዲዮ ቅረጽ"</item>
-    <item msgid="1444183972646890539">"ድምጽ አጫውት"</item>
+    <item msgid="1444183972646890539">"ድምፅ አጫውት"</item>
     <item msgid="4337542044275236638">"ቅንጥብ ሰሌዳ አንብብ"</item>
     <item msgid="2681224211796661809">"ቅንጥብ መለያ ቀይር"</item>
     <item msgid="4479361062226474111">"የሚዲያ አዝራሮች"</item>
@@ -260,7 +260,7 @@
     <item msgid="6749550886745567276">"የጥሪ መጠን"</item>
     <item msgid="2218685029915863168">"የማህደረ መረጃ ክፍልፍል"</item>
     <item msgid="4266577290496513640">"የማንቂያ ድምፅ መጠን"</item>
-    <item msgid="8608084169623998854">"የማሳወቂያ ድምጽ መጠን"</item>
+    <item msgid="8608084169623998854">"የማሳወቂያ ድምፅ መጠን"</item>
     <item msgid="7948784184567841794">"የብሉቱዝ ድምፅ መጠን"</item>
     <item msgid="1148968792599973150">"እንደነቃ አቆይ"</item>
     <item msgid="8482874682804856549">"አካባቢ"</item>
diff --git a/res/values/config.xml b/res/values/config.xml
index 29de56f..4b725b1 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -318,6 +318,9 @@
     surface in search results or not.-->
     <bool name="config_show_wifi_settings">true</bool>
 
+    <!-- Whether Wi-Fi hotspot settings should be shown or not. -->
+    <bool name="config_show_wifi_hotspot_settings">true</bool>
+
     <!-- Whether toggle_airplane is available or not. -->
     <bool name="config_show_toggle_airplane">true</bool>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c66c5b9..645a826 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2599,6 +2599,8 @@
     <string name="screensaver_settings_title">Screen saver</string>
     <!-- List of synonyms used in the settings search bar to find the “Screen saver”. [CHAR LIMIT=NONE] -->
     <string name="keywords_screensaver">screensaver</string>
+    <!-- Summary for screensaver unavailable when Bedtime mode is on [CHAR LIMIT=50] -->
+    <string name="screensaver_settings_when_to_dream_bedtime">Unavailable because bedtime mode is on</string>
     <!-- The title for the toggle which disables/enables screen savers [CHAR_LIMIT=30] -->
     <string name="screensaver_settings_toggle_title">Use screen saver</string>
     <!-- Display settings screen, summary fragment for screen saver options, activated when docked or asleep and charging [CHAR LIMIT=35] -->
@@ -8022,6 +8024,8 @@
     <string name="notif_listener_excluded_app_summary">Change settings for each app that sends notifications</string>
     <string name="notif_listener_excluded_app_screen_title">Apps shown on device</string>
     <string name="notif_listener_not_migrated">This app doesn\u2019t support enhanced settings</string>
+    <string name="notif_listener_more_settings">More settings</string>
+    <string name="notif_listener_more_settings_desc">More settings are available inside this app</string>
 
     <!-- Title for managing VR (virtual reality) helper services. [CHAR LIMIT=50] -->
     <string name="vr_listeners_title">VR helper services</string>
diff --git a/res/xml/notification_access_permission_details.xml b/res/xml/notification_access_permission_details.xml
index 9867b6d..32a79e8 100644
--- a/res/xml/notification_access_permission_details.xml
+++ b/res/xml/notification_access_permission_details.xml
@@ -64,6 +64,12 @@
         settings:searchable="false"
         settings:controller="com.android.settings.applications.specialaccess.notificationaccess.BridgedAppsLinkPreferenceController" />
 
+    <Preference
+        android:key="more_settings"
+        android:title="@string/notif_listener_more_settings"
+        android:summary="@string/notif_listener_more_settings_desc"
+        settings:controller="com.android.settings.applications.specialaccess.notificationaccess.MoreSettingsPreferenceController" />
+
     <com.android.settingslib.widget.FooterPreference
         android:key="notif_listener_not_migrated"
         android:title="@string/notif_listener_not_migrated"
diff --git a/res/xml/privacy_dashboard_settings.xml b/res/xml/privacy_dashboard_settings.xml
index f8f916e..4abdeda 100644
--- a/res/xml/privacy_dashboard_settings.xml
+++ b/res/xml/privacy_dashboard_settings.xml
@@ -86,8 +86,7 @@
     <com.android.settings.RestrictedListPreference
         android:key="privacy_lock_screen_notifications"
         android:title="@string/lock_screen_notifs_title"
-        android:summary="@string/summary_placeholder"
-        settings:searchable="false"/>
+        android:summary="@string/summary_placeholder"/>
 
     <!-- Show media on lock screen -->
     <SwitchPreference
@@ -117,8 +116,7 @@
             android:key="privacy_lock_screen_work_profile_notifications"
             android:title="@string/locked_work_profile_notification_title"
             android:summary="@string/summary_placeholder"
-            android:order="999"
-            settings:searchable="false"/>
+            android:order="999"/>
     </PreferenceCategory>
 
     <!-- Content Capture -->
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 6cdf43f..53960b4 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -158,6 +158,11 @@
         /** Redirects to SafetyCenter if enabled. */
         @VisibleForTesting
         public void handleSafetyCenterRedirection() {
+            if (isFinishing()) {
+                // Don't trampoline if already exiting this activity.
+                return;
+            }
+
             if (SafetyCenterManagerWrapper.get().isEnabled(this)) {
                 try {
                     startActivity(new Intent(Intent.ACTION_SAFETY_CENTER));
@@ -219,6 +224,11 @@
         /** Redirects to SafetyCenter if enabled. */
         @VisibleForTesting
         public void handleSafetyCenterRedirection() {
+            if (isFinishing()) {
+                // Don't trampoline if already exiting this activity.
+                return;
+            }
+
             if (ACTION_PRIVACY_SETTINGS.equals(getIntent().getAction())
                     && SafetyCenterManagerWrapper.get().isEnabled(this)) {
                 try {
diff --git a/src/com/android/settings/applications/ApplicationFeatureProvider.java b/src/com/android/settings/applications/ApplicationFeatureProvider.java
index e9f877e..8a9f000 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProvider.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProvider.java
@@ -19,6 +19,8 @@
 import android.annotation.UserIdInt;
 import android.content.Intent;
 
+import androidx.annotation.NonNull;
+
 import java.util.List;
 import java.util.Set;
 
@@ -85,8 +87,9 @@
      * Returns a user readable text explaining how much time user has spent in an app at a
      * pre-specified duration.
      */
+    @NonNull
     default CharSequence getTimeSpentInApp(String packageName) {
-        return null;
+        return "";
     }
 
     /**
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceController.java
new file mode 100644
index 0000000..a1ae3a4
--- /dev/null
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceController.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 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.applications.specialaccess.notificationaccess;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.service.notification.NotificationListenerService;
+
+import androidx.preference.Preference;
+
+import com.android.settings.core.BasePreferenceController;
+
+import java.util.List;
+
+/**
+ * Controls link to reach more preference settings inside the app.
+ */
+public class MoreSettingsPreferenceController extends BasePreferenceController {
+
+    private static final String TAG = "MoreSettingsPrefContr";
+    private static final String KEY_MORE_SETTINGS = "more_settings";
+
+    PackageManager mPm;
+    String mPackage;
+    int mUserId;
+    Intent mIntent = new Intent(Intent.ACTION_MAIN)
+            .addCategory(NotificationListenerService.INTENT_CATEGORY_SETTINGS_HOME);
+
+    public MoreSettingsPreferenceController(Context context) {
+        super(context, KEY_MORE_SETTINGS);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        final List<ResolveInfo> resolveInfos = mPm.queryIntentActivities(
+                mIntent,
+                PackageManager.ResolveInfoFlags.of(0));
+        if (resolveInfos == null || resolveInfos.isEmpty()) {
+            return CONDITIONALLY_UNAVAILABLE;
+        }
+        return AVAILABLE;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_MORE_SETTINGS;
+    }
+
+    public MoreSettingsPreferenceController setPackageManager(PackageManager pm) {
+        mPm = pm;
+        return this;
+    }
+
+    public MoreSettingsPreferenceController setPackage(String pkg) {
+        mPackage = pkg;
+        mIntent.setPackage(mPackage);
+        return this;
+    }
+
+    public void updateState(Preference preference) {
+        preference.setIntent(mIntent);
+    }
+}
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
index e6feebb..531fb22 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
@@ -124,6 +124,9 @@
                 .setCn(mComponentName)
                 .setUserId(mUserId)
                 .setTargetSdk(listenerTargetSdk);
+        use(MoreSettingsPreferenceController.class)
+                .setPackage(mComponentName.getPackageName())
+                .setPackageManager(mPm);
         final int finalListenerTargetSdk = listenerTargetSdk;
         getPreferenceControllers().forEach(controllers -> {
             controllers.forEach(controller -> {
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
index d5de41a..ed7a1fc 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
@@ -87,9 +87,7 @@
                 return;
             }
 
-            if (mBluetoothManager.getCachedDeviceManager().shouldPairByCsip(device, groupId)) {
-                device.createBond(BluetoothDevice.TRANSPORT_LE);
-            }
+            mBluetoothManager.getCachedDeviceManager().pairDeviceByCsip(device, groupId);
         }
     }
 }
diff --git a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java
index 2ad9331..eb8add5 100644
--- a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java
@@ -88,6 +88,7 @@
         for (int simSlotNumber = 1; simSlotNumber < mTelephonyManager.getPhoneCount();
                 simSlotNumber++) {
             final Preference multiSimPreference = createNewPreference(screen.getContext());
+            multiSimPreference.setCopyingEnabled(true);
             multiSimPreference.setOrder(phonePreferenceOrder + simSlotNumber);
             multiSimPreference.setKey(KEY_PHONE_NUMBER + simSlotNumber);
             category.addPreference(multiSimPreference);
diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java b/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java
index 4204ec1..e392bd6 100644
--- a/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java
@@ -75,6 +75,7 @@
         for (int simSlotNumber = 1; simSlotNumber < mTelephonyManager.getPhoneCount();
                 simSlotNumber++) {
             final Preference multiSimPreference = createNewPreference(screen.getContext());
+            multiSimPreference.setCopyingEnabled(true);
             multiSimPreference.setOrder(simStatusOrder + simSlotNumber);
             multiSimPreference.setKey(KEY_SIM_STATUS + simSlotNumber);
             category.addPreference(multiSimPreference);
diff --git a/src/com/android/settings/display/ScreenSaverPreferenceController.java b/src/com/android/settings/display/ScreenSaverPreferenceController.java
index 676a567..de1aaea 100644
--- a/src/com/android/settings/display/ScreenSaverPreferenceController.java
+++ b/src/com/android/settings/display/ScreenSaverPreferenceController.java
@@ -18,6 +18,7 @@
 
 import androidx.preference.Preference;
 
+import com.android.settings.R;
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settings.dream.DreamSettings;
 import com.android.settingslib.core.AbstractPreferenceController;
@@ -26,9 +27,12 @@
         PreferenceControllerMixin {
 
     private static final String KEY_SCREEN_SAVER = "screensaver";
+    private final boolean mDreamsDisabledByAmbientModeSuppression;
 
     public ScreenSaverPreferenceController(Context context) {
         super(context);
+        mDreamsDisabledByAmbientModeSuppression = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
     }
 
     @Override
@@ -47,7 +51,12 @@
 
     @Override
     public void updateState(Preference preference) {
-        preference.setSummary(DreamSettings.getSummaryTextWithDreamName(mContext));
+        if (mDreamsDisabledByAmbientModeSuppression
+                && AmbientDisplayAlwaysOnPreferenceController.isAodSuppressedByBedtime(mContext)) {
+            preference.setSummary(R.string.screensaver_settings_when_to_dream_bedtime);
+        } else {
+            preference.setSummary(DreamSettings.getSummaryTextWithDreamName(mContext));
+        }
     }
 
     private boolean isSystemUser() {
diff --git a/src/com/android/settings/dream/WhenToDreamPreferenceController.java b/src/com/android/settings/dream/WhenToDreamPreferenceController.java
index 4108e85..02ae6a7 100644
--- a/src/com/android/settings/dream/WhenToDreamPreferenceController.java
+++ b/src/com/android/settings/dream/WhenToDreamPreferenceController.java
@@ -20,7 +20,10 @@
 
 import androidx.preference.Preference;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
 import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.display.AmbientDisplayAlwaysOnPreferenceController;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.dream.DreamBackend;
 
@@ -29,19 +32,34 @@
 
     private static final String WHEN_TO_START = "when_to_start";
     private final DreamBackend mBackend;
+    private final boolean mDreamsDisabledByAmbientModeSuppression;
 
     WhenToDreamPreferenceController(Context context) {
+        this(context, context.getResources().getBoolean(
+                com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig));
+    }
+
+    @VisibleForTesting
+    WhenToDreamPreferenceController(Context context,
+            boolean dreamsDisabledByAmbientModeSuppression) {
         super(context);
 
         mBackend = DreamBackend.getInstance(context);
+        mDreamsDisabledByAmbientModeSuppression = dreamsDisabledByAmbientModeSuppression;
     }
 
     @Override
     public void updateState(Preference preference) {
         super.updateState(preference);
 
-        int resId = DreamSettings.getDreamSettingDescriptionResId(mBackend.getWhenToDreamSetting());
-        preference.setSummary(preference.getContext().getString(resId));
+        if (mDreamsDisabledByAmbientModeSuppression
+                && AmbientDisplayAlwaysOnPreferenceController.isAodSuppressedByBedtime(mContext)) {
+            preference.setSummary(R.string.screensaver_settings_when_to_dream_bedtime);
+        } else {
+            final int resId = DreamSettings.getDreamSettingDescriptionResId(
+                    mBackend.getWhenToDreamSetting());
+            preference.setSummary(resId);
+        }
     }
 
     @Override
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java
new file mode 100644
index 0000000..5d8757d
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+/** A {@link BatteryUsageBroadcastReceiver} for battery usage data requesting. */
+public final class BatteryUsageBroadcastReceiver extends BroadcastReceiver {
+    private static final String TAG = "BatteryUsageBroadcastReceiver";
+    /** An intent action to request Settings to fetch usage data. */
+    public static final String ACTION_FETCH_BATTERY_USAGE_DATA =
+            "com.android.settings.battery.action.FETCH_BATTERY_USAGE_DATA";
+    /** An intent action to request Settings to clear cache data. */
+    public static final String ACTION_CLEAR_BATTERY_CACHE_DATA =
+            "com.android.settings.battery.action.CLEAR_BATTERY_CACHE_DATA";
+
+    @VisibleForTesting
+    static boolean sIsDebugMode = Build.TYPE.equals("userdebug");
+
+    @VisibleForTesting
+    boolean mFetchBatteryUsageData = false;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent == null || intent.getAction() == null) {
+            return;
+        }
+        Log.d(TAG, "onReceive:" + intent.getAction());
+        switch (intent.getAction()) {
+            case ACTION_FETCH_BATTERY_USAGE_DATA:
+                mFetchBatteryUsageData = true;
+                BatteryUsageDataLoader.enqueueWork(context);
+                break;
+            case ACTION_CLEAR_BATTERY_CACHE_DATA:
+                if (sIsDebugMode) {
+                    BatteryDiffEntry.clearCache();
+                    BatteryEntry.clearUidCache();
+                }
+                break;
+        }
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
new file mode 100644
index 0000000..8d5ba33
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2022 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.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDao;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
+
+import java.time.Clock;
+import java.time.Duration;
+
+/** {@link ContentProvider} class to fetch battery usage data. */
+public class BatteryUsageContentProvider extends ContentProvider {
+    private static final String TAG = "BatteryUsageContentProvider";
+
+    // TODO: Updates the duration to a more reasonable value for since-last-full-charge.
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    public static final Duration QUERY_DURATION_HOURS = Duration.ofHours(28);
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    public static final String QUERY_KEY_TIMESTAMP = "timestamp";
+
+    /** Codes */
+    private static final int BATTERY_STATE_CODE = 1;
+    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+    static {
+        sUriMatcher.addURI(
+                DatabaseUtils.AUTHORITY,
+                /*path=*/ DatabaseUtils.BATTERY_STATE_TABLE,
+                /*code=*/ BATTERY_STATE_CODE);
+    }
+
+    private Clock mClock;
+    private BatteryStateDao mBatteryStateDao;
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    public void setClock(Clock clock) {
+        this.mClock = clock;
+    }
+
+    @Override
+    public boolean onCreate() {
+        if (DatabaseUtils.isWorkProfile(getContext())) {
+            Log.w(TAG, "do not create provider for work profile");
+            return false;
+        }
+        mClock = Clock.systemUTC();
+        mBatteryStateDao = BatteryStateDatabase.getInstance(getContext()).batteryStateDao();
+        Log.w(TAG, "create content provider from " + getCallingPackage());
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public Cursor query(
+            @NonNull Uri uri,
+            @Nullable String[] strings,
+            @Nullable String s,
+            @Nullable String[] strings1,
+            @Nullable String s1) {
+        switch (sUriMatcher.match(uri)) {
+            case BATTERY_STATE_CODE:
+                return getBatteryStates(uri);
+            default:
+                throw new IllegalArgumentException("unknown URI: " + uri);
+        }
+    }
+
+    @Nullable
+    @Override
+    public String getType(@NonNull Uri uri) {
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
+        switch (sUriMatcher.match(uri)) {
+            case BATTERY_STATE_CODE:
+                try {
+                    mBatteryStateDao.insert(BatteryState.create(contentValues));
+                    return uri;
+                } catch (RuntimeException e) {
+                    Log.e(TAG, "insert() from:" + uri + " error:" + e);
+                    return null;
+                }
+            default:
+                throw new IllegalArgumentException("unknown URI: " + uri);
+        }
+    }
+
+    @Override
+    public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
+        throw new UnsupportedOperationException("unsupported!");
+    }
+
+    @Override
+    public int update(
+            @NonNull Uri uri,
+            @Nullable ContentValues contentValues,
+            @Nullable String s,
+            @Nullable String[] strings) {
+        throw new UnsupportedOperationException("unsupported!");
+    }
+
+    private Cursor getBatteryStates(Uri uri) {
+        final long defaultTimestamp = mClock.millis() - QUERY_DURATION_HOURS.toMillis();
+        final long queryTimestamp = getQueryTimestamp(uri, defaultTimestamp);
+        return getBatteryStates(uri, queryTimestamp);
+    }
+
+    private Cursor getBatteryStates(Uri uri, long firstTimestamp) {
+        final long timestamp = mClock.millis();
+        Cursor cursor = null;
+        try {
+            cursor = mBatteryStateDao.getCursorAfter(firstTimestamp);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "query() from:" + uri + " error:" + e);
+        }
+        // TODO: Invokes hourly job recheck.
+        Log.w(TAG, "query battery states in " + (mClock.millis() - timestamp) + "/ms");
+        return cursor;
+    }
+
+    // If URI contains query parameter QUERY_KEY_TIMESTAMP, use the value directly.
+    // Otherwise, load the data for QUERY_DURATION_HOURS by default.
+    private long getQueryTimestamp(Uri uri, long defaultTimestamp) {
+        final String firstTimestampString = uri.getQueryParameter(QUERY_KEY_TIMESTAMP);
+        if (TextUtils.isEmpty(firstTimestampString)) {
+            Log.w(TAG, "empty query timestamp");
+            return defaultTimestamp;
+        }
+
+        try {
+            return Long.parseLong(firstTimestampString);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "invalid query timestamp: " + firstTimestampString, e);
+            return defaultTimestamp;
+        }
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
new file mode 100644
index 0000000..dc9c8b7
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 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.os.AsyncTask;
+import android.os.BatteryUsageStats;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.List;
+
+/** Load battery usage data in the background. */
+public final class BatteryUsageDataLoader {
+    private static final String TAG = "BatteryUsageDataLoader";
+
+    @VisibleForTesting
+    static BatteryAppListPreferenceController sController;
+
+    private BatteryUsageDataLoader() {
+    }
+
+    static void enqueueWork(Context context) {
+        AsyncTask.execute(() -> {
+            Log.d(TAG, "loadUsageDataSafely() in the AsyncTask");
+            loadUsageDataSafely(context.getApplicationContext());
+        });
+    }
+
+    @VisibleForTesting
+    static void loadUsageData(Context context) {
+        // Checks whether the battery content provider is available.
+        if (!DatabaseUtils.isContentProviderEnabled(context)) {
+            Log.w(TAG, "battery usage content provider is disabled!");
+            return;
+        }
+        final long start = System.currentTimeMillis();
+        final BatteryUsageStats batteryUsageStats = DataProcessor.getBatteryUsageStats(context);
+        final List<BatteryEntry> batteryEntryList =
+                DataProcessor.generateBatteryEntryListFromBatteryUsageStats(
+                        context,
+                        batteryUsageStats,
+                        sController);
+        if (batteryEntryList == null || batteryEntryList.isEmpty()) {
+            Log.w(TAG, "getBatteryEntryList() returns null or empty content");
+        }
+        final long elapsedTime = System.currentTimeMillis() - start;
+        Log.d(TAG, String.format("getBatteryUsageStats() in %d/ms", elapsedTime));
+
+        // Uploads the BatteryEntry data into SettingsIntelligence.
+        DatabaseUtils.sendBatteryEntryData(
+                context, batteryEntryList, batteryUsageStats);
+        DataProcessor.closeBatteryUsageStats(batteryUsageStats);
+    }
+
+    private static void loadUsageDataSafely(Context context) {
+        try {
+            loadUsageData(context);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "loadUsageData:" + e);
+        }
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
new file mode 100644
index 0000000..0e2a81f
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2022 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.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.BatteryUsageStats;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.fuelgauge.BatteryStatus;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** A utility class to operate battery usage database. */
+public final class DatabaseUtils {
+    private static final String TAG = "DatabaseUtils";
+    private static final String PREF_FILE_NAME = "battery_module_preference";
+    private static final String PREF_FULL_CHARGE_TIMESTAMP_KEY = "last_full_charge_timestamp_key";
+    /** Key for query parameter timestamp used in BATTERY_CONTENT_URI **/
+    private static final String QUERY_KEY_TIMESTAMP = "timestamp";
+    /** Clear memory threshold for device booting phase. **/
+    private static final long CLEAR_MEMORY_THRESHOLD_MS = Duration.ofMinutes(5).toMillis();
+    private static final long CLEAR_MEMORY_DELAYED_MS = Duration.ofSeconds(2).toMillis();
+
+    /** An authority name of the battery content provider. */
+    public static final String AUTHORITY = "com.android.settings.battery.usage.provider";
+    /** A table name for battery usage history. */
+    public static final String BATTERY_STATE_TABLE = "BatteryState";
+    /** A class name for battery usage data provider. */
+    public static final String SETTINGS_PACKAGE_PATH = "com.android.settings";
+    public static final String BATTERY_PROVIDER_CLASS_PATH =
+            "com.android.settings.fuelgauge.batteryusage.BatteryUsageContentProvider";
+
+    /** A content URI to access battery usage states data. */
+    public static final Uri BATTERY_CONTENT_URI =
+            new Uri.Builder()
+                    .scheme(ContentResolver.SCHEME_CONTENT)
+                    .authority(AUTHORITY)
+                    .appendPath(BATTERY_STATE_TABLE)
+                    .build();
+
+    private DatabaseUtils() {
+    }
+
+    /** Returns true if current user is a work profile user. */
+    public static boolean isWorkProfile(Context context) {
+        final UserManager userManager = context.getSystemService(UserManager.class);
+        return userManager.isManagedProfile() && !userManager.isSystemUser();
+    }
+
+    /** Returns true if the chart graph design is enabled. */
+    public static boolean isChartGraphEnabled(Context context) {
+        return isContentProviderEnabled(context);
+    }
+
+    /** Long: for timestamp and String: for BatteryHistEntry.getKey() */
+    public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge(
+            Context context, Calendar calendar) {
+        final long startTime = System.currentTimeMillis();
+        final long lastFullChargeTimestamp =
+                getStartTimestampForLastFullCharge(context, calendar);
+        // Builds the content uri everytime to avoid cache.
+        final Uri batteryStateUri =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(AUTHORITY)
+                        .appendPath(BATTERY_STATE_TABLE)
+                        .appendQueryParameter(
+                                QUERY_KEY_TIMESTAMP, Long.toString(lastFullChargeTimestamp))
+                        .build();
+
+        final Map<Long, Map<String, BatteryHistEntry>> resultMap =
+                loadHistoryMapFromContentProvider(context, batteryStateUri);
+        if (resultMap == null || resultMap.isEmpty()) {
+            Log.d(TAG, "getHistoryMapSinceLastFullCharge() returns empty or null");
+        } else {
+            Log.d(TAG, String.format("getHistoryMapSinceLastFullCharge() size=%d in %d/ms",
+                    resultMap.size(), (System.currentTimeMillis() - startTime)));
+        }
+        return resultMap;
+    }
+
+    static boolean isContentProviderEnabled(Context context) {
+        return context.getPackageManager()
+                .getComponentEnabledSetting(
+                        new ComponentName(SETTINGS_PACKAGE_PATH, BATTERY_PROVIDER_CLASS_PATH))
+                == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+    }
+
+    static List<ContentValues> sendBatteryEntryData(
+            Context context,
+            List<BatteryEntry> batteryEntryList,
+            BatteryUsageStats batteryUsageStats) {
+        final long startTime = System.currentTimeMillis();
+        final Intent intent = getBatteryIntent(context);
+        if (intent == null) {
+            Log.e(TAG, "sendBatteryEntryData(): cannot fetch battery intent");
+            clearMemory();
+            return null;
+        }
+        final int batteryLevel = getBatteryLevel(intent);
+        final int batteryStatus = intent.getIntExtra(
+                BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
+        final int batteryHealth = intent.getIntExtra(
+                BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
+        // We should use the same timestamp for each data snapshot.
+        final long snapshotTimestamp = Clock.systemUTC().millis();
+        final long snapshotBootTimestamp = SystemClock.elapsedRealtime();
+
+        // Creates the ContentValues list to insert them into provider.
+        final List<ContentValues> valuesList = new ArrayList<>();
+        if (batteryEntryList != null) {
+            batteryEntryList.stream()
+                    .filter(entry -> {
+                        final long foregroundMs = entry.getTimeInForegroundMs();
+                        final long backgroundMs = entry.getTimeInBackgroundMs();
+                        if (entry.getConsumedPower() == 0
+                                && (foregroundMs != 0 || backgroundMs != 0)) {
+                            Log.w(TAG, String.format(
+                                    "no consumed power but has running time for %s time=%d|%d",
+                                    entry.getLabel(), foregroundMs, backgroundMs));
+                        }
+                        return entry.getConsumedPower() != 0
+                                || foregroundMs != 0
+                                || backgroundMs != 0;
+                    })
+                    .forEach(entry -> valuesList.add(
+                            ConvertUtils.convertToContentValues(
+                                    entry,
+                                    batteryUsageStats,
+                                    batteryLevel,
+                                    batteryStatus,
+                                    batteryHealth,
+                                    snapshotBootTimestamp,
+                                    snapshotTimestamp)));
+        }
+
+        int size = 1;
+        final ContentResolver resolver = context.getContentResolver();
+        // Inserts all ContentValues into battery provider.
+        if (!valuesList.isEmpty()) {
+            final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
+            valuesList.toArray(valuesArray);
+            try {
+                size = resolver.bulkInsert(BATTERY_CONTENT_URI, valuesArray);
+            } catch (Exception e) {
+                Log.e(TAG, "bulkInsert() data into database error:\n" + e);
+            }
+        } else {
+            // Inserts one fake data into battery provider.
+            final ContentValues contentValues =
+                    ConvertUtils.convertToContentValues(
+                            /*entry=*/ null,
+                            /*batteryUsageStats=*/ null,
+                            batteryLevel,
+                            batteryStatus,
+                            batteryHealth,
+                            snapshotBootTimestamp,
+                            snapshotTimestamp);
+            try {
+                resolver.insert(BATTERY_CONTENT_URI, contentValues);
+            } catch (Exception e) {
+                Log.e(TAG, "insert() data into database error:\n" + e);
+            }
+            valuesList.add(contentValues);
+        }
+        saveLastFullChargeTimestampPref(context, batteryStatus, batteryLevel, snapshotTimestamp);
+        resolver.notifyChange(BATTERY_CONTENT_URI, /*observer=*/ null);
+        Log.d(TAG, String.format("sendBatteryEntryData() size=%d in %d/ms",
+                size, (System.currentTimeMillis() - startTime)));
+        clearMemory();
+        return valuesList;
+    }
+
+    @VisibleForTesting
+    static void saveLastFullChargeTimestampPref(
+            Context context, int batteryStatus, int batteryLevel, long timestamp) {
+        // Updates the SharedPreference only when timestamp is valid and phone is full charge.
+        if (!BatteryStatus.isCharged(batteryStatus, batteryLevel)) {
+            return;
+        }
+
+        final boolean success =
+                getSharedPreferences(context)
+                        .edit()
+                        .putLong(PREF_FULL_CHARGE_TIMESTAMP_KEY, timestamp)
+                        .commit();
+        if (!success) {
+            Log.w(TAG, "saveLastFullChargeTimestampPref() fail: value=" + timestamp);
+        }
+    }
+
+    @VisibleForTesting
+    static long getLastFullChargeTimestampPref(Context context) {
+        return getSharedPreferences(context).getLong(PREF_FULL_CHARGE_TIMESTAMP_KEY, 0);
+    }
+
+    /**
+     * Returns the start timestamp for "since last full charge" battery usage chart.
+     * If the last full charge happens within the last 7 days, returns the timestamp of last full
+     * charge. Otherwise, returns the timestamp for 00:00 6 days before the calendar date.
+     */
+    @VisibleForTesting
+    static long getStartTimestampForLastFullCharge(
+            Context context, Calendar calendar) {
+        final long lastFullChargeTimestamp = getLastFullChargeTimestampPref(context);
+        final long sixDayAgoTimestamp = getTimestampSixDaysAgo(calendar);
+        return Math.max(lastFullChargeTimestamp, sixDayAgoTimestamp);
+    }
+
+    private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
+            Context context, Uri batteryStateUri) {
+        final boolean isWorkProfileUser = isWorkProfile(context);
+        Log.d(TAG, "loadHistoryMapFromContentProvider() isWorkProfileUser:" + isWorkProfileUser);
+        if (isWorkProfileUser) {
+            try {
+                context = context.createPackageContextAsUser(
+                        /*packageName=*/ context.getPackageName(),
+                        /*flags=*/ 0,
+                        /*user=*/ UserHandle.OWNER);
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "context.createPackageContextAsUser() fail:" + e);
+                return null;
+            }
+        }
+        if (!isContentProviderEnabled(context)) {
+            return null;
+        }
+        final Map<Long, Map<String, BatteryHistEntry>> resultMap = new HashMap();
+        try (Cursor cursor =
+                     context.getContentResolver().query(batteryStateUri, null, null, null)) {
+            if (cursor == null || cursor.getCount() == 0) {
+                return resultMap;
+            }
+            // Loads and recovers all BatteryHistEntry data from cursor.
+            while (cursor.moveToNext()) {
+                final BatteryHistEntry entry = new BatteryHistEntry(cursor);
+                final long timestamp = entry.mTimestamp;
+                final String key = entry.getKey();
+                Map batteryHistEntryMap = resultMap.get(timestamp);
+                // Creates new one if there is no corresponding map.
+                if (batteryHistEntryMap == null) {
+                    batteryHistEntryMap = new HashMap<>();
+                    resultMap.put(timestamp, batteryHistEntryMap);
+                }
+                batteryHistEntryMap.put(key, entry);
+            }
+        }
+        return resultMap;
+    }
+
+    /** Gets the latest sticky battery intent from framework. */
+    private static Intent getBatteryIntent(Context context) {
+        return context.registerReceiver(
+                /*receiver=*/ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+    }
+
+    private static int getBatteryLevel(Intent intent) {
+        final int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+        final int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+        return scale == 0
+                ? -1 /*invalid battery level*/
+                : Math.round((level / (float) scale) * 100f);
+    }
+
+    private static void clearMemory() {
+        if (SystemClock.uptimeMillis() > CLEAR_MEMORY_THRESHOLD_MS) {
+            return;
+        }
+        final Handler mainHandler = new Handler(Looper.getMainLooper());
+        mainHandler.postDelayed(() -> {
+            System.gc();
+            System.runFinalization();
+            System.gc();
+            Log.w(TAG, "invoke clearMemory()");
+        }, CLEAR_MEMORY_DELAYED_MS);
+    }
+
+    private static SharedPreferences getSharedPreferences(Context context) {
+        return context
+                .getApplicationContext() // ensures we bind it with application
+                .createDeviceProtectedStorageContext()
+                .getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
+    }
+
+    /** Returns the timestamp for 00:00 6 days before the calendar date. */
+    private static long getTimestampSixDaysAgo(Calendar calendar) {
+        Calendar startCalendar =
+                calendar == null ? Calendar.getInstance() : (Calendar) calendar.clone();
+        startCalendar.add(Calendar.DAY_OF_YEAR, -6);
+        startCalendar.set(Calendar.HOUR_OF_DAY, 0);
+        startCalendar.set(Calendar.MINUTE, 0);
+        startCalendar.set(Calendar.SECOND, 0);
+        startCalendar.set(Calendar.MILLISECOND, 0);
+        return startCalendar.getTimeInMillis();
+    }
+
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryState.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryState.java
new file mode 100644
index 0000000..11db118
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryState.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2022 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.db;
+
+import android.content.ContentValues;
+import android.content.Intent;
+import android.os.BatteryManager;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/** A {@link Entity} class to save battery states snapshot into database. */
+@Entity
+public class BatteryState {
+    private static String sCacheZoneId;
+    private static SimpleDateFormat sCacheSimpleDateFormat;
+
+    @PrimaryKey(autoGenerate = true)
+    private long mId;
+
+    // Records the app relative information.
+    public final long uid;
+    public final long userId;
+    public final String appLabel;
+    public final String packageName;
+    // Whether the data is represented as system component or not?
+    public final boolean isHidden;
+    // Records the timestamp relative information.
+    public final long bootTimestamp;
+    public final long timestamp;
+    public final String zoneId;
+    // Records the battery usage relative information.
+    public final double totalPower;
+    public final double consumePower;
+    public final double percentOfTotal;
+    public final long foregroundUsageTimeInMs;
+    public final long backgroundUsageTimeInMs;
+    public final int drainType;
+    public final int consumerType;
+    // Records the battery intent relative information.
+    public final int batteryLevel;
+    public final int batteryStatus;
+    public final int batteryHealth;
+
+    public BatteryState(
+            long uid,
+            long userId,
+            String appLabel,
+            String packageName,
+            boolean isHidden,
+            long bootTimestamp,
+            long timestamp,
+            String zoneId,
+            double totalPower,
+            double consumePower,
+            double percentOfTotal,
+            long foregroundUsageTimeInMs,
+            long backgroundUsageTimeInMs,
+            int drainType,
+            int consumerType,
+            int batteryLevel,
+            int batteryStatus,
+            int batteryHealth) {
+        // Records the app relative information.
+        this.uid = uid;
+        this.userId = userId;
+        this.appLabel = appLabel;
+        this.packageName = packageName;
+        this.isHidden = isHidden;
+        // Records the timestamp relative information.
+        this.bootTimestamp = bootTimestamp;
+        this.timestamp = timestamp;
+        this.zoneId = zoneId;
+        // Records the battery usage relative information.
+        this.totalPower = totalPower;
+        this.consumePower = consumePower;
+        this.percentOfTotal = percentOfTotal;
+        this.foregroundUsageTimeInMs = foregroundUsageTimeInMs;
+        this.backgroundUsageTimeInMs = backgroundUsageTimeInMs;
+        this.drainType = drainType;
+        this.consumerType = consumerType;
+        // Records the battery intent relative information.
+        this.batteryLevel = batteryLevel;
+        this.batteryStatus = batteryStatus;
+        this.batteryHealth = batteryHealth;
+    }
+
+    /** Sets the auto-generated content ID. */
+    public void setId(long id) {
+        this.mId = id;
+    }
+
+    /** Gets the auto-generated content ID. */
+    public long getId() {
+        return mId;
+    }
+
+    @Override
+    @SuppressWarnings("JavaUtilDate")
+    public String toString() {
+        final String currentZoneId = TimeZone.getDefault().getID();
+        if (!currentZoneId.equals(sCacheZoneId) || sCacheSimpleDateFormat == null) {
+            sCacheZoneId = currentZoneId;
+            sCacheSimpleDateFormat = new SimpleDateFormat("MMM dd,yyyy HH:mm:ss", Locale.US);
+        }
+        final String recordAtDateTime = sCacheSimpleDateFormat.format(new Date(timestamp));
+        final StringBuilder builder = new StringBuilder()
+                .append("\nBatteryState{")
+                .append(String.format(Locale.US,
+                        "\n\tpackage=%s|label=%s|uid=%d|userId=%d|isHidden=%b",
+                        packageName, appLabel, uid, userId, isHidden))
+                .append(String.format(Locale.US, "\n\ttimestamp=%s|zoneId=%s|bootTimestamp=%d",
+                        recordAtDateTime, zoneId, Duration.ofMillis(bootTimestamp).getSeconds()))
+                .append(String.format(Locale.US,
+                        "\n\tusage=%f|total=%f|consume=%f|elapsedTime=%d|%d",
+                        percentOfTotal, totalPower, consumePower,
+                        Duration.ofMillis(foregroundUsageTimeInMs).getSeconds(),
+                        Duration.ofMillis(backgroundUsageTimeInMs).getSeconds()))
+                .append(String.format(Locale.US,
+                        "\n\tdrain=%d|consumer=%d", drainType, consumerType))
+                .append(String.format(Locale.US, "\n\tbattery=%d|status=%d|health=%d\n}",
+                        batteryLevel, batteryStatus, batteryHealth));
+        return builder.toString();
+    }
+
+
+    /** Creates new {@link BatteryState} from {@link ContentValues}. */
+    public static BatteryState create(ContentValues contentValues) {
+        Builder builder = BatteryState.newBuilder();
+        if (contentValues.containsKey("uid")) {
+            builder.setUid(contentValues.getAsLong("uid"));
+        }
+        if (contentValues.containsKey("userId")) {
+            builder.setUserId(contentValues.getAsLong("userId"));
+        }
+        if (contentValues.containsKey("appLabel")) {
+            builder.setAppLabel(contentValues.getAsString("appLabel"));
+        }
+        if (contentValues.containsKey("packageName")) {
+            builder.setPackageName(contentValues.getAsString("packageName"));
+        }
+        if (contentValues.containsKey("isHidden")) {
+            builder.setIsHidden(contentValues.getAsBoolean("isHidden"));
+        }
+        if (contentValues.containsKey("bootTimestamp")) {
+            builder.setBootTimestamp(contentValues.getAsLong("bootTimestamp"));
+        }
+        if (contentValues.containsKey("timestamp")) {
+            builder.setTimestamp(contentValues.getAsLong("timestamp"));
+        }
+        if (contentValues.containsKey("consumePower")) {
+            builder.setConsumePower(contentValues.getAsDouble("consumePower"));
+        }
+        if (contentValues.containsKey("totalPower")) {
+            builder.setTotalPower(contentValues.getAsDouble("totalPower"));
+        }
+        if (contentValues.containsKey("percentOfTotal")) {
+            builder.setPercentOfTotal(contentValues.getAsDouble("percentOfTotal"));
+        }
+        if (contentValues.containsKey("foregroundUsageTimeInMs")) {
+            builder.setForegroundUsageTimeInMs(
+                    contentValues.getAsLong("foregroundUsageTimeInMs"));
+        }
+        if (contentValues.containsKey("backgroundUsageTimeInMs")) {
+            builder.setBackgroundUsageTimeInMs(
+                    contentValues.getAsLong("backgroundUsageTimeInMs"));
+        }
+        if (contentValues.containsKey("drainType")) {
+            builder.setDrainType(contentValues.getAsInteger("drainType"));
+        }
+        if (contentValues.containsKey("consumerType")) {
+            builder.setConsumerType(contentValues.getAsInteger("consumerType"));
+        }
+        if (contentValues.containsKey("batteryLevel")) {
+            builder.setBatteryLevel(contentValues.getAsInteger("batteryLevel"));
+        }
+        if (contentValues.containsKey("batteryStatus")) {
+            builder.setBatteryStatus(contentValues.getAsInteger("batteryStatus"));
+        }
+        if (contentValues.containsKey("batteryHealth")) {
+            builder.setBatteryHealth(contentValues.getAsInteger("batteryHealth"));
+        }
+        return builder.build();
+    }
+
+    /** Creates a new {@link Builder} instance. */
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /** A convenience builder class to improve readability. */
+    public static class Builder {
+        private long mUid;
+        private long mUserId;
+        private String mAppLabel;
+        private String mPackageName;
+        private boolean mIsHidden;
+        private long mBootTimestamp;
+        private long mTimestamp;
+        private double mTotalPower;
+        private double mConsumePower;
+        private double mPercentOfTotal;
+        private long mForegroundUsageTimeInMs;
+        private long mBackgroundUsageTimeInMs;
+        private int mDrainType;
+        private int mConsumerType;
+        private int mBatteryLevel;
+        private int mBatteryStatus;
+        private int mBatteryHealth;
+
+        /** Sets the uid. */
+        @CanIgnoreReturnValue
+        public Builder setUid(long uid) {
+            this.mUid = uid;
+            return this;
+        }
+
+        /** Sets the user ID. */
+        @CanIgnoreReturnValue
+        public Builder setUserId(long userId) {
+            this.mUserId = userId;
+            return this;
+        }
+
+        /** Sets the app label. */
+        @CanIgnoreReturnValue
+        public Builder setAppLabel(String appLabel) {
+            this.mAppLabel = appLabel;
+            return this;
+        }
+
+        /** Sets the package name. */
+        @CanIgnoreReturnValue
+        public Builder setPackageName(String packageName) {
+            this.mPackageName = packageName;
+            return this;
+        }
+
+        /** Sets the is hidden value. */
+        @CanIgnoreReturnValue
+        public Builder setIsHidden(boolean isHidden) {
+            this.mIsHidden = isHidden;
+            return this;
+        }
+
+        /** Sets the boot timestamp. */
+        @CanIgnoreReturnValue
+        public Builder setBootTimestamp(long bootTimestamp) {
+            this.mBootTimestamp = bootTimestamp;
+            return this;
+        }
+
+        /** Sets the timestamp. */
+        @CanIgnoreReturnValue
+        public Builder setTimestamp(long timestamp) {
+            this.mTimestamp = timestamp;
+            return this;
+        }
+
+        /** Sets the total power. */
+        @CanIgnoreReturnValue
+        public Builder setTotalPower(double totalPower) {
+            this.mTotalPower = totalPower;
+            return this;
+        }
+
+        /** Sets the consumed power. */
+        @CanIgnoreReturnValue
+        public Builder setConsumePower(double consumePower) {
+            this.mConsumePower = consumePower;
+            return this;
+        }
+
+        /** Sets the percentage of total. */
+        @CanIgnoreReturnValue
+        public Builder setPercentOfTotal(double percentOfTotal) {
+            this.mPercentOfTotal = percentOfTotal;
+            return this;
+        }
+
+        /** Sets the foreground usage time. */
+        @CanIgnoreReturnValue
+        public Builder setForegroundUsageTimeInMs(long foregroundUsageTimeInMs) {
+            this.mForegroundUsageTimeInMs = foregroundUsageTimeInMs;
+            return this;
+        }
+
+        /** Sets the background usage time. */
+        @CanIgnoreReturnValue
+        public Builder setBackgroundUsageTimeInMs(long backgroundUsageTimeInMs) {
+            this.mBackgroundUsageTimeInMs = backgroundUsageTimeInMs;
+            return this;
+        }
+
+        /** Sets the drain type. */
+        @CanIgnoreReturnValue
+        public Builder setDrainType(int drainType) {
+            this.mDrainType = drainType;
+            return this;
+        }
+
+        /** Sets the consumer type. */
+        @CanIgnoreReturnValue
+        public Builder setConsumerType(int consumerType) {
+            this.mConsumerType = consumerType;
+            return this;
+        }
+
+        /** Sets the battery level. */
+        @CanIgnoreReturnValue
+        public Builder setBatteryLevel(int batteryLevel) {
+            this.mBatteryLevel = batteryLevel;
+            return this;
+        }
+
+        /** Sets the battery status. */
+        @CanIgnoreReturnValue
+        public Builder setBatteryStatus(int batteryStatus) {
+            this.mBatteryStatus = batteryStatus;
+            return this;
+        }
+
+        /** Sets the battery health. */
+        @CanIgnoreReturnValue
+        public Builder setBatteryHealth(int batteryHealth) {
+            this.mBatteryHealth = batteryHealth;
+            return this;
+        }
+
+        /** Sets the battery intent. */
+        @CanIgnoreReturnValue
+        public Builder setBatteryIntent(Intent batteryIntent) {
+            final int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+            final int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+            this.mBatteryLevel =
+                    scale == 0
+                            ? -1 /*invalid battery level*/
+                            : Math.round((level / (float) scale) * 100f);
+            this.mBatteryStatus =
+                    batteryIntent.getIntExtra(
+                            BatteryManager.EXTRA_STATUS,
+                            BatteryManager.BATTERY_STATUS_UNKNOWN);
+            this.mBatteryHealth =
+                    batteryIntent.getIntExtra(
+                            BatteryManager.EXTRA_HEALTH,
+                            BatteryManager.BATTERY_HEALTH_UNKNOWN);
+            return this;
+        }
+
+        /** Builds the BatteryState. */
+        public BatteryState build() {
+            return new BatteryState(
+                    mUid,
+                    mUserId,
+                    mAppLabel,
+                    mPackageName,
+                    mIsHidden,
+                    mBootTimestamp,
+                    mTimestamp,
+                    /*zoneId=*/ TimeZone.getDefault().getID(),
+                    mTotalPower,
+                    mConsumePower,
+                    mPercentOfTotal,
+                    mForegroundUsageTimeInMs,
+                    mBackgroundUsageTimeInMs,
+                    mDrainType,
+                    mConsumerType,
+                    mBatteryLevel,
+                    mBatteryStatus,
+                    mBatteryHealth);
+        }
+
+        private Builder() {}
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java
new file mode 100644
index 0000000..b1afa6b
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 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.db;
+
+import android.database.Cursor;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+import java.util.List;
+
+/** Data access object for accessing {@link BatteryState} in the database. */
+@Dao
+public interface BatteryStateDao {
+
+    /** Inserts a {@link BatteryState} data into the database. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(BatteryState state);
+
+    /** Inserts {@link BatteryState} data into the database. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertAll(List<BatteryState> states);
+
+    /** Lists all recorded data after a specific timestamp. */
+    @Query("SELECT * FROM BatteryState WHERE timestamp > :timestamp ORDER BY timestamp DESC")
+    List<BatteryState> getAllAfter(long timestamp);
+
+    /** Gets the {@link Cursor} of all recorded data from a specific timestamp. */
+    @Query("SELECT * FROM BatteryState WHERE timestamp >= :timestamp ORDER BY timestamp DESC")
+    Cursor getCursorAfter(long timestamp);
+
+    /** Get the count of distinct timestamp after a specific timestamp. */
+    @Query("SELECT COUNT(DISTINCT timestamp) FROM BatteryState WHERE timestamp > :timestamp")
+    int getDistinctTimestampCount(long timestamp);
+
+    /** Lists all distinct timestamps after a specific timestamp. */
+    @Query("SELECT DISTINCT timestamp FROM BatteryState WHERE timestamp > :timestamp")
+    List<Long> getDistinctTimestamps(long timestamp);
+
+    /** Deletes all recorded data before a specific timestamp. */
+    @Query("DELETE FROM BatteryState WHERE timestamp <= :timestamp")
+    void clearAllBefore(long timestamp);
+
+    /** Clears all recorded data in the database. */
+    @Query("DELETE FROM BatteryState")
+    void clearAll();
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java
new file mode 100644
index 0000000..9396546
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.db;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
+/** A {@link RoomDatabase} for battery usage states history. */
+@Database(
+        entities = {BatteryState.class},
+        version = 1)
+public abstract class BatteryStateDatabase extends RoomDatabase {
+    private static final String TAG = "BatteryStateDatabase";
+
+    private static BatteryStateDatabase sBatteryStateDatabase;
+
+    /** Provides DAO for battery state table. */
+    public abstract BatteryStateDao batteryStateDao();
+
+    /** Gets or creates an instance of {@link RoomDatabase}. */
+    public static BatteryStateDatabase getInstance(Context context) {
+        if (sBatteryStateDatabase == null) {
+            sBatteryStateDatabase =
+                    Room.databaseBuilder(
+                                    context, BatteryStateDatabase.class, "battery-usage-db-v1")
+                            // Allows accessing data in the main thread for dumping bugreport.
+                            .allowMainThreadQueries()
+                            .fallbackToDestructiveMigration()
+                            .build();
+            Log.d(TAG, "initialize battery states database");
+        }
+        return sBatteryStateDatabase;
+    }
+
+    /** Sets the instance of {@link RoomDatabase}. */
+    public static void setBatteryStateDatabase(BatteryStateDatabase database) {
+        BatteryStateDatabase.sBatteryStateDatabase = database;
+    }
+}
diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java
index 0da2d50..a94a1bf 100644
--- a/src/com/android/settings/network/NetworkDashboardFragment.java
+++ b/src/com/android/settings/network/NetworkDashboardFragment.java
@@ -121,7 +121,7 @@
             controllers.add(internetPreferenceController);
         }
         controllers.add(privateDnsPreferenceController);
-        controllers.add(new NetworkProviderCallsSmsController(context, lifecycle));
+        controllers.add(new NetworkProviderCallsSmsController(context, lifecycle, lifecycleOwner));
         return controllers;
     }
 
diff --git a/src/com/android/settings/network/NetworkProviderCallsSmsController.java b/src/com/android/settings/network/NetworkProviderCallsSmsController.java
index c8b1c49..bfe30f7 100644
--- a/src/com/android/settings/network/NetworkProviderCallsSmsController.java
+++ b/src/com/android/settings/network/NetworkProviderCallsSmsController.java
@@ -21,13 +21,13 @@
 import android.content.Context;
 import android.os.UserManager;
 import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.util.Log;
 import android.view.View;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.OnLifecycleEvent;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
@@ -37,50 +37,55 @@
 import com.android.settingslib.Utils;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
+import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
+import com.android.settingslib.mobile.dataservice.UiccInfoEntity;
 
 import java.util.List;
 
 public class NetworkProviderCallsSmsController extends AbstractPreferenceController implements
-        SubscriptionsChangeListener.SubscriptionsChangeListenerClient, LifecycleObserver {
+        LifecycleObserver, MobileNetworkRepository.MobileNetworkCallback {
 
     private static final String TAG = "NetworkProviderCallsSmsController";
     private static final String KEY = "calls_and_sms";
     private static final String RTL_MARK = "\u200F";
 
     private UserManager mUserManager;
-    private SubscriptionManager mSubscriptionManager;
-    private SubscriptionsChangeListener mSubscriptionsChangeListener;
     private TelephonyManager mTelephonyManager;
     private RestrictedPreference mPreference;
     private boolean mIsRtlMode;
+    private LifecycleOwner mLifecycleOwner;
+    private MobileNetworkRepository mMobileNetworkRepository;
+    private List<SubscriptionInfoEntity> mSubInfoEntityList;
 
     /**
      * The summary text and click behavior of the "Calls & SMS" item on the
      * Network & internet page.
      */
-    public NetworkProviderCallsSmsController(Context context, Lifecycle lifecycle) {
+    public NetworkProviderCallsSmsController(Context context, Lifecycle lifecycle,
+            LifecycleOwner lifecycleOwner) {
         super(context);
 
         mUserManager = context.getSystemService(UserManager.class);
-        mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
         mIsRtlMode = context.getResources().getConfiguration().getLayoutDirection()
                 == View.LAYOUT_DIRECTION_RTL;
+        mLifecycleOwner = lifecycleOwner;
+        mMobileNetworkRepository = new MobileNetworkRepository(context, this);
         if (lifecycle != null) {
-            mSubscriptionsChangeListener = new SubscriptionsChangeListener(context, this);
             lifecycle.addObserver(this);
         }
     }
 
     @OnLifecycleEvent(Event.ON_RESUME)
     public void onResume() {
-        mSubscriptionsChangeListener.start();
+        mMobileNetworkRepository.addRegister(mLifecycleOwner);
         update();
     }
 
     @OnLifecycleEvent(Event.ON_PAUSE)
     public void onPause() {
-        mSubscriptionsChangeListener.stop();
+        mMobileNetworkRepository.removeRegister();
     }
 
     @Override
@@ -91,27 +96,24 @@
 
     @Override
     public CharSequence getSummary() {
-        final List<SubscriptionInfo> subs = SubscriptionUtil.getActiveSubscriptions(
-                mSubscriptionManager);
-
-        if (subs.isEmpty()) {
+        List<SubscriptionInfoEntity> list = getSubscriptionInfoList();
+        if (list == null || list .isEmpty()) {
             return setSummaryResId(R.string.calls_sms_no_sim);
         } else {
             final StringBuilder summary = new StringBuilder();
-            for (SubscriptionInfo subInfo : subs) {
-                int subsSize = subs.size();
-                int subId = subInfo.getSubscriptionId();
-                final CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName(
-                        subInfo, mContext);
+            for (SubscriptionInfoEntity subInfo : list) {
+                int subsSize = list.size();
+                int subId = Integer.parseInt(subInfo.subId);
+                final CharSequence displayName = subInfo.uniqueName;
 
                 // Set displayName as summary if there is only one valid SIM.
                 if (subsSize == 1
-                        && SubscriptionManager.isValidSubscriptionId(subId)
+                        && list.get(0).isValidSubscription
                         && isInService(subId)) {
                     return displayName;
                 }
 
-                CharSequence status = getPreferredStatus(subsSize, subId);
+                CharSequence status = getPreferredStatus(subInfo, subsSize, subId);
                 if (status.toString().isEmpty()) {
                     // If there are 2 or more SIMs and one of these has no preferred status,
                     // set only its displayName as summary.
@@ -123,7 +125,7 @@
                             .append(")");
                 }
                 // Do not add ", " for the last subscription.
-                if (subInfo != subs.get(subs.size() - 1)) {
+                if (!subInfo.equals(list.get(list.size() - 1))) {
                     summary.append(", ");
                 }
 
@@ -136,12 +138,13 @@
     }
 
     @VisibleForTesting
-    protected CharSequence getPreferredStatus(int subsSize, int subId) {
+    protected CharSequence getPreferredStatus(SubscriptionInfoEntity subInfo, int subsSize,
+            int subId) {
         String status = "";
-        boolean isDataPreferred = subId == getDefaultVoiceSubscriptionId();
-        boolean isSmsPreferred = subId == getDefaultSmsSubscriptionId();
+        boolean isDataPreferred = subInfo.isDefaultVoiceSubscription;
+        boolean isSmsPreferred = subInfo.isDefaultSmsSubscription;
 
-        if (!SubscriptionManager.isValidSubscriptionId(subId) || !isInService(subId)) {
+        if (!subInfo.isValidSubscription || !isInService(subId)) {
             status = setSummaryResId(subsSize > 1 ? R.string.calls_sms_unavailable :
                     R.string.calls_sms_temp_unavailable);
         } else {
@@ -161,13 +164,8 @@
     }
 
     @VisibleForTesting
-    protected int getDefaultVoiceSubscriptionId() {
-        return SubscriptionManager.getDefaultVoiceSubscriptionId();
-    }
-
-    @VisibleForTesting
-    protected int getDefaultSmsSubscriptionId() {
-        return SubscriptionManager.getDefaultSmsSubscriptionId();
+    protected List<SubscriptionInfoEntity> getSubscriptionInfoList() {
+        return mSubInfoEntityList;
     }
 
     private void update() {
@@ -178,9 +176,7 @@
         mPreference.setOnPreferenceClickListener(null);
         mPreference.setFragment(null);
 
-        final List<SubscriptionInfo> subs = SubscriptionUtil.getActiveSubscriptions(
-                mSubscriptionManager);
-        if (subs.isEmpty()) {
+        if (mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) {
             mPreference.setEnabled(false);
         } else {
             mPreference.setEnabled(true);
@@ -213,16 +209,34 @@
         update();
     }
 
-    @Override
-    public void onSubscriptionsChanged() {
-        refreshSummary(mPreference);
-        update();
-    }
-
     @VisibleForTesting
     protected boolean isInService(int subId) {
         ServiceState serviceState =
                 mTelephonyManager.createForSubscriptionId(subId).getServiceState();
         return Utils.isInService(serviceState);
     }
+
+    @Override
+    public void onAvailableSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList) {
+    }
+
+    @Override
+    public void onActiveSubInfoChanged(List<SubscriptionInfoEntity> activeSubInfoList) {
+        if ((mSubInfoEntityList != null &&
+                (activeSubInfoList.isEmpty() || !activeSubInfoList.equals(mSubInfoEntityList)))
+                || (!activeSubInfoList.isEmpty() && mSubInfoEntityList == null)) {
+            Log.d(TAG, "subInfo list from framework is changed, update the subInfo entity list.");
+            mSubInfoEntityList = activeSubInfoList;
+            update();
+        }
+    }
+
+    @Override
+    public void onAllUiccInfoChanged(List<UiccInfoEntity> uiccInfoEntityList) {
+    }
+
+    @Override
+    public void onAllMobileNetworkInfoChanged(
+            List<MobileNetworkInfoEntity> mobileNetworkInfoEntityList) {
+    }
 }
diff --git a/src/com/android/settings/network/UiccSlotUtil.java b/src/com/android/settings/network/UiccSlotUtil.java
index 7b8d330..4c26995 100644
--- a/src/com/android/settings/network/UiccSlotUtil.java
+++ b/src/com/android/settings/network/UiccSlotUtil.java
@@ -28,7 +28,6 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.uicc.UiccController;
 import com.android.settingslib.utils.ThreadUtils;
 
 import com.google.common.collect.ImmutableList;
@@ -273,6 +272,7 @@
         if (slotId == INVALID_PHYSICAL_SLOT_ID) {
             for (int i = 0; i < slots.length; i++) {
                 if (slots[i].isRemovable()
+                        && !slots[i].getIsEuicc()
                         && !slots[i].getPorts().stream().findFirst().get().isActive()
                         && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_ERROR
                         && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) {
@@ -413,4 +413,29 @@
                 .findFirst()
                 .orElse(INVALID_LOGICAL_SLOT_ID);
     }
+
+    /**
+     * Return whether the removable psim is enabled.
+     *
+     * @param telMgr is a TelephonyManager.
+     * @return whether the removable psim is enabled.
+     */
+    public static boolean isRemovableSimEnabled(TelephonyManager telMgr) {
+        if (telMgr == null) {
+            return false;
+        }
+        ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(telMgr);
+        boolean isRemovableSimEnabled =
+                slotInfos.stream()
+                        .anyMatch(
+                                slot -> slot != null
+                                        && slot.isRemovable()
+                                        && !slot.getIsEuicc()
+                                        && slot.getPorts().stream().anyMatch(
+                                                port -> port.isActive())
+                                        && slot.getCardStateInfo()
+                                                == UiccSlotInfo.CARD_STATE_INFO_PRESENT);
+        Log.i(TAG, "isRemovableSimEnabled: " + isRemovableSimEnabled);
+        return isRemovableSimEnabled;
+    }
 }
diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
index a878cb3..6fa803d 100644
--- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
+++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
@@ -24,7 +24,6 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.UiccCardInfo;
-import android.telephony.UiccSlotInfo;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -39,8 +38,6 @@
 import com.android.settings.network.UiccSlotUtil;
 import com.android.settings.sim.SimActivationNotifier;
 
-import com.google.common.collect.ImmutableList;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -586,18 +583,7 @@
     }
 
     private boolean isRemovableSimEnabled() {
-        ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(mTelMgr);
-        boolean isRemovableSimEnabled =
-                slotInfos.stream()
-                        .anyMatch(
-                                slot -> slot != null
-                                        && slot.isRemovable()
-                                        && slot.getPorts().stream().anyMatch(
-                                                port -> port.isActive())
-                                        && slot.getCardStateInfo()
-                                                == UiccSlotInfo.CARD_STATE_INFO_PRESENT);
-        Log.i(TAG, "isRemovableSimEnabled: " + isRemovableSimEnabled);
-        return isRemovableSimEnabled;
+        return UiccSlotUtil.isRemovableSimEnabled(mTelMgr);
     }
 
     private boolean isMultipleEnabledProfilesSupported() {
diff --git a/src/com/android/settings/privacy/PrivacyDashboardFragment.java b/src/com/android/settings/privacy/PrivacyDashboardFragment.java
index 75ed225..46a05b0 100644
--- a/src/com/android/settings/privacy/PrivacyDashboardFragment.java
+++ b/src/com/android/settings/privacy/PrivacyDashboardFragment.java
@@ -25,9 +25,12 @@
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.SearchIndexableResource;
 
 import com.android.settings.R;
+import com.android.settings.Utils;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.notification.LockScreenNotificationPreferenceController;
 import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
@@ -131,5 +134,21 @@
                         Context context) {
                     return buildPreferenceControllers(context, null);
                 }
+
+                @Override
+                public List<String> getNonIndexableKeys(Context context) {
+                    final List<String> keys = super.getNonIndexableKeys(context);
+                    final int profileUserId =
+                            Utils.getManagedProfileId(
+                                    UserManager.get(context), UserHandle.myUserId());
+                    // If work profile is supported, we should keep the search result.
+                    if (profileUserId != UserHandle.USER_NULL) {
+                        return keys;
+                    }
+
+                    // Otherwise, we should hide the search result.
+                    keys.add(KEY_NOTIFICATION_WORK_PROFILE_NOTIFICATIONS);
+                    return keys;
+                }
             };
 }
diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java
index 569a0ea..1cc3276 100644
--- a/src/com/android/settings/slices/CustomSliceRegistry.java
+++ b/src/com/android/settings/slices/CustomSliceRegistry.java
@@ -315,37 +315,28 @@
             .appendPath("always_on_display")
             .build();
 
-    /**
-     * Backing Uri for the Turn on Wi-Fi Slice.
-     */
-    public static final Uri TURN_ON_WIFI_SLICE_URI = new Uri.Builder()
-            .scheme(ContentResolver.SCHEME_CONTENT)
-            .authority(SettingsSliceProvider.SLICE_AUTHORITY)
-            .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
-            .appendPath("turn_on_wifi")
-            .build();
-
     @VisibleForTesting
     static final Map<Uri, Class<? extends CustomSliceable>> sUriToSlice;
 
     static {
         sUriToSlice = new ArrayMap<>();
-
-        sUriToSlice.put(BATTERY_FIX_SLICE_URI, BatteryFixSlice.class);
-        sUriToSlice.put(BLUETOOTH_DEVICES_SLICE_URI, BluetoothDevicesSlice.class);
-        sUriToSlice.put(CONTEXTUAL_ADAPTIVE_SLEEP_URI, ContextualAdaptiveSleepSlice.class);
-        sUriToSlice.put(CONTEXTUAL_WIFI_SLICE_URI, ContextualWifiSlice.class);
-        sUriToSlice.put(FACE_ENROLL_SLICE_URI, FaceSetupSlice.class);
         sUriToSlice.put(FLASHLIGHT_SLICE_URI, FlashlightSlice.class);
         sUriToSlice.put(LOCATION_SLICE_URI, LocationSlice.class);
-        sUriToSlice.put(LOW_STORAGE_SLICE_URI, LowStorageSlice.class);
-        sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class);
         sUriToSlice.put(MOBILE_DATA_SLICE_URI, MobileDataSlice.class);
         sUriToSlice.put(PROVIDER_MODEL_SLICE_URI, ProviderModelSlice.class);
         sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class);
         sUriToSlice.put(DARK_THEME_SLICE_URI, DarkThemeSlice.class);
-        sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class);
         sUriToSlice.put(ALWAYS_ON_SLICE_URI, AlwaysOnDisplaySlice.class);
+        sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class);
+        sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class);
+
+        // Slices for contextual card.
+        sUriToSlice.put(FACE_ENROLL_SLICE_URI, FaceSetupSlice.class);
+        sUriToSlice.put(BATTERY_FIX_SLICE_URI, BatteryFixSlice.class);
+        sUriToSlice.put(CONTEXTUAL_ADAPTIVE_SLEEP_URI, ContextualAdaptiveSleepSlice.class);
+        sUriToSlice.put(CONTEXTUAL_WIFI_SLICE_URI, ContextualWifiSlice.class);
+        sUriToSlice.put(LOW_STORAGE_SLICE_URI, LowStorageSlice.class);
+        sUriToSlice.put(BLUETOOTH_DEVICES_SLICE_URI, BluetoothDevicesSlice.class);
     }
 
     public static Class<? extends CustomSliceable> getSliceClassByUri(Uri uri) {
diff --git a/src/com/android/settings/spa/app/appinfo/AppButtonRepository.kt b/src/com/android/settings/spa/app/appinfo/AppButtonRepository.kt
index 3890e32..2383ddb 100644
--- a/src/com/android/settings/spa/app/appinfo/AppButtonRepository.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppButtonRepository.kt
@@ -60,13 +60,12 @@
         val homePackages = mutableSetOf<String>()
         val homeActivities = ArrayList<ResolveInfo>()
         val currentDefaultHome = packageManager.getHomeActivities(homeActivities)
-        homeActivities.map { it.activityInfo }.forEach {
-            homePackages.add(it.packageName)
+        homeActivities.mapNotNull { it.activityInfo }.forEach { activityInfo ->
+            homePackages.add(activityInfo.packageName)
             // Also make sure to include anything proxying for the home app
-            val metaPackageName = it.metaData?.getString(ActivityManager.META_HOME_ALTERNATE)
-            if (metaPackageName != null && signaturesMatch(metaPackageName, it.packageName)) {
-                homePackages.add(metaPackageName)
-            }
+            activityInfo.metaData?.getString(ActivityManager.META_HOME_ALTERNATE)
+                ?.takeIf { signaturesMatch(it, activityInfo.packageName) }
+                ?.let { homePackages.add(it) }
         }
         return HomePackages(homePackages, currentDefaultHome)
     }
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index 8235101..82fdc84 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -92,7 +92,12 @@
 
         AppPermissionPreference(app)
         AppStoragePreference(app)
+        // TODO: instant_app_launch_supported_domain_urls
+        // TODO: data_settings
         AppTimeSpentPreference(app)
+        // TODO: battery
+        // TODO: app_language_setting
+        AppOpenByDefaultPreference(app)
 
         Category(title = stringResource(R.string.advanced_apps)) {
             DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
diff --git a/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt b/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt
index a3ddfab..4ff2461 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt
@@ -33,7 +33,7 @@
         val app = packageInfo.applicationInfo
         if (!app.isInstantApp) return null
 
-        return AppStoreUtil.getAppStoreLink(packageInfoPresenter.contextAsUser, app.packageName)
+        return AppStoreUtil.getAppStoreLink(packageInfoPresenter.userContext, app.packageName)
             ?.let { intent -> installButton(intent, app) }
     }
 
diff --git a/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt b/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
new file mode 100644
index 0000000..936dee6
--- /dev/null
+++ b/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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.spa.app.appinfo
+
+import android.app.settings.SettingsEnums
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.liveData
+import com.android.settings.R
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.applications.intentpicker.AppLaunchSettings
+import com.android.settings.applications.intentpicker.IntentPickerUtils
+import com.android.settingslib.applications.AppUtils
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.framework.common.asUser
+import com.android.settingslib.spaprivileged.framework.common.domainVerificationManager
+import com.android.settingslib.spaprivileged.model.app.hasFlag
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import com.android.settingslib.spaprivileged.model.app.userId
+import kotlinx.coroutines.Dispatchers
+
+@Composable
+fun AppOpenByDefaultPreference(app: ApplicationInfo) {
+    val context = LocalContext.current
+    val presenter = remember { AppOpenByDefaultPresenter(context, app) }
+    if (!presenter.isAvailable()) return
+
+    Preference(object : PreferenceModel {
+        override val title = stringResource(R.string.launch_by_default)
+        override val summary = presenter.summaryLiveData.observeAsState(
+            initial = stringResource(R.string.summary_placeholder),
+        )
+        override val enabled = stateOf(presenter.isEnabled())
+        override val onClick = presenter::startActivity
+    })
+}
+
+private class AppOpenByDefaultPresenter(
+    private val context: Context,
+    private val app: ApplicationInfo,
+) {
+    private val domainVerificationManager = context.asUser(app.userHandle).domainVerificationManager
+
+    fun isAvailable() =
+        !app.isInstantApp && !AppUtils.isBrowserApp(context, app.packageName, app.userId)
+
+    fun isEnabled() = app.hasFlag(ApplicationInfo.FLAG_INSTALLED) && app.enabled
+
+    val summaryLiveData = liveData(Dispatchers.IO) {
+        emit(context.getString(when {
+            isLinkHandlingAllowed() -> R.string.app_link_open_always
+            else -> R.string.app_link_open_never
+        }))
+    }
+
+    fun isLinkHandlingAllowed(): Boolean {
+        val userState = IntentPickerUtils.getDomainVerificationUserState(
+            domainVerificationManager, app.packageName
+        )
+        return userState?.isLinkHandlingAllowed ?: false
+    }
+
+    fun startActivity() {
+        AppInfoDashboardFragment.startAppInfoFragment(
+            AppLaunchSettings::class.java,
+            app,
+            context,
+            SettingsEnums.APPLICATIONS_INSTALLED_APP_DETAILS,
+        )
+    }
+}
diff --git a/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt b/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
index 9c5f673..f73c35a 100644
--- a/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
@@ -24,6 +24,7 @@
 import com.android.settings.R
 import com.android.settingslib.applications.PermissionsSummaryHelper
 import com.android.settingslib.applications.PermissionsSummaryHelper.PermissionsResultCallback
+import com.android.settingslib.spaprivileged.framework.common.asUser
 import com.android.settingslib.spaprivileged.model.app.userHandle
 
 data class AppPermissionSummaryState(
@@ -35,8 +36,8 @@
     private val context: Context,
     private val app: ApplicationInfo,
 ) : LiveData<AppPermissionSummaryState>() {
-    private val contextAsUser = context.createContextAsUser(app.userHandle, 0)
-    private val packageManager = contextAsUser.packageManager
+    private val userContext = context.asUser(app.userHandle)
+    private val packageManager = userContext.packageManager
 
     private val onPermissionsChangedListener = OnPermissionsChangedListener { uid ->
         if (uid == app.uid) update()
@@ -53,7 +54,7 @@
 
     private fun update() {
         PermissionsSummaryHelper.getPermissionSummary(
-            contextAsUser, app.packageName, permissionsCallback
+            userContext, app.packageName, permissionsCallback
         )
     }
 
diff --git a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
index 2f5dda1..050c048 100644
--- a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
+++ b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
@@ -29,6 +29,7 @@
 import androidx.compose.runtime.Composable
 import com.android.settings.overlay.FeatureFactory
 import com.android.settingslib.spa.framework.compose.LocalNavController
+import com.android.settingslib.spaprivileged.framework.common.asUser
 import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
 import kotlinx.coroutines.CoroutineScope
@@ -49,8 +50,8 @@
     private val coroutineScope: CoroutineScope,
 ) {
     private val metricsFeatureProvider = FeatureFactory.getFactory(context).metricsFeatureProvider
-    val contextAsUser by lazy { context.createContextAsUser(UserHandle.of(userId), 0) }
-    val packageManagerAsUser: PackageManager by lazy { contextAsUser.packageManager }
+    val userContext by lazy { context.asUser(UserHandle.of(userId)) }
+    val packageManagerAsUser: PackageManager by lazy { userContext.packageManager }
     private val _flow: MutableStateFlow<PackageInfo?> = MutableStateFlow(null)
 
     val flow: StateFlow<PackageInfo?> = _flow
diff --git a/src/com/android/settings/vpn2/ConfigDialog.java b/src/com/android/settings/vpn2/ConfigDialog.java
index bf0dfc9..be2a68e 100644
--- a/src/com/android/settings/vpn2/ConfigDialog.java
+++ b/src/com/android/settings/vpn2/ConfigDialog.java
@@ -626,7 +626,14 @@
             String proxyPort = mProxyPort.getText().toString().trim();
             // 0 is a last resort default, but the interface validates that the proxy port is
             // present and non-zero.
-            int port = proxyPort.isEmpty() ? 0 : Integer.parseInt(proxyPort);
+            int port = 0;
+            if (!proxyPort.isEmpty()) {
+                try {
+                    port = Integer.parseInt(proxyPort);
+                } catch (NumberFormatException e) {
+                    Log.e(TAG, "Could not parse proxy port integer ", e);
+                }
+            }
             profile.proxy = ProxyInfo.buildDirectProxy(proxyHost, port);
         } else {
             profile.proxy = null;
diff --git a/src/com/android/settings/wifi/NetworkRequestDialogActivity.java b/src/com/android/settings/wifi/NetworkRequestDialogActivity.java
index 30f38d2..7b7d33f 100644
--- a/src/com/android/settings/wifi/NetworkRequestDialogActivity.java
+++ b/src/com/android/settings/wifi/NetworkRequestDialogActivity.java
@@ -27,12 +27,14 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.os.Message;
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
 
 import com.android.settings.R;
 import com.android.settings.wifi.NetworkRequestErrorDialogFragment.ERROR_DIALOG_TYPE;
@@ -143,7 +145,7 @@
         super.onPause();
     }
 
-    private final Handler mHandler = new Handler() {
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -162,13 +164,17 @@
         dismissDialogs();
 
         // Throws error dialog.
+        final FragmentManager fragmentManager = getSupportFragmentManager();
+        if (fragmentManager.isDestroyed() || fragmentManager.isStateSaved()) {
+            return;
+        }
         final NetworkRequestErrorDialogFragment dialogFragment =
                 NetworkRequestErrorDialogFragment.newInstance();
         dialogFragment.setRejectCallback(mUserSelectionCallback);
         final Bundle bundle = new Bundle();
         bundle.putSerializable(NetworkRequestErrorDialogFragment.DIALOG_TYPE, type);
         dialogFragment.setArguments(bundle);
-        dialogFragment.show(getSupportFragmentManager(), TAG);
+        dialogFragment.show(fragmentManager, TAG);
         mShowingErrorDialog = true;
     }
 
diff --git a/src/com/android/settings/wifi/WifiUtils.java b/src/com/android/settings/wifi/WifiUtils.java
index 4b94c81..a9010ac 100644
--- a/src/com/android/settings/wifi/WifiUtils.java
+++ b/src/com/android/settings/wifi/WifiUtils.java
@@ -22,14 +22,20 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.net.NetworkCapabilities;
+import android.net.TetheringManager;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.wifitrackerlib.WifiEntry;
 
@@ -38,12 +44,16 @@
 /** A utility class for Wi-Fi functions. */
 public class WifiUtils extends com.android.settingslib.wifi.WifiUtils {
 
+    static final String TAG = "WifiUtils";
+
     private static final int SSID_ASCII_MIN_LENGTH = 1;
     private static final int SSID_ASCII_MAX_LENGTH = 32;
 
     private static final int PSK_PASSPHRASE_ASCII_MIN_LENGTH = 8;
     private static final int PSK_PASSPHRASE_ASCII_MAX_LENGTH = 63;
 
+    private static Boolean sCanShowWifiHotspotCached;
+
     public static boolean isSSIDTooLong(String ssid) {
         if (TextUtils.isEmpty(ssid)) {
             return false;
@@ -240,4 +250,62 @@
 
         return WifiEntry.SECURITY_NONE;
     }
+
+    /**
+     * Check if Wi-Fi hotspot settings can be displayed.
+     *
+     * @param context Context of caller
+     * @return true if Wi-Fi hotspot settings can be displayed
+     */
+    public static boolean checkShowWifiHotspot(Context context) {
+        if (context == null) return false;
+
+        boolean showWifiHotspotSettings =
+                context.getResources().getBoolean(R.bool.config_show_wifi_hotspot_settings);
+        if (!showWifiHotspotSettings) {
+            Log.w(TAG, "config_show_wifi_hotspot_settings:false");
+            return false;
+        }
+
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        if (wifiManager == null) {
+            Log.e(TAG, "WifiManager is null");
+            return false;
+        }
+
+        TetheringManager tetheringManager = context.getSystemService(TetheringManager.class);
+        if (tetheringManager == null) {
+            Log.e(TAG, "TetheringManager is null");
+            return false;
+        }
+        String[] wifiRegexs = tetheringManager.getTetherableWifiRegexs();
+        if (wifiRegexs == null || wifiRegexs.length == 0) {
+            Log.w(TAG, "TetherableWifiRegexs is empty");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Return the cached result to see if Wi-Fi hotspot settings can be displayed.
+     *
+     * @param context Context of caller
+     * @return true if Wi-Fi hotspot settings can be displayed
+     */
+    public static boolean canShowWifiHotspot(Context context) {
+        if (sCanShowWifiHotspotCached == null) {
+            sCanShowWifiHotspotCached = checkShowWifiHotspot(context);
+        }
+        return sCanShowWifiHotspotCached;
+    }
+
+    /**
+     * Sets the sCanShowWifiHotspotCached for testing purposes.
+     *
+     * @param cached Cached value for #canShowWifiHotspot()
+     */
+    @VisibleForTesting
+    public static void setCanShowWifiHotspotCached(Boolean cached) {
+        sCanShowWifiHotspotCached = cached;
+    }
 }
diff --git a/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java
index c0ac159..49b437e 100644
--- a/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java
+++ b/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java
@@ -16,9 +16,10 @@
 
 package com.android.settings.wifi.tether;
 
+import static com.android.settings.wifi.WifiUtils.canShowWifiHotspot;
+
 import android.annotation.NonNull;
 import android.content.Context;
-import android.net.TetheringManager;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
@@ -46,7 +47,6 @@
 
     private static final String WIFI_TETHER_SETTINGS = "wifi_tether";
 
-    private boolean mIsWifiTetherable;
     private WifiManager mWifiManager;
     private boolean mIsWifiTetheringAllow;
     private int mSoftApState;
@@ -59,7 +59,6 @@
         // TODO(b/246537032):Use fragment context to WifiManager service will caused memory leak
         this(context, lifecycle,
                 context.getApplicationContext().getSystemService(WifiManager.class),
-                context.getSystemService(TetheringManager.class),
                 true /* initSoftApManager */,
                 WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(context));
     }
@@ -69,15 +68,9 @@
             Context context,
             Lifecycle lifecycle,
             WifiManager wifiManager,
-            TetheringManager tetheringManager,
             boolean initSoftApManager,
             boolean isWifiTetheringAllow) {
         super(context);
-        final String[] wifiRegexs = tetheringManager.getTetherableWifiRegexs();
-        if (wifiRegexs != null && wifiRegexs.length != 0) {
-            mIsWifiTetherable = true;
-        }
-
         mIsWifiTetheringAllow = isWifiTetheringAllow;
         if (!isWifiTetheringAllow) return;
 
@@ -93,7 +86,7 @@
 
     @Override
     public boolean isAvailable() {
-        return mIsWifiTetherable && !Utils.isMonkeyRunning();
+        return canShowWifiHotspot(mContext) && !Utils.isMonkeyRunning();
     }
 
     @Override
diff --git a/src/com/android/settings/wifi/tether/WifiTetherSettings.java b/src/com/android/settings/wifi/tether/WifiTetherSettings.java
index 5b9ce42..bee265e 100644
--- a/src/com/android/settings/wifi/tether/WifiTetherSettings.java
+++ b/src/com/android/settings/wifi/tether/WifiTetherSettings.java
@@ -18,6 +18,8 @@
 
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_CHANGED_ACTION;
 
+import static com.android.settings.wifi.WifiUtils.canShowWifiHotspot;
+
 import android.app.settings.SettingsEnums;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -108,6 +110,13 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
+        if (!canShowWifiHotspot(getContext())) {
+            Log.e(TAG, "can not launch Wi-Fi hotspot settings"
+                    + " because the config is not set to show.");
+            finish();
+            return;
+        }
+
         setIfOnlyAvailableForAdmins(true);
         mUnavailable = isUiRestricted() || !mWifiRestriction.isHotspotAvailable(getContext());
     }
diff --git a/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java
index 3305cde..458c5c6 100644
--- a/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java
@@ -16,15 +16,24 @@
 
 package com.android.settings.dream;
 
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.PowerManager;
 
 import androidx.preference.Preference;
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.settings.R;
+import com.android.settings.display.AmbientDisplayAlwaysOnPreferenceController;
 import com.android.settingslib.dream.DreamBackend;
 import com.android.settingslib.dream.DreamBackend.WhenToDream;
 
@@ -38,32 +47,64 @@
 
 @RunWith(RobolectricTestRunner.class)
 public class WhenToDreamPreferenceControllerTest {
+    private static final String TEST_PACKAGE = "com.android.test";
 
     private WhenToDreamPreferenceController mController;
     private Context mContext;
     @Mock
     private DreamBackend mBackend;
+    @Mock
+    private PowerManager mPowerManager;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private ApplicationInfo mApplicationInfo;
 
     @Before
-    public void setup() {
+    public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mContext = ApplicationProvider.getApplicationContext();
-        mController = new WhenToDreamPreferenceController(mContext);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mController = new WhenToDreamPreferenceController(mContext, true);
         ReflectionHelpers.setField(mController, "mBackend", mBackend);
+        when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+        when(mPowerManager.isAmbientDisplaySuppressedForTokenByApp(anyString(), anyInt()))
+                .thenReturn(false);
+
+        mApplicationInfo.uid = 1;
+        when(mContext.getString(
+                com.android.internal.R.string.config_defaultWellbeingPackage)).thenReturn(
+                TEST_PACKAGE);
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.getApplicationInfo(TEST_PACKAGE, /* flag= */ 0)).thenReturn(
+                mApplicationInfo);
     }
 
     @Test
-    public void updateSummary() {
+    public void testUpdateSummary() {
         // Don't have to test the other settings because DreamSettings tests that all
         // @WhenToDream values map to the correct ResId
         final @WhenToDream int testSetting = DreamBackend.WHILE_CHARGING;
         final Preference mockPref = mock(Preference.class);
         when(mockPref.getContext()).thenReturn(mContext);
         when(mBackend.getWhenToDreamSetting()).thenReturn(testSetting);
-        final String expectedString =
-                mContext.getString(DreamSettings.getDreamSettingDescriptionResId(testSetting));
+        final int expectedResId = DreamSettings.getDreamSettingDescriptionResId(testSetting);
 
         mController.updateState(mockPref);
-        verify(mockPref).setSummary(expectedString);
+        verify(mockPref).setSummary(expectedResId);
+    }
+
+    @Test
+    public void testBedtimeModeSuppression() {
+        final Preference mockPref = mock(Preference.class);
+        when(mockPref.getContext()).thenReturn(mContext);
+        when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.WHILE_CHARGING);
+        when(mPowerManager.isAmbientDisplaySuppressedForTokenByApp(anyString(), anyInt()))
+                .thenReturn(true);
+
+        assertTrue(AmbientDisplayAlwaysOnPreferenceController.isAodSuppressedByBedtime(mContext));
+
+        mController.updateState(mockPref);
+        verify(mockPref).setSummary(R.string.screensaver_settings_when_to_dream_bedtime);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java
new file mode 100644
index 0000000..c7e8322
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryUsageBroadcastReceiverTest {
+
+    private Context mContext;
+    private BatteryUsageBroadcastReceiver mBatteryUsageBroadcastReceiver;
+    @Mock
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        mBatteryUsageBroadcastReceiver = new BatteryUsageBroadcastReceiver();
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+    }
+
+    @Test
+    public void onReceive_fetchUsageDataIntent_startService() {
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        mBatteryUsageBroadcastReceiver.onReceive(mContext,
+                new Intent(BatteryUsageBroadcastReceiver.ACTION_FETCH_BATTERY_USAGE_DATA));
+
+        assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isTrue();
+    }
+
+    @Test
+    public void onReceive_invalidIntent_notStartService() {
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        mBatteryUsageBroadcastReceiver.onReceive(mContext, new Intent("invalid intent"));
+
+        assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isFalse();
+    }
+
+    @Test
+    public void onReceive_clearCacheIntentInDebugMode_clearBatteryCacheData() {
+        BatteryUsageBroadcastReceiver.sIsDebugMode = true;
+        // Insert testing data first.
+        BatteryDiffEntry.sValidForRestriction.put(
+                /*packageName*/ "com.android.testing_package", Boolean.valueOf(true));
+        assertThat(BatteryDiffEntry.sValidForRestriction).isNotEmpty();
+
+        mBatteryUsageBroadcastReceiver.onReceive(mContext,
+                new Intent(BatteryUsageBroadcastReceiver.ACTION_CLEAR_BATTERY_CACHE_DATA));
+
+        assertThat(BatteryDiffEntry.sValidForRestriction).isEmpty();
+    }
+
+    @Test
+    public void onReceive_clearCacheIntentInNotDebugMode_notClearBatteryCacheData() {
+        BatteryUsageBroadcastReceiver.sIsDebugMode = false;
+        // Insert testing data first.
+        BatteryDiffEntry.sValidForRestriction.put(
+                /*packageName*/ "com.android.testing_package", Boolean.valueOf(true));
+        assertThat(BatteryDiffEntry.sValidForRestriction).isNotEmpty();
+
+        mBatteryUsageBroadcastReceiver.onReceive(mContext,
+                new Intent(BatteryUsageBroadcastReceiver.ACTION_CLEAR_BATTERY_CACHE_DATA));
+
+        assertThat(BatteryDiffEntry.sValidForRestriction).isNotEmpty();
+    }
+
+    private void setProviderSetting(int value) {
+        when(mPackageManager.getComponentEnabledSetting(
+                new ComponentName(
+                        DatabaseUtils.SETTINGS_PACKAGE_PATH,
+                        DatabaseUtils.BATTERY_PROVIDER_CLASS_PATH)))
+                .thenReturn(value);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
new file mode 100644
index 0000000..61d4efa
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2022 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
+import com.android.settings.testutils.BatteryTestUtils;
+import com.android.settings.testutils.FakeClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.time.Duration;
+import java.util.List;
+
+/** Tests for {@link BatteryUsageContentProvider}. */
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryUsageContentProviderTest {
+    private static final Uri VALID_BATTERY_STATE_CONTENT_URI = DatabaseUtils.BATTERY_CONTENT_URI;
+
+    private Context mContext;
+    private BatteryUsageContentProvider mProvider;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mProvider = new BatteryUsageContentProvider();
+        mProvider.attachInfo(mContext, /*info=*/ null);
+        BatteryTestUtils.setUpBatteryStateDatabase(mContext);
+    }
+
+    @Test
+    public void onCreate_withoutWorkProfileMode_returnsTrue() {
+        assertThat(mProvider.onCreate()).isTrue();
+    }
+
+    @Test
+    public void onCreate_withWorkProfileMode_returnsFalse() {
+        BatteryTestUtils.setWorkProfile(mContext);
+        assertThat(mProvider.onCreate()).isFalse();
+    }
+
+    @Test
+    public void queryAndInsert_incorrectContentUri_throwsIllegalArgumentException() {
+        final Uri.Builder builder =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(DatabaseUtils.AUTHORITY)
+                        .appendPath(DatabaseUtils.BATTERY_STATE_TABLE + "/0");
+        final Uri uri = builder.build();
+        mProvider.onCreate();
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        mProvider.query(
+                                uri, /*strings=*/ null, /*s=*/ null, /*strings1=*/ null,
+                                /*s1=*/ null));
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mProvider.insert(uri, /*contentValues=*/ null));
+    }
+
+    @Test
+    public void queryAndInsert_incorrectAuthority_throwsIllegalArgumentException() {
+        final Uri.Builder builder =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(DatabaseUtils.AUTHORITY + ".debug")
+                        .appendPath(DatabaseUtils.BATTERY_STATE_TABLE);
+        final Uri uri = builder.build();
+        mProvider.onCreate();
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        mProvider.query(
+                                uri, /*strings=*/ null, /*s=*/ null, /*strings1=*/ null,
+                                /*s1=*/ null));
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mProvider.insert(uri, /*contentValues=*/ null));
+    }
+
+    @Test
+    public void query_batteryState_returnsExpectedResult() throws Exception {
+        mProvider.onCreate();
+        final Duration currentTime = Duration.ofHours(52);
+        final long expiredTimeCutoff = currentTime.toMillis()
+                - BatteryUsageContentProvider.QUERY_DURATION_HOURS.toMillis();
+        testQueryBatteryState(currentTime, expiredTimeCutoff, /*hasQueryTimestamp=*/ false);
+    }
+
+    @Test
+    public void query_batteryStateTimestamp_returnsExpectedResult() throws Exception {
+        mProvider.onCreate();
+        final Duration currentTime = Duration.ofHours(52);
+        final long expiredTimeCutoff = currentTime.toMillis() - Duration.ofHours(10).toMillis();
+        testQueryBatteryState(currentTime, expiredTimeCutoff, /*hasQueryTimestamp=*/ true);
+    }
+
+    @Test
+    public void query_incorrectParameterFormat_returnsExpectedResult() throws Exception {
+        mProvider.onCreate();
+        final Duration currentTime = Duration.ofHours(52);
+        final long expiredTimeCutoff =
+                currentTime.toMillis()
+                        - BatteryUsageContentProvider.QUERY_DURATION_HOURS.toMillis();
+        testQueryBatteryState(
+                currentTime,
+                expiredTimeCutoff,
+                /*hasQueryTimestamp=*/ false,
+                /*customParameter=*/ "invalid number format");
+    }
+
+    @Test
+    public void insert_batteryState_returnsExpectedResult() {
+        mProvider.onCreate();
+        ContentValues values = new ContentValues();
+        values.put("uid", Long.valueOf(101L));
+        values.put("userId", Long.valueOf(1001L));
+        values.put("appLabel", new String("Settings"));
+        values.put("packageName", new String("com.android.settings"));
+        values.put("timestamp", Long.valueOf(2100021L));
+        values.put("isHidden", Boolean.valueOf(true));
+        values.put("totalPower", Double.valueOf(99.0));
+        values.put("consumePower", Double.valueOf(9.0));
+        values.put("percentOfTotal", Double.valueOf(0.9));
+        values.put("foregroundUsageTimeInMs", Long.valueOf(1000));
+        values.put("backgroundUsageTimeInMs", Long.valueOf(2000));
+        values.put("drainType", Integer.valueOf(1));
+        values.put("consumerType", Integer.valueOf(2));
+        values.put("batteryLevel", Integer.valueOf(51));
+        values.put("batteryStatus", Integer.valueOf(2));
+        values.put("batteryHealth", Integer.valueOf(3));
+
+        final Uri uri = mProvider.insert(VALID_BATTERY_STATE_CONTENT_URI, values);
+
+        assertThat(uri).isEqualTo(VALID_BATTERY_STATE_CONTENT_URI);
+        // Verifies the BatteryState content.
+        final List<BatteryState> states =
+                BatteryStateDatabase.getInstance(mContext).batteryStateDao().getAllAfter(0);
+        assertThat(states).hasSize(1);
+        assertThat(states.get(0).uid).isEqualTo(101L);
+        assertThat(states.get(0).userId).isEqualTo(1001L);
+        assertThat(states.get(0).appLabel).isEqualTo("Settings");
+        assertThat(states.get(0).packageName).isEqualTo("com.android.settings");
+        assertThat(states.get(0).isHidden).isTrue();
+        assertThat(states.get(0).timestamp).isEqualTo(2100021L);
+        assertThat(states.get(0).totalPower).isEqualTo(99.0);
+        assertThat(states.get(0).consumePower).isEqualTo(9.0);
+        assertThat(states.get(0).percentOfTotal).isEqualTo(0.9);
+        assertThat(states.get(0).foregroundUsageTimeInMs).isEqualTo(1000);
+        assertThat(states.get(0).backgroundUsageTimeInMs).isEqualTo(2000);
+        assertThat(states.get(0).drainType).isEqualTo(1);
+        assertThat(states.get(0).consumerType).isEqualTo(2);
+        assertThat(states.get(0).batteryLevel).isEqualTo(51);
+        assertThat(states.get(0).batteryStatus).isEqualTo(2);
+        assertThat(states.get(0).batteryHealth).isEqualTo(3);
+    }
+
+    @Test
+    public void insert_partialFieldsContentValues_returnsExpectedResult() {
+        mProvider.onCreate();
+        final ContentValues values = new ContentValues();
+        values.put("packageName", new String("fake_data"));
+        values.put("timestamp", Long.valueOf(2100022L));
+        values.put("batteryLevel", Integer.valueOf(52));
+        values.put("batteryStatus", Integer.valueOf(3));
+        values.put("batteryHealth", Integer.valueOf(2));
+
+        final Uri uri = mProvider.insert(VALID_BATTERY_STATE_CONTENT_URI, values);
+
+        assertThat(uri).isEqualTo(VALID_BATTERY_STATE_CONTENT_URI);
+        // Verifies the BatteryState content.
+        final List<BatteryState> states =
+                BatteryStateDatabase.getInstance(mContext).batteryStateDao().getAllAfter(0);
+        assertThat(states).hasSize(1);
+        assertThat(states.get(0).packageName).isEqualTo("fake_data");
+        assertThat(states.get(0).timestamp).isEqualTo(2100022L);
+        assertThat(states.get(0).batteryLevel).isEqualTo(52);
+        assertThat(states.get(0).batteryStatus).isEqualTo(3);
+        assertThat(states.get(0).batteryHealth).isEqualTo(2);
+    }
+
+    @Test
+    public void delete_throwsUnsupportedOperationException() {
+        assertThrows(
+                UnsupportedOperationException.class,
+                () -> mProvider.delete(/*uri=*/ null, /*s=*/ null, /*strings=*/ null));
+    }
+
+    @Test
+    public void update_throwsUnsupportedOperationException() {
+        assertThrows(
+                UnsupportedOperationException.class,
+                () ->
+                        mProvider.update(
+                                /*uri=*/ null, /*contentValues=*/ null, /*s=*/ null,
+                                /*strings=*/ null));
+    }
+
+    private void testQueryBatteryState(
+            Duration currentTime, long expiredTimeCutoff, boolean hasQueryTimestamp)
+            throws Exception {
+        testQueryBatteryState(currentTime, expiredTimeCutoff, hasQueryTimestamp, null);
+    }
+
+    private void testQueryBatteryState(
+            Duration currentTime,
+            long expiredTimeCutoff,
+            boolean hasQueryTimestamp,
+            String customParameter)
+            throws Exception {
+        mProvider.onCreate();
+        final FakeClock fakeClock = new FakeClock();
+        fakeClock.setCurrentTime(currentTime);
+        mProvider.setClock(fakeClock);
+        // Inserts some expired testing data.
+        BatteryTestUtils.insertDataToBatteryStateDatabase(
+                mContext, expiredTimeCutoff - 1, "com.android.sysui1");
+        BatteryTestUtils.insertDataToBatteryStateDatabase(
+                mContext, expiredTimeCutoff - 2, "com.android.sysui2");
+        BatteryTestUtils.insertDataToBatteryStateDatabase(
+                mContext, expiredTimeCutoff - 3, "com.android.sysui3");
+        // Inserts some valid testing data.
+        final String packageName1 = "com.android.settings1";
+        final String packageName2 = "com.android.settings2";
+        final String packageName3 = "com.android.settings3";
+        BatteryTestUtils.insertDataToBatteryStateDatabase(
+                mContext, currentTime.toMillis(), packageName1);
+        BatteryTestUtils.insertDataToBatteryStateDatabase(
+                mContext, expiredTimeCutoff + 2, packageName2);
+        BatteryTestUtils.insertDataToBatteryStateDatabase(
+                mContext, expiredTimeCutoff, packageName3);
+
+        final Uri.Builder builder =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(DatabaseUtils.AUTHORITY)
+                        .appendPath(DatabaseUtils.BATTERY_STATE_TABLE);
+        if (customParameter != null) {
+            builder.appendQueryParameter(
+                    BatteryUsageContentProvider.QUERY_KEY_TIMESTAMP, customParameter);
+        } else if (hasQueryTimestamp) {
+            builder.appendQueryParameter(
+                    BatteryUsageContentProvider.QUERY_KEY_TIMESTAMP,
+                    Long.toString(expiredTimeCutoff));
+        }
+        final Uri batteryStateQueryContentUri = builder.build();
+
+        final Cursor cursor =
+                mProvider.query(
+                        batteryStateQueryContentUri,
+                        /*strings=*/ null,
+                        /*s=*/ null,
+                        /*strings1=*/ null,
+                        /*s1=*/ null);
+
+        // Verifies the result not include expired data.
+        assertThat(cursor.getCount()).isEqualTo(3);
+        final int packageNameIndex = cursor.getColumnIndex("packageName");
+        // Verifies the first data package name.
+        cursor.moveToFirst();
+        final String actualPackageName1 = cursor.getString(packageNameIndex);
+        assertThat(actualPackageName1).isEqualTo(packageName1);
+        // Verifies the second data package name.
+        cursor.moveToNext();
+        final String actualPackageName2 = cursor.getString(packageNameIndex);
+        assertThat(actualPackageName2).isEqualTo(packageName2);
+        // Verifies the third data package name.
+        cursor.moveToNext();
+        final String actualPackageName3 = cursor.getString(packageNameIndex);
+        assertThat(actualPackageName3).isEqualTo(packageName3);
+        cursor.close();
+        // TODO: add verification for recheck broadcast.
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java
new file mode 100644
index 0000000..4124e34
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2022 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryUsageDataLoaderTest {
+
+    private Context mContext;
+    @Mock
+    private ContentResolver mMockContentResolver;
+    @Mock
+    private BatteryStatsManager mBatteryStatsManager;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private BatteryUsageStats mBatteryUsageStats;
+    @Mock
+    private BatteryAppListPreferenceController mMockBatteryAppListController;
+    @Mock
+    private BatteryEntry mMockBatteryEntry;
+    @Captor
+    private ArgumentCaptor<BatteryUsageStatsQuery> mStatsQueryCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        BatteryUsageDataLoader.sController = mMockBatteryAppListController;
+        doReturn(mContext).when(mContext).getApplicationContext();
+        doReturn(mBatteryStatsManager).when(mContext).getSystemService(
+                Context.BATTERY_STATS_SERVICE);
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(mMockContentResolver).when(mContext).getContentResolver();
+        doReturn(new Intent()).when(mContext).registerReceiver(any(), any());
+    }
+
+    @Test
+    public void loadUsageData_loadUsageDataWithHistory() {
+        final List<BatteryEntry> batteryEntryList = new ArrayList<>();
+        batteryEntryList.add(mMockBatteryEntry);
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        when(mBatteryStatsManager.getBatteryUsageStats(mStatsQueryCaptor.capture()))
+                .thenReturn(mBatteryUsageStats);
+        when(mMockBatteryAppListController.getBatteryEntryList(mBatteryUsageStats, true))
+                .thenReturn(batteryEntryList);
+
+        BatteryUsageDataLoader.loadUsageData(mContext);
+
+        final int queryFlags = mStatsQueryCaptor.getValue().getFlags();
+        assertThat(queryFlags
+                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY)
+                .isNotEqualTo(0);
+        verify(mMockBatteryAppListController)
+                .getBatteryEntryList(mBatteryUsageStats, /*showAllApps=*/ true);
+        verify(mMockContentResolver).insert(any(), any());
+    }
+
+    @Test
+    public void loadUsageData_nullBatteryUsageStats_notLoadBatteryEntryData() {
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        when(mBatteryStatsManager.getBatteryUsageStats(mStatsQueryCaptor.capture()))
+                .thenReturn(null);
+
+        BatteryUsageDataLoader.loadUsageData(mContext);
+
+        final int queryFlags = mStatsQueryCaptor.getValue().getFlags();
+        assertThat(queryFlags
+                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY)
+                .isNotEqualTo(0);
+        verify(mMockBatteryAppListController, never())
+                .getBatteryEntryList(mBatteryUsageStats, /*showAllApps=*/ true);
+        verify(mMockContentResolver).insert(any(), any());
+    }
+
+    @Test
+    public void loadUsageData_nullBatteryEntryList_insertFakeDataIntoProvider() {
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        when(mBatteryStatsManager.getBatteryUsageStats(mStatsQueryCaptor.capture()))
+                .thenReturn(mBatteryUsageStats);
+        when(mMockBatteryAppListController.getBatteryEntryList(mBatteryUsageStats, true))
+                .thenReturn(null);
+
+        BatteryUsageDataLoader.loadUsageData(mContext);
+
+        verify(mMockContentResolver).insert(any(), any());
+    }
+
+    @Test
+    public void loadUsageData_emptyBatteryEntryList_insertFakeDataIntoProvider() {
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        when(mBatteryStatsManager.getBatteryUsageStats(mStatsQueryCaptor.capture()))
+                .thenReturn(mBatteryUsageStats);
+        when(mMockBatteryAppListController.getBatteryEntryList(mBatteryUsageStats, true))
+                .thenReturn(new ArrayList<BatteryEntry>());
+
+        BatteryUsageDataLoader.loadUsageData(mContext);
+
+        verify(mMockContentResolver).insert(any(), any());
+    }
+
+    @Test
+    public void loadUsageData_providerIsDisabled_notLoadHistory() {
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+        when(mBatteryStatsManager.getBatteryUsageStats(mStatsQueryCaptor.capture()))
+                .thenReturn(mBatteryUsageStats);
+
+        BatteryUsageDataLoader.loadUsageData(mContext);
+
+        verify(mBatteryStatsManager, never()).getBatteryUsageStats(
+                mStatsQueryCaptor.capture());
+    }
+
+    private void setProviderSetting(int value) {
+        when(mPackageManager.getComponentEnabledSetting(
+                new ComponentName(
+                        DatabaseUtils.SETTINGS_PACKAGE_PATH,
+                        DatabaseUtils.BATTERY_PROVIDER_CLASS_PATH)))
+                .thenReturn(value);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
new file mode 100644
index 0000000..cb5255e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2022 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.MatrixCursor;
+import android.os.BatteryManager;
+import android.os.BatteryUsageStats;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.settings.testutils.BatteryTestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DatabaseUtilsTest {
+
+    private Context mContext;
+
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock private ContentResolver mMockContentResolver;
+    @Mock private ContentResolver mMockContentResolver2;
+    @Mock private BatteryUsageStats mBatteryUsageStats;
+    @Mock private BatteryEntry mMockBatteryEntry1;
+    @Mock private BatteryEntry mMockBatteryEntry2;
+    @Mock private BatteryEntry mMockBatteryEntry3;
+    @Mock private Context mMockContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        doReturn(mMockContentResolver2).when(mMockContext).getContentResolver();
+        doReturn(mMockContentResolver).when(mContext).getContentResolver();
+        doReturn(mPackageManager).when(mMockContext).getPackageManager();
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+    }
+
+    @Test
+    public void isWorkProfile_defaultValue_returnFalse() {
+        assertThat(DatabaseUtils.isWorkProfile(mContext)).isFalse();
+    }
+
+    @Test
+    public void isWorkProfile_withManagedUser_returnTrue() {
+        BatteryTestUtils.setWorkProfile(mContext);
+        assertThat(DatabaseUtils.isWorkProfile(mContext)).isTrue();
+    }
+
+    @Test
+    public void isWorkProfile_withSystemUser_returnFalse() {
+        BatteryTestUtils.setWorkProfile(mContext);
+        Shadows.shadowOf(mContext.getSystemService(UserManager.class)).setIsSystemUser(true);
+
+        assertThat(DatabaseUtils.isWorkProfile(mContext)).isFalse();
+    }
+
+    @Test
+    public void isChartGraphEnabled_providerIsEnabled_returnTrue() {
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        assertThat(DatabaseUtils.isChartGraphEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    public void isChartGraphEnabled_providerIsDisabled_returnFalse() {
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+        assertThat(DatabaseUtils.isChartGraphEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    public void isContentProviderEnabled_providerEnabled_returnsTrue() {
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        assertThat(DatabaseUtils.isContentProviderEnabled(mContext)).isTrue();
+    }
+
+    @Test
+    public void isContentProviderEnabled_providerDisabled_returnsFalse() {
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+        assertThat(DatabaseUtils.isContentProviderEnabled(mContext)).isFalse();
+    }
+
+    @Test
+    public void sendBatteryEntryData_nullBatteryIntent_returnsNullValue() {
+        doReturn(null).when(mContext).registerReceiver(any(), any());
+        assertThat(
+                DatabaseUtils.sendBatteryEntryData(
+                        mContext, /*batteryEntryList=*/ null, mBatteryUsageStats))
+                .isNull();
+    }
+
+    @Test
+    public void sendBatteryEntryData_returnsExpectedList() {
+        doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
+        // Configures the testing BatteryEntry data.
+        final List<BatteryEntry> batteryEntryList = new ArrayList<>();
+        batteryEntryList.add(mMockBatteryEntry1);
+        batteryEntryList.add(mMockBatteryEntry2);
+        batteryEntryList.add(mMockBatteryEntry3);
+        doReturn(0.0).when(mMockBatteryEntry1).getConsumedPower();
+        doReturn(0.5).when(mMockBatteryEntry2).getConsumedPower();
+        doReturn(0.0).when(mMockBatteryEntry3).getConsumedPower();
+        doReturn(1L).when(mMockBatteryEntry3).getTimeInForegroundMs();
+
+        final List<ContentValues> valuesList =
+                DatabaseUtils.sendBatteryEntryData(
+                        mContext, batteryEntryList, mBatteryUsageStats);
+
+        assertThat(valuesList).hasSize(2);
+        // Verifies the ContentValues content.
+        verifyContentValues(0.5, valuesList.get(0));
+        verifyContentValues(0.0, valuesList.get(1));
+        // Verifies the inserted ContentValues into content provider.
+        final ContentValues[] valuesArray =
+                new ContentValues[] {valuesList.get(0), valuesList.get(1)};
+        verify(mMockContentResolver).bulkInsert(
+                DatabaseUtils.BATTERY_CONTENT_URI, valuesArray);
+        verify(mMockContentResolver).notifyChange(
+                DatabaseUtils.BATTERY_CONTENT_URI, /*observer=*/ null);
+    }
+
+    @Test
+    public void sendBatteryEntryData_emptyBatteryEntryList_sendFakeDataIntoProvider() {
+        doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
+
+        final List<ContentValues> valuesList =
+                DatabaseUtils.sendBatteryEntryData(
+                        mContext,
+                        new ArrayList<>(),
+                        mBatteryUsageStats);
+
+        assertThat(valuesList).hasSize(1);
+        verifyFakeContentValues(valuesList.get(0));
+        // Verifies the inserted ContentValues into content provider.
+        verify(mMockContentResolver).insert(any(), any());
+        verify(mMockContentResolver).notifyChange(
+                DatabaseUtils.BATTERY_CONTENT_URI, /*observer=*/ null);
+    }
+
+    @Test
+    public void sendBatteryEntryData_nullBatteryEntryList_sendFakeDataIntoProvider() {
+        doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
+
+        final List<ContentValues> valuesList =
+                DatabaseUtils.sendBatteryEntryData(
+                        mContext,
+                        /*batteryEntryList=*/ null,
+                        mBatteryUsageStats);
+
+        assertThat(valuesList).hasSize(1);
+        verifyFakeContentValues(valuesList.get(0));
+        // Verifies the inserted ContentValues into content provider.
+        verify(mMockContentResolver).insert(any(), any());
+        verify(mMockContentResolver).notifyChange(
+                DatabaseUtils.BATTERY_CONTENT_URI, /*observer=*/ null);
+    }
+
+    @Test
+    public void sendBatteryEntryData_nullBatteryUsageStats_sendFakeDataIntoProvider() {
+        doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
+
+        final List<ContentValues> valuesList =
+                DatabaseUtils.sendBatteryEntryData(
+                        mContext,
+                        /*batteryEntryList=*/ null,
+                        /*batteryUsageStats=*/ null);
+
+        assertThat(valuesList).hasSize(1);
+        verifyFakeContentValues(valuesList.get(0));
+        // Verifies the inserted ContentValues into content provider.
+        verify(mMockContentResolver).insert(any(), any());
+        verify(mMockContentResolver).notifyChange(
+                DatabaseUtils.BATTERY_CONTENT_URI, /*observer=*/ null);
+    }
+
+    @Test
+    public void getHistoryMapSinceLastFullCharge_providerIsDisabled_returnNull() {
+        setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+        assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
+                mContext, /*calendar=*/ null)).isNull();
+    }
+
+    @Test
+    public void getHistoryMapSinceLastFullCharge_emptyCursorContent_returnEmptyMap() {
+        final MatrixCursor cursor = new MatrixCursor(
+                new String[] {
+                        BatteryHistEntry.KEY_UID,
+                        BatteryHistEntry.KEY_USER_ID,
+                        BatteryHistEntry.KEY_TIMESTAMP});
+        doReturn(cursor).when(mMockContentResolver).query(any(), any(), any(), any());
+
+        assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
+                mContext, /*calendar=*/ null)).isEmpty();
+    }
+
+    @Test
+    public void getHistoryMapSinceLastFullCharge_nullCursor_returnEmptyMap() {
+        doReturn(null).when(mMockContentResolver).query(any(), any(), any(), any());
+        assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
+                mContext, /*calendar=*/ null)).isEmpty();
+    }
+
+    @Test
+    public void getHistoryMapSinceLastFullCharge_returnExpectedMap() {
+        final Long timestamp1 = Long.valueOf(1001L);
+        final Long timestamp2 = Long.valueOf(1002L);
+        final MatrixCursor cursor = getMatrixCursor();
+        doReturn(cursor).when(mMockContentResolver).query(any(), any(), any(), any());
+        // Adds fake data into the cursor.
+        cursor.addRow(new Object[] {
+                "app name1", timestamp1, 1, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+        cursor.addRow(new Object[] {
+                "app name2", timestamp2, 2, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+        cursor.addRow(new Object[] {
+                "app name3", timestamp2, 3, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+        cursor.addRow(new Object[] {
+                "app name4", timestamp2, 4, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
+                DatabaseUtils.getHistoryMapSinceLastFullCharge(
+                        mContext, /*calendar=*/ null);
+
+        assertThat(batteryHistMap).hasSize(2);
+        // Verifies the BatteryHistEntry data for timestamp1.
+        Map<String, BatteryHistEntry> batteryMap = batteryHistMap.get(timestamp1);
+        assertThat(batteryMap).hasSize(1);
+        assertThat(batteryMap.get("1").mAppLabel).isEqualTo("app name1");
+        // Verifies the BatteryHistEntry data for timestamp2.
+        batteryMap = batteryHistMap.get(timestamp2);
+        assertThat(batteryMap).hasSize(3);
+        assertThat(batteryMap.get("2").mAppLabel).isEqualTo("app name2");
+        assertThat(batteryMap.get("3").mAppLabel).isEqualTo("app name3");
+        assertThat(batteryMap.get("4").mAppLabel).isEqualTo("app name4");
+    }
+
+    @Test
+    public void getHistoryMapSinceLastFullCharge_withWorkProfile_returnExpectedMap()
+            throws PackageManager.NameNotFoundException {
+        doReturn("com.fake.package").when(mContext).getPackageName();
+        doReturn(mMockContext).when(mContext).createPackageContextAsUser(
+                "com.fake.package", /*flags=*/ 0, UserHandle.OWNER);
+        BatteryTestUtils.setWorkProfile(mContext);
+        doReturn(getMatrixCursor()).when(mMockContentResolver2)
+                .query(any(), any(), any(), any());
+        doReturn(null).when(mMockContentResolver).query(any(), any(), any(), any());
+
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
+                DatabaseUtils.getHistoryMapSinceLastFullCharge(
+                        mContext, /*calendar=*/ null);
+
+        assertThat(batteryHistMap).isEmpty();
+    }
+
+    @Test
+    public void saveLastFullChargeTimestampPref_notFullCharge_returnsFalse() {
+        DatabaseUtils.saveLastFullChargeTimestampPref(
+                mContext,
+                BatteryManager.BATTERY_STATUS_UNKNOWN,
+                /* level */ 10,
+                /* timestamp */ 1);
+        assertThat(DatabaseUtils.getLastFullChargeTimestampPref(mContext)).isEqualTo(0);
+    }
+
+    @Test
+    public void saveLastFullChargeTimestampPref_fullStatus_returnsTrue() {
+        long expectedTimestamp = 1;
+        DatabaseUtils.saveLastFullChargeTimestampPref(
+                mContext,
+                BatteryManager.BATTERY_STATUS_FULL,
+                /* level */ 10,
+                /* timestamp */ expectedTimestamp);
+        assertThat(DatabaseUtils.getLastFullChargeTimestampPref(mContext))
+                .isEqualTo(expectedTimestamp);
+    }
+
+    @Test
+    public void saveLastFullChargeTimestampPref_level100_returnsTrue() {
+        long expectedTimestamp = 1;
+        DatabaseUtils.saveLastFullChargeTimestampPref(
+                mContext,
+                BatteryManager.BATTERY_STATUS_UNKNOWN,
+                /* level */ 100,
+                /* timestamp */ expectedTimestamp);
+        assertThat(DatabaseUtils.getLastFullChargeTimestampPref(mContext))
+                .isEqualTo(expectedTimestamp);
+    }
+
+    @Test
+    public void getStartTimestampForLastFullCharge_noTimestampPreference_returnsSixDaysAgo() {
+        Calendar currentCalendar = Calendar.getInstance();
+        currentCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50
+        Calendar expectedCalendar = Calendar.getInstance();
+        expectedCalendar.set(2022, 5, 29, 0, 0, 0); // 2022-06-29 00:00:00
+        expectedCalendar.set(Calendar.MILLISECOND, 0);
+
+        assertThat(DatabaseUtils.getStartTimestampForLastFullCharge(mContext, currentCalendar))
+                .isEqualTo(expectedCalendar.getTimeInMillis());
+    }
+
+    @Test
+    public void getStartTimestampForLastFullCharge_lastFullChargeEarlier_returnsSixDaysAgo() {
+        Calendar lastFullCalendar = Calendar.getInstance();
+        lastFullCalendar.set(2021, 11, 25, 6, 30, 50); // 2021-12-25 06:30:50
+        DatabaseUtils.saveLastFullChargeTimestampPref(
+                mContext,
+                BatteryManager.BATTERY_STATUS_UNKNOWN,
+                /* level */ 100,
+                /* timestamp */ lastFullCalendar.getTimeInMillis());
+        Calendar currentCalendar = Calendar.getInstance();
+        currentCalendar.set(2022, 0, 2, 6, 30, 50); // 2022-01-02 06:30:50
+        Calendar expectedCalendar = Calendar.getInstance();
+        expectedCalendar.set(2021, 11, 27, 0, 0, 0); // 2021-12-27 00:00:00
+        expectedCalendar.set(Calendar.MILLISECOND, 0);
+
+        assertThat(DatabaseUtils.getStartTimestampForLastFullCharge(mContext, currentCalendar))
+                .isEqualTo(expectedCalendar.getTimeInMillis());
+    }
+
+    @Test
+    public void getStartTimestampForLastFullCharge_lastFullChargeLater_returnsLastFullCharge() {
+        Calendar lastFullCalendar = Calendar.getInstance();
+        lastFullCalendar.set(2022, 6, 1, 6, 30, 50); // 2022-07-01 06:30:50
+        long expectedTimestamp = lastFullCalendar.getTimeInMillis();
+        DatabaseUtils.saveLastFullChargeTimestampPref(
+                mContext,
+                BatteryManager.BATTERY_STATUS_UNKNOWN,
+                /* level */ 100,
+                /* timestamp */ expectedTimestamp);
+        Calendar currentCalendar = Calendar.getInstance();
+        currentCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50
+
+        assertThat(DatabaseUtils.getStartTimestampForLastFullCharge(mContext, currentCalendar))
+                .isEqualTo(expectedTimestamp);
+    }
+
+    private void setProviderSetting(int value) {
+        when(mPackageManager.getComponentEnabledSetting(
+                new ComponentName(
+                        DatabaseUtils.SETTINGS_PACKAGE_PATH,
+                        DatabaseUtils.BATTERY_PROVIDER_CLASS_PATH)))
+                .thenReturn(value);
+    }
+
+    private static void verifyContentValues(double consumedPower, ContentValues values) {
+        assertThat(values.getAsDouble(BatteryHistEntry.KEY_CONSUME_POWER))
+                .isEqualTo(consumedPower);
+        assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_LEVEL)).isEqualTo(20);
+        assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_STATUS))
+                .isEqualTo(BatteryManager.BATTERY_STATUS_FULL);
+        assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_HEALTH))
+                .isEqualTo(BatteryManager.BATTERY_HEALTH_COLD);
+    }
+
+    private static void verifyFakeContentValues(ContentValues values) {
+        assertThat(values.getAsInteger("batteryLevel")).isEqualTo(20);
+        assertThat(values.getAsInteger("batteryStatus"))
+                .isEqualTo(BatteryManager.BATTERY_STATUS_FULL);
+        assertThat(values.getAsInteger("batteryHealth"))
+                .isEqualTo(BatteryManager.BATTERY_HEALTH_COLD);
+        assertThat(values.getAsString("packageName"))
+                .isEqualTo(ConvertUtils.FAKE_PACKAGE_NAME);
+    }
+
+    private static Intent getBatteryIntent() {
+        final Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        intent.putExtra(BatteryManager.EXTRA_LEVEL, 20);
+        intent.putExtra(BatteryManager.EXTRA_SCALE, 100);
+        intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
+        intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_COLD);
+        return intent;
+    }
+
+    private static MatrixCursor getMatrixCursor() {
+        return new MatrixCursor(
+                new String[] {
+                        BatteryHistEntry.KEY_APP_LABEL,
+                        BatteryHistEntry.KEY_TIMESTAMP,
+                        BatteryHistEntry.KEY_UID,
+                        BatteryHistEntry.KEY_CONSUMER_TYPE});
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java
new file mode 100644
index 0000000..41e3f4d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 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.db;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.database.Cursor;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.testutils.BatteryTestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.List;
+
+/** Tests for {@link BatteryStateDao}. */
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryStateDaoTest {
+    private static final int CURSOR_COLUMN_SIZE = 19;
+    private static final long TIMESTAMP1 = System.currentTimeMillis();
+    private static final long TIMESTAMP2 = System.currentTimeMillis() + 2;
+    private static final long TIMESTAMP3 = System.currentTimeMillis() + 4;
+    private static final String PACKAGE_NAME1 = "com.android.apps.settings";
+    private static final String PACKAGE_NAME2 = "com.android.apps.calendar";
+    private static final String PACKAGE_NAME3 = "com.android.apps.gmail";
+
+    private Context mContext;
+    private BatteryStateDatabase mDatabase;
+    private BatteryStateDao mBatteryStateDao;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mDatabase = BatteryTestUtils.setUpBatteryStateDatabase(mContext);
+        mBatteryStateDao = mDatabase.batteryStateDao();
+        BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP3, PACKAGE_NAME3);
+        BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP2, PACKAGE_NAME2);
+        BatteryTestUtils.insertDataToBatteryStateDatabase(
+                mContext, TIMESTAMP1, PACKAGE_NAME1, /*multiple=*/ true);
+    }
+
+    @After
+    public void closeDb() {
+        mDatabase.close();
+        BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null);
+    }
+
+    @Test
+    public void batteryStateDao_insertAll() throws Exception {
+        final List<BatteryState> states = mBatteryStateDao.getAllAfter(TIMESTAMP1);
+        assertThat(states).hasSize(2);
+        // Verifies the queried battery states.
+        assertBatteryState(states.get(0), TIMESTAMP3, PACKAGE_NAME3);
+        assertBatteryState(states.get(1), TIMESTAMP2, PACKAGE_NAME2);
+    }
+
+    @Test
+    public void batteryStateDao_getCursorAfter() throws Exception {
+        final Cursor cursor = mBatteryStateDao.getCursorAfter(TIMESTAMP2);
+        assertThat(cursor.getCount()).isEqualTo(2);
+        assertThat(cursor.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE);
+        // Verifies the queried first battery state.
+        cursor.moveToFirst();
+        assertThat(cursor.getString(4 /*packageName*/)).isEqualTo(PACKAGE_NAME3);
+        // Verifies the queried second battery state.
+        cursor.moveToNext();
+        assertThat(cursor.getString(4 /*packageName*/)).isEqualTo(PACKAGE_NAME2);
+    }
+
+    @Test
+    public void batteryStateDao_clearAllBefore() throws Exception {
+        mBatteryStateDao.clearAllBefore(TIMESTAMP2);
+
+        final List<BatteryState> states = mBatteryStateDao.getAllAfter(0);
+        assertThat(states).hasSize(1);
+        // Verifies the queried battery state.
+        assertBatteryState(states.get(0), TIMESTAMP3, PACKAGE_NAME3);
+    }
+
+    @Test
+    public void batteryStateDao_clearAll() throws Exception {
+        assertThat(mBatteryStateDao.getAllAfter(0)).hasSize(3);
+        mBatteryStateDao.clearAll();
+        assertThat(mBatteryStateDao.getAllAfter(0)).isEmpty();
+    }
+
+    @Test
+    public void getInstance_createNewInstance() throws Exception {
+        BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null);
+        assertThat(BatteryStateDatabase.getInstance(mContext)).isNotNull();
+    }
+
+    @Test
+    public void getDistinctTimestampCount_returnsExpectedResult() {
+        assertThat(mBatteryStateDao.getDistinctTimestampCount(/*timestamp=*/ 0))
+                .isEqualTo(3);
+        assertThat(mBatteryStateDao.getDistinctTimestampCount(TIMESTAMP1))
+                .isEqualTo(2);
+    }
+
+    @Test
+    public void getDistinctTimestamps_returnsExpectedResult() {
+        final List<Long> timestamps =
+                mBatteryStateDao.getDistinctTimestamps(/*timestamp=*/ 0);
+
+        assertThat(timestamps).hasSize(3);
+        assertThat(timestamps).containsExactly(TIMESTAMP1, TIMESTAMP2, TIMESTAMP3);
+    }
+
+    private static void assertBatteryState(
+            BatteryState state, long timestamp, String packageName) {
+        assertThat(state.timestamp).isEqualTo(timestamp);
+        assertThat(state.packageName).isEqualTo(packageName);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateTest.java
new file mode 100644
index 0000000..ef23c41
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 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.db;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.os.BatteryManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link BatteryState}. */
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryStateTest {
+    private static final int BATTERY_LEVEL = 45;
+    private static final int BATTERY_STATUS = BatteryManager.BATTERY_STATUS_FULL;
+    private static final int BATTERY_HEALTH = BatteryManager.BATTERY_HEALTH_COLD;
+
+    private Intent mBatteryIntent;
+
+    @Before
+    public void setUp() {
+        mBatteryIntent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        // Inserts the battery states into intent.
+        mBatteryIntent.putExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL);
+        mBatteryIntent.putExtra(BatteryManager.EXTRA_STATUS, BATTERY_STATUS);
+        mBatteryIntent.putExtra(BatteryManager.EXTRA_HEALTH, BATTERY_HEALTH);
+    }
+
+    @Test
+    public void testBuilder_returnsExpectedResult() {
+        mBatteryIntent.putExtra(BatteryManager.EXTRA_SCALE, 100);
+        BatteryState state = create(mBatteryIntent);
+
+        // Verifies the app relative information.
+        assertThat(state.uid).isEqualTo(1001L);
+        assertThat(state.userId).isEqualTo(100L);
+        assertThat(state.appLabel).isEqualTo("Settings");
+        assertThat(state.packageName).isEqualTo("com.android.settings");
+        assertThat(state.isHidden).isTrue();
+        assertThat(state.bootTimestamp).isEqualTo(101L);
+        assertThat(state.timestamp).isEqualTo(100001L);
+        // Verifies the battery relative information.
+        assertThat(state.totalPower).isEqualTo(100);
+        assertThat(state.consumePower).isEqualTo(3);
+        assertThat(state.percentOfTotal).isEqualTo(10);
+        assertThat(state.foregroundUsageTimeInMs).isEqualTo(60000);
+        assertThat(state.backgroundUsageTimeInMs).isEqualTo(10000);
+        assertThat(state.drainType).isEqualTo(1);
+        assertThat(state.consumerType).isEqualTo(2);
+        assertThat(state.batteryLevel).isEqualTo(BATTERY_LEVEL);
+        assertThat(state.batteryStatus).isEqualTo(BATTERY_STATUS);
+        assertThat(state.batteryHealth).isEqualTo(BATTERY_HEALTH);
+    }
+
+    @Test
+    public void create_withoutBatteryScale_returnsStateWithInvalidLevel() {
+        BatteryState state = create(mBatteryIntent);
+        assertThat(state.batteryLevel).isEqualTo(-1);
+    }
+
+    private static BatteryState create(Intent intent) {
+        return BatteryState.newBuilder()
+                .setUid(1001L)
+                .setUserId(100L)
+                .setAppLabel("Settings")
+                .setPackageName("com.android.settings")
+                .setIsHidden(true)
+                .setBootTimestamp(101L)
+                .setTimestamp(100001L)
+                .setTotalPower(100f)
+                .setConsumePower(3f)
+                .setPercentOfTotal(10f)
+                .setForegroundUsageTimeInMs(60000)
+                .setBackgroundUsageTimeInMs(10000)
+                .setDrainType(1)
+                .setConsumerType(2)
+                .setBatteryIntent(intent)
+                .build();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
index 9043974..c3b7821 100644
--- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
+++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
@@ -61,12 +61,9 @@
 import com.android.settings.testutils.shadow.ShadowThreadUtils;
 import com.android.settings.testutils.shadow.ShadowUserManager;
 import com.android.settings.testutils.shadow.ShadowUtils;
-import com.android.settings.wifi.slice.WifiScanWorker;
-import com.android.settingslib.wifi.WifiTracker;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -100,7 +97,6 @@
 @Config(shadows = {ShadowUserManager.class, ShadowUtils.class,
         SlicesDatabaseAccessorTest.ShadowApplicationPackageManager.class,
         ShadowBluetoothAdapter.class, ShadowLockPatternUtils.class,
-        SettingsSliceProviderTest.ShadowWifiScanWorker.class,
         SettingsSliceProviderTest.ShadowTheme.class})
 public class SettingsSliceProviderTest {
 
@@ -123,6 +119,7 @@
     private Context mContext;
     private SettingsSliceProvider mProvider;
     private ShadowPackageManager mPackageManager;
+
     @Mock
     private SliceManager mManager;
 
@@ -573,14 +570,6 @@
     }
 
     @Test
-    @Ignore
-    public void bindSlice_wifiSlice_returnsWifiSlice() {
-        final Slice wifiSlice = mProvider.onBindSlice(CustomSliceRegistry.WIFI_SLICE_URI);
-
-        assertThat(wifiSlice.getUri()).isEqualTo(CustomSliceRegistry.WIFI_SLICE_URI);
-    }
-
-    @Test
     public void bindSlice_flashlightSlice_returnsFlashlightSlice() {
         Settings.Secure.putInt(
                 mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
@@ -604,32 +593,6 @@
     }
 
     @Test
-    @Ignore
-    public void onSlicePinned_backgroundWorker_started() {
-        mProvider.onSlicePinned(CustomSliceRegistry.WIFI_SLICE_URI);
-
-        verify(ShadowWifiScanWorker.getWifiTracker()).onStart();
-    }
-
-    @Test
-    @Ignore
-    public void onSlicePinned_backgroundWorker_stopped() {
-        mProvider.onSlicePinned(CustomSliceRegistry.WIFI_SLICE_URI);
-        mProvider.onSliceUnpinned(CustomSliceRegistry.WIFI_SLICE_URI);
-
-        verify(ShadowWifiScanWorker.getWifiTracker()).onStop();
-    }
-
-    @Test
-    @Ignore
-    public void shutdown_backgroundWorker_closed() {
-        mProvider.onSlicePinned(CustomSliceRegistry.WIFI_SLICE_URI);
-        mProvider.shutdown();
-
-        verify(ShadowWifiScanWorker.getWifiTracker()).onDestroy();
-    }
-
-    @Test
     @Config(qualifiers = "mcc998")
     public void grantAllowlistedPackagePermissions_noAllowlist_shouldNotGrant() {
         final List<Uri> uris = new ArrayList<>();
@@ -716,31 +679,6 @@
                 .build();
     }
 
-    @Implements(WifiScanWorker.class)
-    public static class ShadowWifiScanWorker {
-        private static WifiTracker mWifiTracker;
-
-        @Implementation
-        protected void onSlicePinned() {
-            mWifiTracker = mock(WifiTracker.class);
-            mWifiTracker.onStart();
-        }
-
-        @Implementation
-        protected void onSliceUnpinned() {
-            mWifiTracker.onStop();
-        }
-
-        @Implementation
-        protected void close() {
-            mWifiTracker.onDestroy();
-        }
-
-        static WifiTracker getWifiTracker() {
-            return mWifiTracker;
-        }
-    }
-
     @Implements(value = StrictMode.class)
     public static class ShadowStrictMode {
 
diff --git a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
index e4e26d2..fa3ee10 100644
--- a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
+++ b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
@@ -16,8 +16,21 @@
 
 package com.android.settings.testutils;
 
+import android.content.Context;
 import android.content.Intent;
 import android.os.BatteryManager;
+import android.os.UserManager;
+
+import androidx.room.Room;
+
+import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDao;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
+
+import com.google.common.collect.ImmutableList;
+
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowUserManager;
 
 public class BatteryTestUtils {
 
@@ -37,6 +50,65 @@
                 BatteryManager.BATTERY_STATUS_DISCHARGING);
     }
 
+    /** Sets the work profile mode. */
+    public static void setWorkProfile(Context context) {
+        final UserManager userManager = context.getSystemService(UserManager.class);
+        Shadows.shadowOf(userManager).setManagedProfile(true);
+        // Changes out of the default system user so isSystemUser() returns false.
+        final int userId = 1001;
+        Shadows.shadowOf(userManager)
+                .addUser(userId, "name", /*flags=*/ ShadowUserManager.FLAG_PRIMARY);
+        Shadows.shadowOf(userManager).switchUser(userId);
+    }
+
+    /** Creates and sets up the in-memory {@link BatteryStateDatabase}. */
+    public static BatteryStateDatabase setUpBatteryStateDatabase(Context context) {
+        final BatteryStateDatabase inMemoryDatabase =
+                Room.inMemoryDatabaseBuilder(context, BatteryStateDatabase.class)
+                        .allowMainThreadQueries()
+                        .build();
+        BatteryStateDatabase.setBatteryStateDatabase(inMemoryDatabase);
+        return inMemoryDatabase;
+    }
+
+    /** Inserts a fake data into the database for testing. */
+    public static void insertDataToBatteryStateDatabase(
+            Context context, long timestamp, String packageName) {
+        insertDataToBatteryStateDatabase(context, timestamp, packageName, /*multiple=*/ false);
+    }
+
+    /** Inserts a fake data into the database for testing. */
+    public static void insertDataToBatteryStateDatabase(
+            Context context, long timestamp, String packageName, boolean multiple) {
+        final BatteryState state =
+                new BatteryState(
+                        /*uid=*/ 1001L,
+                        /*userId=*/ 100L,
+                        /*appLabel=*/ "Settings",
+                        packageName,
+                        /*isHidden=*/ true,
+                        /*bootTimestamp=*/ timestamp - 1,
+                        timestamp,
+                        /*zoneId=*/ "Europe/Paris",
+                        /*totalPower=*/ 100f,
+                        /*consumePower=*/ 0.3f,
+                        /*percentOfTotal=*/ 10f,
+                        /*foregroundUsageTimeInMs=*/ 60000,
+                        /*backgroundUsageTimeInMs=*/ 10000,
+                        /*drainType=*/ 1,
+                        /*consumerType=*/ 2,
+                        /*batteryLevel=*/ 31,
+                        /*batteryStatus=*/ 0,
+                        /*batteryHealth=*/ 0);
+        BatteryStateDao dao =
+                BatteryStateDatabase.getInstance(context).batteryStateDao();
+        if (multiple) {
+            dao.insertAll(ImmutableList.of(state));
+        } else {
+            dao.insert(state);
+        }
+    }
+
     private static Intent getCustomBatteryIntent(int plugged, int level, int scale, int status) {
         Intent intent = new Intent();
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, plugged);
diff --git a/tests/robotests/src/com/android/settings/testutils/FakeClock.java b/tests/robotests/src/com/android/settings/testutils/FakeClock.java
new file mode 100644
index 0000000..946d051
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/FakeClock.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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.testutils;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+
+/** A fake {@link Clock} class for testing. */
+public final class FakeClock extends Clock {
+    private long mCurrentTimeMillis;
+
+    public FakeClock() {}
+
+    /** Sets the time in millis for {@link Clock#millis()} method. */
+    public void setCurrentTime(Duration duration) {
+        mCurrentTimeMillis = duration.toMillis();
+    }
+
+    @Override
+    public ZoneId getZone() {
+        throw new UnsupportedOperationException("unsupported!");
+    }
+
+    @Override
+    public Clock withZone(ZoneId zone) {
+        throw new UnsupportedOperationException("unsupported!");
+    }
+
+    @Override
+    public Instant instant() {
+        throw new UnsupportedOperationException("unsupported!");
+    }
+
+    @Override
+    public long millis() {
+        return mCurrentTimeMillis;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java
index 4183f29..94d797a 100644
--- a/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java
@@ -31,7 +31,6 @@
 import com.android.settings.core.BasePreferenceController;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -94,9 +93,11 @@
                 .isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE);
     }
 
-    @Ignore
     @Test
     public void isChecked_uwbEnabled_shouldReturnTrue() {
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(true).when(mPackageManager)
+                .hasSystemFeature(PackageManager.FEATURE_UWB);
         doReturn(mController.STATE_ENABLED_ACTIVE).when(mUwbManager).getAdapterState();
 
         assertThat(mController.isChecked()).isTrue();
@@ -104,6 +105,9 @@
 
     @Test
     public void isChecked_uwbDisabled_shouldReturnFalse() {
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(true).when(mPackageManager)
+                .hasSystemFeature(PackageManager.FEATURE_UWB);
         doReturn(mController.STATE_DISABLED).when(mUwbManager).getAdapterState();
 
         assertThat(mController.isChecked()).isFalse();
diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java
index e8ee7c3..bc6053b 100644
--- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java
@@ -16,13 +16,14 @@
 
 package com.android.settings.wifi.tether;
 
+import static com.android.settings.wifi.WifiUtils.setCanShowWifiHotspotCached;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.net.TetheringManager;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiManager;
 
@@ -62,8 +63,6 @@
     @Mock
     private Lifecycle mLifecycle;
     @Mock
-    private TetheringManager mTetheringManager;
-    @Mock
     private WifiManager mWifiManager;
     @Mock
     private PreferenceScreen mScreen;
@@ -74,38 +73,37 @@
 
     @Before
     public void setUp() {
+        setCanShowWifiHotspotCached(true);
         FakeFeatureFactory.setupForTest();
         mPreference = new PrimarySwitchPreference(mContext);
-        when(mContext.getSystemService(Context.TETHERING_SERVICE)).thenReturn(mTetheringManager);
         when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager);
         when(mScreen.findPreference(anyString())).thenReturn(mPreference);
         mSoftApConfiguration = new SoftApConfiguration.Builder().setSsid(SSID).build();
         when(mWifiManager.getSoftApConfiguration()).thenReturn(mSoftApConfiguration);
 
-        when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{"1", "2"});
         mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager,
-                mTetheringManager, false /* initSoftApManager */, true /* isWifiTetheringAllow */);
+                false /* initSoftApManager */, true /* isWifiTetheringAllow */);
         mController.displayPreference(mScreen);
     }
 
     @Test
-    public void isAvailable_noTetherRegex_shouldReturnFalse() {
-        when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{});
-        mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager,
-                mTetheringManager, false /* initSoftApManager */, true /* isWifiTetheringAllow */);
+    public void isAvailable_canNotShowWifiHotspot_shouldReturnFalse() {
+        setCanShowWifiHotspotCached(false);
 
         assertThat(mController.isAvailable()).isFalse();
     }
 
     @Test
-    public void isAvailable_hasTetherRegex_shouldReturnTrue() {
+    public void isAvailable_canShowWifiHostspot_shouldReturnTrue() {
+        setCanShowWifiHotspotCached(true);
+
         assertThat(mController.isAvailable()).isTrue();
     }
 
     @Test
     public void displayPreference_wifiTetheringNotAllowed_shouldDisable() {
         mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager,
-                mTetheringManager, false /* initSoftApManager */, false /* isWifiTetheringAllow */);
+                false /* initSoftApManager */, false /* isWifiTetheringAllow */);
 
         mController.displayPreference(mScreen);
 
@@ -116,7 +114,7 @@
     @Test
     public void displayPreference_wifiTetheringAllowed_shouldEnable() {
         mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager,
-                mTetheringManager, false /* initSoftApManager */, true /* isWifiTetheringAllow */);
+                false /* initSoftApManager */, true /* isWifiTetheringAllow */);
 
         mController.displayPreference(mScreen);
 
diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java
index d19bc90..6d3b879 100644
--- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.wifi.tether;
 
+import static com.android.settings.wifi.WifiUtils.setCanShowWifiHotspotCached;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -42,6 +44,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.R;
+import com.android.settings.dashboard.RestrictedDashboardFragment;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.shadow.ShadowFragment;
 
@@ -55,6 +58,8 @@
 import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
 import org.robolectric.util.ReflectionHelpers;
 
 import java.util.List;
@@ -88,6 +93,7 @@
 
     @Before
     public void setUp() {
+        setCanShowWifiHotspotCached(true);
         doReturn(mWifiManager).when(mContext).getSystemService(WifiManager.class);
         doReturn(mConnectivityManager)
                 .when(mContext).getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -101,6 +107,17 @@
     }
 
     @Test
+    @Config(shadows = ShadowRestrictedDashboardFragment.class)
+    public void onCreate_canNotShowWifiHotspot_shouldFinish() {
+        setCanShowWifiHotspotCached(false);
+        mWifiTetherSettings = spy(new WifiTetherSettings(mWifiRestriction));
+
+        mWifiTetherSettings.onCreate(null);
+
+        verify(mWifiTetherSettings).finish();
+    }
+
+    @Test
     @Config(shadows = ShadowFragment.class)
     public void onStart_uiIsRestricted_removeAllPreferences() {
         spyWifiTetherSettings();
@@ -219,4 +236,13 @@
 
         mWifiTetherSettings.onCreate(Bundle.EMPTY);
     }
+
+    @Implements(RestrictedDashboardFragment.class)
+    public static final class ShadowRestrictedDashboardFragment {
+
+        @Implementation
+        public void onCreate(Bundle icicle) {
+            // do nothing
+        }
+    }
 }
diff --git a/tests/spa_unit/Android.bp b/tests/spa_unit/Android.bp
index ed83ab2..9126c55 100644
--- a/tests/spa_unit/Android.bp
+++ b/tests/spa_unit/Android.bp
@@ -35,9 +35,12 @@
         "androidx.compose.ui_ui-test-manifest",
         "androidx.test.ext.junit",
         "androidx.test.runner",
-        "mockito-target-minus-junit4",
+        "mockito-target-inline-minus-junit4",
         "truth-prebuilt",
     ],
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+    ],
     kotlincflags: [
         "-Xjvm-default=all",
         "-opt-in=kotlin.RequiresOptIn",
diff --git a/tests/spa_unit/AndroidManifest.xml b/tests/spa_unit/AndroidManifest.xml
index 5cf8ffd..be16de3 100644
--- a/tests/spa_unit/AndroidManifest.xml
+++ b/tests/spa_unit/AndroidManifest.xml
@@ -19,7 +19,7 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="com.android.settings.tests.spa_unit">
 
-    <application>
+    <application android:debuggable="true">
         <provider android:name="com.android.settings.slices.SettingsSliceProvider"
                   android:authorities="${applicationId}.slices"
                   tools:replace="android:authorities"/>
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonRepositoryTest.kt
new file mode 100644
index 0000000..4e1b1b6
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonRepositoryTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 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.spa.app.appinfo
+
+import android.app.ActivityManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import androidx.core.os.bundleOf
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppButtonRepositoryTest {
+
+    @JvmField
+    @Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+
+    private lateinit var appButtonRepository: AppButtonRepository
+
+    @Before
+    fun setUp() {
+        whenever(context.packageManager).thenReturn(packageManager)
+
+        appButtonRepository = AppButtonRepository(context)
+    }
+
+    private fun mockGetHomeActivities(
+        homeActivities: List<ResolveInfo>,
+        currentDefaultHome: ComponentName? = null,
+    ) {
+        whenever(packageManager.getHomeActivities(any())).then {
+            @Suppress("UNCHECKED_CAST")
+            (it.arguments[0] as ArrayList<ResolveInfo>).addAll(homeActivities)
+            currentDefaultHome
+        }
+    }
+
+    @Test
+    fun getHomePackageInfo_empty() {
+        mockGetHomeActivities(homeActivities = emptyList())
+
+        val homePackageInfo = appButtonRepository.getHomePackageInfo()
+
+        assertThat(homePackageInfo.homePackages).isEmpty()
+        assertThat(homePackageInfo.currentDefaultHome).isNull()
+    }
+
+    @Test
+    fun getHomePackageInfo_noActivityInfo() {
+        mockGetHomeActivities(homeActivities = listOf(ResolveInfo()))
+
+        val homePackageInfo = appButtonRepository.getHomePackageInfo()
+
+        assertThat(homePackageInfo.homePackages).isEmpty()
+        assertThat(homePackageInfo.currentDefaultHome).isNull()
+    }
+
+    @Test
+    fun getHomePackageInfo_oneHome() {
+        mockGetHomeActivities(
+            homeActivities = listOf(RESOLVE_INFO),
+            currentDefaultHome = COMPONENT_NAME,
+        )
+
+        val homePackageInfo = appButtonRepository.getHomePackageInfo()
+
+        assertThat(homePackageInfo.homePackages).containsExactly(PACKAGE_NAME)
+        assertThat(homePackageInfo.currentDefaultHome).isSameInstanceAs(COMPONENT_NAME)
+    }
+
+    @Test
+    fun getHomePackageInfo_homeAlternateSignatureMatch() {
+        mockGetHomeActivities(homeActivities = listOf(RESOLVE_INFO_WITH_ALTERNATE))
+        whenever(packageManager.checkSignatures(PACKAGE_NAME_ALTERNATE, PACKAGE_NAME))
+            .thenReturn(PackageManager.SIGNATURE_MATCH)
+
+        val homePackageInfo = appButtonRepository.getHomePackageInfo()
+
+        assertThat(homePackageInfo.homePackages).containsExactly(
+            PACKAGE_NAME, PACKAGE_NAME_ALTERNATE
+        )
+    }
+
+    @Test
+    fun getHomePackageInfo_homeAlternateSignatureNoMatch() {
+        mockGetHomeActivities(homeActivities = listOf(RESOLVE_INFO_WITH_ALTERNATE))
+        whenever(packageManager.checkSignatures(PACKAGE_NAME_ALTERNATE, PACKAGE_NAME))
+            .thenReturn(PackageManager.SIGNATURE_NO_MATCH)
+
+        val homePackageInfo = appButtonRepository.getHomePackageInfo()
+
+        assertThat(homePackageInfo.homePackages).containsExactly(PACKAGE_NAME)
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "packageName"
+        const val PACKAGE_NAME_ALTERNATE = "packageName.alternate"
+        const val ACTIVITY_NAME = "activityName"
+        val COMPONENT_NAME = ComponentName(PACKAGE_NAME, ACTIVITY_NAME)
+        val RESOLVE_INFO = ResolveInfo().apply {
+            activityInfo = ActivityInfo().apply {
+                packageName = PACKAGE_NAME
+            }
+        }
+        val RESOLVE_INFO_WITH_ALTERNATE = ResolveInfo().apply {
+            activityInfo = ActivityInfo().apply {
+                packageName = PACKAGE_NAME
+                metaData = bundleOf(
+                    ActivityManager.META_HOME_ALTERNATE to PACKAGE_NAME_ALTERNATE,
+                )
+            }
+        }
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreferenceTest.kt
new file mode 100644
index 0000000..a402a02
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreferenceTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2022 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.spa.app.appinfo
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationUserState
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.spaprivileged.framework.common.domainVerificationManager
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doReturn
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppOpenByDefaultPreferenceTest {
+    @JvmField
+    @Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+
+    @Mock
+    private lateinit var domainVerificationManager: DomainVerificationManager
+
+    @Mock
+    private lateinit var allowedUserState: DomainVerificationUserState
+
+    @Mock
+    private lateinit var notAllowedUserState: DomainVerificationUserState
+
+    @Before
+    fun setUp() {
+        whenever(context.packageManager).thenReturn(packageManager)
+        doReturn(context).`when`(context).createContextAsUser(any(), anyInt())
+        whenever(context.domainVerificationManager).thenReturn(domainVerificationManager)
+        whenever(allowedUserState.isLinkHandlingAllowed).thenReturn(true)
+        whenever(notAllowedUserState.isLinkHandlingAllowed).thenReturn(false)
+    }
+
+    @Test
+    fun instantApp_notDisplay() {
+        val instantApp = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
+        }
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                AppOpenByDefaultPreference(instantApp)
+            }
+        }
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun browserApp_notDisplay() {
+        val browserApp = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
+        }
+        val resolveInfo = ResolveInfo().apply {
+            activityInfo = ActivityInfo()
+            handleAllWebDataURI = true
+        }
+        whenever(packageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt()))
+            .thenReturn(listOf(resolveInfo))
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                AppOpenByDefaultPreference(browserApp)
+            }
+        }
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun allowedUserState_alwaysOpen() {
+        whenever(domainVerificationManager.getDomainVerificationUserState(PACKAGE_NAME))
+            .thenReturn(allowedUserState)
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                AppOpenByDefaultPreference(INSTALLED_ENABLED_APP)
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.launch_by_default))
+            .assertIsDisplayed()
+            .assertIsEnabled()
+        composeTestRule.onNodeWithText(context.getString(R.string.app_link_open_always))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun notAllowedUserState_neverOpen() {
+        whenever(domainVerificationManager.getDomainVerificationUserState(PACKAGE_NAME))
+            .thenReturn(notAllowedUserState)
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                AppOpenByDefaultPreference(INSTALLED_ENABLED_APP)
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.launch_by_default))
+            .assertIsDisplayed()
+            .assertIsEnabled()
+        composeTestRule.onNodeWithText(context.getString(R.string.app_link_open_never))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun notInstalledApp_disabled() {
+        val notInstalledApp = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                AppOpenByDefaultPreference(notInstalledApp)
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.launch_by_default))
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun notEnabledApp_disabled() {
+        val notEnabledApp = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            flags = ApplicationInfo.FLAG_INSTALLED
+            enabled = false
+        }
+
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                AppOpenByDefaultPreference(notEnabledApp)
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.launch_by_default))
+            .assertIsNotEnabled()
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "package name"
+
+        val INSTALLED_ENABLED_APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            flags = ApplicationInfo.FLAG_INSTALLED
+            enabled = true
+        }
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt
index 39c3413..47f553b 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt
@@ -68,12 +68,12 @@
     }
 
     @Test
-    fun uninstalledApp_notDisplayed() {
-        val uninstalledApp = ApplicationInfo()
+    fun notInstalledApp_notDisplayed() {
+        val notInstalledApp = ApplicationInfo()
 
         composeTestRule.setContent {
             CompositionLocalProvider(LocalContext provides context) {
-                AppStoragePreference(uninstalledApp)
+                AppStoragePreference(notInstalledApp)
             }
         }
 
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
index 56ba33a..e3fcdd9 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
@@ -35,13 +35,11 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settings.R
 import com.android.settings.testutils.FakeFeatureFactory
-import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Spy
@@ -122,15 +120,15 @@
     }
 
     @Test
-    fun uninstalledApp_disabled() {
+    fun notInstalledApp_disabled() {
         mockActivitiesQueryResult(listOf(MATCHED_RESOLVE_INFO))
-        val uninstalledApp = ApplicationInfo().apply {
+        val notInstalledApp = ApplicationInfo().apply {
             packageName = PACKAGE_NAME
         }
 
         composeTestRule.setContent {
             CompositionLocalProvider(LocalContext provides context) {
-                AppTimeSpentPreference(uninstalledApp)
+                AppTimeSpentPreference(notInstalledApp)
             }
         }
 
diff --git a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceControllerTest.java
new file mode 100644
index 0000000..503af46
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceControllerTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 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.applications.specialaccess.notificationaccess;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.service.notification.NotificationListenerService;
+
+import androidx.preference.Preference;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class MoreSettingsPreferenceControllerTest {
+
+    Context mContext;
+    private MoreSettingsPreferenceController mController;
+    @Mock
+    PackageManager mPm;
+    final String mPkg = "pkg";
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = ApplicationProvider.getApplicationContext();
+
+        mController = new MoreSettingsPreferenceController(mContext);
+        mController.setPackage(mPkg);
+        mController.setPackageManager(mPm);
+
+    }
+
+    @Test
+    public void getAvailabilityStatus_available() {
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        when(mPm.queryIntentActivities(captor.capture(), any())).thenReturn(
+                ImmutableList.of(mock(ResolveInfo.class)));
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+        assertThat(captor.getValue().getPackage()).isEqualTo(mPkg);
+        assertThat(captor.getValue().getAction()).isEqualTo(Intent.ACTION_MAIN);
+        assertThat(captor.getValue().getCategories()).contains(
+                NotificationListenerService.INTENT_CATEGORY_SETTINGS_HOME);
+    }
+
+    @Test
+    public void getAvailabilityStatus_notAvailable() {
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        when(mPm.queryIntentActivities(captor.capture(), any())).thenReturn(ImmutableList.of());
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void updateState() {
+        Preference preference = new Preference(mContext);
+        mController.updateState(preference);
+
+        assertThat(preference.getIntent().getPackage()).isEqualTo(mPkg);
+        assertThat(preference.getIntent().getAction()).isEqualTo(Intent.ACTION_MAIN);
+        assertThat(preference.getIntent().getCategories()).contains(
+                NotificationListenerService.INTENT_CATEGORY_SETTINGS_HOME);
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java b/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java
index eba45d4..88cf775 100644
--- a/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java
+++ b/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java
@@ -21,7 +21,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -29,6 +28,7 @@
 import android.os.Looper;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
 import androidx.lifecycle.LifecycleOwner;
@@ -42,6 +42,7 @@
 import com.android.settings.testutils.ResourcesUtils;
 import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,58 +51,63 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.List;
 
 
 @RunWith(AndroidJUnit4.class)
 public class NetworkProviderCallsSmsControllerTest {
 
-    private static final int SUB_ID_1 = 1;
-    private static final int SUB_ID_2 = 2;
+    private static final String SUB_ID_1 = "1";
+    private static final String SUB_ID_2 = "2";
+    private static final String INVALID_SUB_ID = "-1";
     private static final String KEY_PREFERENCE_CALLS_SMS = "calls_and_sms";
     private static final String DISPLAY_NAME_1 = "Sub 1";
     private static final String DISPLAY_NAME_2 = "Sub 2";
+    private static final String SUB_MCC_1 = "123";
+    private static final String SUB_MNC_1 = "456";
+    private static final String SUB_MCC_2 = "223";
+    private static final String SUB_MNC_2 = "456";
+    private static final String SUB_COUNTRY_ISO_1 = "Sub 1";
+    private static final String SUB_COUNTRY_ISO_2 = "Sub 2";
 
     @Mock
-    private SubscriptionManager mSubscriptionManager;
+    private SubscriptionInfoEntity mSubInfo1;
     @Mock
-    private SubscriptionInfo mSubscriptionInfo1;
-    @Mock
-    private SubscriptionInfo mSubscriptionInfo2;
+    private SubscriptionInfoEntity mSubInfo2;
     @Mock
     private Lifecycle mLifecycle;
     @Mock
     private LifecycleOwner mLifecycleOwner;
-    private LifecycleRegistry mLifecycleRegistry;
 
+    private LifecycleRegistry mLifecycleRegistry;
     private MockNetworkProviderCallsSmsController mController;
     private PreferenceManager mPreferenceManager;
     private PreferenceScreen mPreferenceScreen;
     private RestrictedPreference mPreference;
-
     private Context mContext;
+    private List<SubscriptionInfoEntity> mSubscriptionInfoEntityList = new ArrayList<>();
 
     /**
-     * Mock the NetworkProviderCallsSmsController that allows allows one to set a default voice
+     * Mock the NetworkProviderCallsSmsController that allows one to set a default voice
      * and SMS subscription ID.
      */
     private class MockNetworkProviderCallsSmsController extends
             com.android.settings.network.NetworkProviderCallsSmsController {
-        public MockNetworkProviderCallsSmsController(Context context, Lifecycle lifecycle) {
-            super(context, lifecycle);
+        public MockNetworkProviderCallsSmsController(Context context, Lifecycle lifecycle,
+                LifecycleOwner lifecycleOwner) {
+            super(context, lifecycle, lifecycleOwner);
         }
 
-        private int mDefaultVoiceSubscriptionId;
-        private int mDefaultSmsSubscriptionId;
+        private List<SubscriptionInfoEntity> mSubscriptionInfoEntity;
         private boolean mIsInService;
-        @Override
-        protected int getDefaultVoiceSubscriptionId() {
-            return mDefaultVoiceSubscriptionId;
-        }
 
         @Override
-        protected int getDefaultSmsSubscriptionId() {
-            return mDefaultSmsSubscriptionId;
+        protected List<SubscriptionInfoEntity> getSubscriptionInfoList() {
+            return mSubscriptionInfoEntity;
+        }
+
+        public void setSubscriptionInfoList(List<SubscriptionInfoEntity> list) {
+            mSubscriptionInfoEntity = list;
         }
 
         @Override
@@ -109,14 +115,6 @@
             return mIsInService;
         }
 
-        public void setDefaultVoiceSubscriptionId(int subscriptionId) {
-            mDefaultVoiceSubscriptionId = subscriptionId;
-        }
-
-        public void setDefaultSmsSubscriptionId(int subscriptionId) {
-            mDefaultSmsSubscriptionId = subscriptionId;
-        }
-
         public void setInService(boolean inService) {
             mIsInService = inService;
         }
@@ -126,7 +124,6 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = spy(ApplicationProvider.getApplicationContext());
-        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
 
         if (Looper.myLooper() == null) {
             Looper.prepare();
@@ -136,7 +133,8 @@
         mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
         mPreference = new RestrictedPreference(mContext);
         mPreference.setKey(KEY_PREFERENCE_CALLS_SMS);
-        mController = new MockNetworkProviderCallsSmsController(mContext, mLifecycle);
+        mController = new MockNetworkProviderCallsSmsController(mContext, mLifecycle,
+                mLifecycleOwner);
         mController.setInService(true);
         mLifecycleRegistry = new LifecycleRegistry(mLifecycleOwner);
         when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry);
@@ -149,13 +147,6 @@
         mLifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME);
     }
 
-    private void setupSubscriptionInfoList(int subId, String displayName,
-                                           SubscriptionInfo subscriptionInfo) {
-        when(subscriptionInfo.getSubscriptionId()).thenReturn(subId);
-        doReturn(subscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(subId);
-        when(subscriptionInfo.getDisplayName()).thenReturn(displayName);
-    }
-
     private String setSummaryResId(String resName) {
         return ResourcesUtils.getResourcesString(mContext, resName);
     }
@@ -163,23 +154,36 @@
     @Test
     @UiThreadTest
     public void getSummary_noSim_returnNoSim() {
-        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(new ArrayList<>());
-        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(new ArrayList<>());
+        mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
         displayPreferenceWithLifecycle();
 
         assertTrue(TextUtils.equals(mController.getSummary(),
                 setSummaryResId("calls_sms_no_sim")));
     }
 
+    private SubscriptionInfoEntity setupSubscriptionInfoEntity(String subId, int slotId,
+            int carrierId, String displayName, String mcc, String mnc, String countryIso,
+            int cardId, boolean isValid, boolean isActive, boolean isAvailable,
+            boolean isDefaultCall, boolean isDefaultSms) {
+        return new SubscriptionInfoEntity(subId, slotId, carrierId,
+                displayName, displayName, 0, mcc, mnc, countryIso, false, cardId,
+                TelephonyManager.DEFAULT_PORT_INDEX, false, null,
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, displayName, false,
+                "1234567890", true, "default", false, isValid,
+                true, isActive, isAvailable, isDefaultCall,
+                isDefaultSms, false, false);
+    }
+
     @Test
     @UiThreadTest
     public void getSummary_invalidSubId_returnUnavailable() {
-        setupSubscriptionInfoList(SubscriptionManager.INVALID_SUBSCRIPTION_ID, DISPLAY_NAME_1,
-                mSubscriptionInfo1);
-        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1));
-        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1));
+
+        mSubInfo1 = setupSubscriptionInfoEntity(INVALID_SUB_ID,
+                SubscriptionManager.INVALID_SIM_SLOT_INDEX, TelephonyManager.UNKNOWN_CARRIER_ID,
+                DISPLAY_NAME_1, SUB_MCC_1, SUB_MNC_1, SUB_COUNTRY_ISO_1,
+                TelephonyManager.UNINITIALIZED_CARD_ID, false, true, true, false, false);
+        mSubscriptionInfoEntityList.add(mSubInfo1);
+        mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
         displayPreferenceWithLifecycle();
 
         final StringBuilder summary = new StringBuilder();
@@ -194,13 +198,16 @@
     @Test
     @UiThreadTest
     public void getSummary_oneIsInvalidSubIdTwoIsValidSubId_returnOneIsUnavailable() {
-        setupSubscriptionInfoList(SubscriptionManager.INVALID_SUBSCRIPTION_ID, DISPLAY_NAME_1,
-                mSubscriptionInfo1);
-        setupSubscriptionInfoList(SUB_ID_2, DISPLAY_NAME_2, mSubscriptionInfo2);
-        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
-        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
+
+        mSubInfo1 = setupSubscriptionInfoEntity(INVALID_SUB_ID,
+                SubscriptionManager.INVALID_SIM_SLOT_INDEX, TelephonyManager.UNKNOWN_CARRIER_ID,
+                DISPLAY_NAME_1, SUB_MCC_1, SUB_MNC_1, SUB_COUNTRY_ISO_1,
+                TelephonyManager.UNINITIALIZED_CARD_ID, false, true, true, false, false);
+        mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
+                SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true, false, false);
+        mSubscriptionInfoEntityList.add(mSubInfo1);
+        mSubscriptionInfoEntityList.add(mSubInfo2);
+        mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
         displayPreferenceWithLifecycle();
 
         final StringBuilder summary = new StringBuilder();
@@ -214,16 +221,14 @@
         assertTrue(TextUtils.equals(mController.getSummary(), summary));
     }
 
-
-
     @Test
     @UiThreadTest
     public void getSummary_oneSubscription_returnDisplayName() {
-        setupSubscriptionInfoList(SUB_ID_1, DISPLAY_NAME_1, mSubscriptionInfo1);
-        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1));
-        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1));
+
+        mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
+                SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true, false, false);
+        mSubscriptionInfoEntityList.add(mSubInfo1);
+        mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
         displayPreferenceWithLifecycle();
 
         assertThat(mPreference.getSummary()).isEqualTo(DISPLAY_NAME_1);
@@ -232,12 +237,14 @@
     @Test
     @UiThreadTest
     public void getSummary_allSubscriptionsHaveNoPreferredStatus_returnDisplayName() {
-        setupSubscriptionInfoList(SUB_ID_1, DISPLAY_NAME_1, mSubscriptionInfo1);
-        setupSubscriptionInfoList(SUB_ID_2, DISPLAY_NAME_2, mSubscriptionInfo2);
-        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
-        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
+
+        mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
+                SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true, false, false);
+        mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
+                SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true, false, false);
+        mSubscriptionInfoEntityList.add(mSubInfo1);
+        mSubscriptionInfoEntityList.add(mSubInfo2);
+        mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
         displayPreferenceWithLifecycle();
 
         final StringBuilder summary = new StringBuilder();
@@ -250,15 +257,13 @@
     @UiThreadTest
     public void getSummary_oneSubscriptionsIsCallPreferredTwoIsSmsPreferred_returnStatus() {
 
-        mController.setDefaultVoiceSubscriptionId(SUB_ID_1);
-        mController.setDefaultSmsSubscriptionId(SUB_ID_2);
-
-        setupSubscriptionInfoList(SUB_ID_1, DISPLAY_NAME_1, mSubscriptionInfo1);
-        setupSubscriptionInfoList(SUB_ID_2, DISPLAY_NAME_2, mSubscriptionInfo2);
-        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
-        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
+        mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
+                SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true, true, false);
+        mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
+                SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true, false, true);
+        mSubscriptionInfoEntityList.add(mSubInfo1);
+        mSubscriptionInfoEntityList.add(mSubInfo2);
+        mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
         displayPreferenceWithLifecycle();
 
         final StringBuilder summary = new StringBuilder();
@@ -279,15 +284,13 @@
     @UiThreadTest
     public void getSummary_oneSubscriptionsIsSmsPreferredTwoIsCallPreferred_returnStatus() {
 
-        mController.setDefaultVoiceSubscriptionId(SUB_ID_2);
-        mController.setDefaultSmsSubscriptionId(SUB_ID_1);
-
-        setupSubscriptionInfoList(SUB_ID_1, DISPLAY_NAME_1, mSubscriptionInfo1);
-        setupSubscriptionInfoList(SUB_ID_2, DISPLAY_NAME_2, mSubscriptionInfo2);
-        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
-        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
+        mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
+                SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true, false, true);
+        mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
+                SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true, true, false);
+        mSubscriptionInfoEntityList.add(mSubInfo1);
+        mSubscriptionInfoEntityList.add(mSubInfo2);
+        mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
         displayPreferenceWithLifecycle();
 
         final StringBuilder summary = new StringBuilder();
@@ -308,15 +311,13 @@
     @UiThreadTest
     public void getSummary_oneSubscriptionsIsSmsPreferredAndIsCallPreferred_returnStatus() {
 
-        mController.setDefaultVoiceSubscriptionId(SUB_ID_1);
-        mController.setDefaultSmsSubscriptionId(SUB_ID_1);
-
-        setupSubscriptionInfoList(SUB_ID_1, DISPLAY_NAME_1, mSubscriptionInfo1);
-        setupSubscriptionInfoList(SUB_ID_2, DISPLAY_NAME_2, mSubscriptionInfo2);
-        when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
-        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
-                Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
+        mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
+                SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true, true, true);
+        mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
+                SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true, false, false);
+        mSubscriptionInfoEntityList.add(mSubInfo1);
+        mSubscriptionInfoEntityList.add(mSubInfo2);
+        mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
         displayPreferenceWithLifecycle();
 
         final StringBuilder summary = new StringBuilder();
diff --git a/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
index 4282b3e6..9a2c611 100644
--- a/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
+++ b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
@@ -638,6 +638,106 @@
         assertThat(testExcludedLogicalSlotIndex).isEqualTo(verifyExcludedLogicalSlotIndex);
     }
 
+    @Test
+    public void isRemovableSimEnabled_noPsim_returnsFalse() {
+        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
+                oneSimSlotDeviceActiveEsim());
+
+        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
+
+        assertThat(testSlot).isFalse();
+    }
+
+    @Test
+    public void isRemovableSimEnabled_activeRemovableEsimAndInactivePsim_returnsFalse() {
+        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
+                twoSimSlotsDeviceActiveRemovableEsimInactivePsim());
+
+        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
+
+        assertThat(testSlot).isFalse();
+    }
+
+    @Test
+    public void isRemovableSimEnabled_activeRemovableEsimAndActivePsim_returnsTrue() {
+        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
+                twoSimSlotsDeviceActivePsimActiveRemovableEsim());
+
+        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
+
+        assertThat(testSlot).isTrue();
+    }
+
+    @Test
+    public void isRemovableSimEnabled_inactiveRemovableEsimAndActivePsim_returnsTrue() {
+        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
+                twoSimSlotsDeviceInactiveRemovableEsimActivePsim());
+
+        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
+
+        assertThat(testSlot).isTrue();
+    }
+
+    @Test
+    public void isRemovableSimEnabled_twoActiveRemovableEsimsAndInactivePsim_returnsFalse() {
+        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
+                twoSimSlotsDeviceTwoActiveRemovableEsimsInactivePsim());
+
+        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
+
+        assertThat(testSlot).isFalse();
+    }
+
+    @Test
+    public void isRemovableSimEnabled_oneActiveOneInactiveRemovableEsimActivePsim_returnsTrue() {
+        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
+                twoSimSlotsDeviceOneActiveOneInactiveRemovableEsimsActivePsim());
+
+        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
+
+        assertThat(testSlot).isTrue();
+    }
+
+    @Test
+    public void isRemovableSimEnabled_activePsim_returnsTrue() {
+        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
+                oneSimSlotDeviceActivePsim());
+
+        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
+
+        assertThat(testSlot).isTrue();
+    }
+
+    @Test
+    public void isRemovableSimEnabled_inactivePsim_returnsFalse() {
+        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
+                oneSimSlotDeviceinactivePsim());
+
+        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
+
+        assertThat(testSlot).isFalse();
+    }
+
+    @Test
+    public void isRemovableSimEnabled_activeEsimAndActivePsim_returnsTrue() {
+        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
+                twoSimSlotsDeviceActivePsimActiveEsim());
+
+        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
+
+        assertThat(testSlot).isTrue();
+    }
+
+    @Test
+    public void isRemovableSimEnabled_activeEsimAndInactivePsim_returnsFalse() {
+        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
+                twoSimSlotsDeviceInactivePsimActiveEsim());
+
+        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
+
+        assertThat(testSlot).isFalse();
+    }
+
     private void compareTwoUiccSlotMappings(Collection<UiccSlotMapping> testUiccSlotMappings,
             Collection<UiccSlotMapping> verifyUiccSlotMappings) {
         assertThat(testUiccSlotMappings.size()).isEqualTo(verifyUiccSlotMappings.size());
@@ -792,6 +892,10 @@
         return new UiccSlotInfo[]{createUiccSlotInfo(true, false, 1, true)};
     }
 
+    private UiccSlotInfo[] oneSimSlotDeviceinactivePsim() {
+        return new UiccSlotInfo[]{createUiccSlotInfo(false, true, -1, false)};
+    }
+
     private UiccSlotInfo[] twoSimSlotsDeviceActivePsimActiveEsim() {
         return new UiccSlotInfo[]{
                 createUiccSlotInfo(false, true, 0, true),
@@ -810,6 +914,30 @@
                 createUiccSlotInfo(true, true, 1, true)};
     }
 
+    private UiccSlotInfo[] twoSimSlotsDeviceActiveRemovableEsimInactivePsim() {
+        return new UiccSlotInfo[]{
+                createUiccSlotInfo(true, true, 0, true),
+                createUiccSlotInfo(false, true, -1, false)};
+    }
+
+    private UiccSlotInfo[] twoSimSlotsDeviceInactiveRemovableEsimActivePsim() {
+        return new UiccSlotInfo[]{
+                createUiccSlotInfo(true, true, -1, false),
+                createUiccSlotInfo(false, true, 0, true)};
+    }
+
+    private UiccSlotInfo[] twoSimSlotsDeviceTwoActiveRemovableEsimsInactivePsim() {
+        return new UiccSlotInfo[]{
+                createUiccSlotInfoForRemovableEsimMep(0, true, 1, true),
+                createUiccSlotInfo(false, true, -1, false)};
+    }
+
+    private UiccSlotInfo[] twoSimSlotsDeviceOneActiveOneInactiveRemovableEsimsActivePsim() {
+        return new UiccSlotInfo[]{
+                createUiccSlotInfoForRemovableEsimMep(1, true, -1, false),
+                createUiccSlotInfo(false, true, 0, true)};
+    }
+
     private UiccSlotInfo[] twoSimSlotsDeviceActiveEsimActivePsim() {
         return new UiccSlotInfo[]{
                 createUiccSlotInfo(true, false, 0, true),
@@ -872,4 +1000,20 @@
                                 logicalSlotIdx2 /* logicalSlotIdx */,
                                 isActiveEsim2 /* isActive */)));
     }
+
+    private UiccSlotInfo createUiccSlotInfoForRemovableEsimMep(int logicalSlotIdx1,
+            boolean isActiveEsim1, int logicalSlotIdx2, boolean isActiveEsim2) {
+        return new UiccSlotInfo(
+                true, /* isEuicc */
+                "123", /* cardId */
+                CARD_STATE_INFO_PRESENT, /* cardStateInfo */
+                true, /* isExtendApduSupported */
+                true, /* isRemovable */
+                Arrays.asList(
+                        new UiccPortInfo("" /* iccId */, 0 /* portIdx */,
+                                logicalSlotIdx1 /* logicalSlotIdx */, isActiveEsim1 /* isActive */),
+                        new UiccPortInfo("" /* iccId */, 1 /* portIdx */,
+                                logicalSlotIdx2 /* logicalSlotIdx */,
+                                isActiveEsim2 /* isActive */)));
+    }
 }
diff --git a/tests/unit/src/com/android/settings/wifi/WifiUtilsTest.java b/tests/unit/src/com/android/settings/wifi/WifiUtilsTest.java
index 1a5e852..2826310 100644
--- a/tests/unit/src/com/android/settings/wifi/WifiUtilsTest.java
+++ b/tests/unit/src/com/android/settings/wifi/WifiUtilsTest.java
@@ -21,19 +21,53 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.TetheringManager;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
 
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.settings.R;
 import com.android.wifitrackerlib.WifiEntry;
 
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 @RunWith(AndroidJUnit4.class)
 public class WifiUtilsTest {
 
+    static final String[] WIFI_REGEXS = {"wifi_regexs"};
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Spy
+    Context mContext = ApplicationProvider.getApplicationContext();
+    @Mock
+    Resources mResources;
+    @Mock
+    WifiManager mWifiManager;
+    @Mock
+    TetheringManager mTetheringManager;
+
+    @Before
+    public void setUp() {
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getBoolean(R.bool.config_show_wifi_hotspot_settings)).thenReturn(true);
+        when(mContext.getSystemService(WifiManager.class)).thenReturn(mWifiManager);
+        when(mContext.getSystemService(TetheringManager.class)).thenReturn(mTetheringManager);
+        when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(WIFI_REGEXS);
+    }
+
     @Test
     public void testSSID() {
         assertThat(WifiUtils.isSSIDTooLong("123")).isFalse();
@@ -108,4 +142,53 @@
         WifiConfiguration config = WifiUtils.getWifiConfig(null /* wifiEntry */,
                 null /* scanResult */);
     }
+
+    @Test
+    public void checkShowWifiHotspot_allReady_returnTrue() {
+        assertThat(WifiUtils.checkShowWifiHotspot(mContext)).isTrue();
+    }
+
+    @Test
+    public void checkShowWifiHotspot_contextIsNull_returnFalse() {
+        assertThat(WifiUtils.checkShowWifiHotspot(null)).isFalse();
+    }
+
+    @Test
+    public void checkShowWifiHotspot_configIsNotShow_returnFalse() {
+        when(mResources.getBoolean(R.bool.config_show_wifi_hotspot_settings)).thenReturn(false);
+
+        assertThat(WifiUtils.checkShowWifiHotspot(mContext)).isFalse();
+    }
+
+    @Test
+    public void checkShowWifiHotspot_wifiManagerIsNull_returnFalse() {
+        when(mContext.getSystemService(WifiManager.class)).thenReturn(null);
+
+        assertThat(WifiUtils.checkShowWifiHotspot(mContext)).isFalse();
+    }
+
+    @Test
+    public void checkShowWifiHotspot_tetheringManagerIsNull_returnFalse() {
+        when(mContext.getSystemService(TetheringManager.class)).thenReturn(null);
+
+        assertThat(WifiUtils.checkShowWifiHotspot(mContext)).isFalse();
+    }
+
+    @Test
+    public void checkShowWifiHotspot_wifiRegexsIsEmpty_returnFalse() {
+        when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(null);
+
+        assertThat(WifiUtils.checkShowWifiHotspot(mContext)).isFalse();
+    }
+
+    @Test
+    public void canShowWifiHotspot_cachedIsReady_returnCached() {
+        WifiUtils.setCanShowWifiHotspotCached(true);
+
+        assertThat(WifiUtils.canShowWifiHotspot(null)).isTrue();
+
+        WifiUtils.setCanShowWifiHotspotCached(false);
+
+        assertThat(WifiUtils.canShowWifiHotspot(null)).isFalse();
+    }
 }