Merge "[Activity embedding] Fix unexpected finished 2nd layer problem"
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 2689f65..fa26dd3 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -983,6 +983,20 @@
         <item>90</item>
     </string-array>
 
+    <!-- Options for screensaver "When to start" for devices that do not support screensavers
+         while on battery -->
+    <string-array name="when_to_start_screensaver_entries_no_battery" translatable="false">
+        <item>@string/screensaver_settings_summary_sleep</item>
+        <item>@string/screensaver_settings_summary_dock_and_charging</item>
+    </string-array>
+
+    <!-- Values for screensaver "When to start" for devices that do not support screensavers
+         while on battery -->
+    <string-array name="when_to_start_screensaver_values_no_battery" translatable="false">
+        <item>while_charging_only</item>
+        <item>while_docked_only</item>
+    </string-array>
+
     <string-array name="when_to_start_screensaver_entries" translatable="false">
         <item>@string/screensaver_settings_summary_sleep</item>
         <item>@string/screensaver_settings_summary_dock</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a92a897..5eae3b4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2605,6 +2605,8 @@
     <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] -->
     <string name="screensaver_settings_summary_either_long">While charging or docked</string>
+    <!-- Display settings screen, summary fragment for screen saver options, actived while docked and charging [CHAR LIMIT=35] -->
+    <string name="screensaver_settings_summary_dock_and_charging">While docked and charging</string>
     <!-- Display settings screen, summary fragment for screen saver options, activated when asleep and charging [CHAR LIMIT=35] -->
     <string name="screensaver_settings_summary_sleep">While charging</string>
     <!-- Display settings screen, summary fragment for screen saver options, activated when docked [CHAR LIMIT=35] -->
diff --git a/src/com/android/settings/SettingsApplication.java b/src/com/android/settings/SettingsApplication.java
index e5e6fcb..9c37d55 100644
--- a/src/com/android/settings/SettingsApplication.java
+++ b/src/com/android/settings/SettingsApplication.java
@@ -61,7 +61,7 @@
      * Override this function to set different spa environment for different Settings app.
      */
     protected void setSpaEnvironment() {
-        SpaEnvironmentFactory.INSTANCE.reset(new SettingsSpaEnvironment());
+        SpaEnvironmentFactory.INSTANCE.reset(new SettingsSpaEnvironment(this));
     }
 
     public void setHomeActivity(SettingsHomepageActivity homeActivity) {
diff --git a/src/com/android/settings/dream/DreamSettings.java b/src/com/android/settings/dream/DreamSettings.java
index ae4610a..80b321d 100644
--- a/src/com/android/settings/dream/DreamSettings.java
+++ b/src/com/android/settings/dream/DreamSettings.java
@@ -90,12 +90,14 @@
         }
     }
 
-    static int getDreamSettingDescriptionResId(@WhenToDream int dreamSetting) {
+    static int getDreamSettingDescriptionResId(@WhenToDream int dreamSetting,
+            boolean enabledOnBattery) {
         switch (dreamSetting) {
             case WHILE_CHARGING:
                 return R.string.screensaver_settings_summary_sleep;
             case WHILE_DOCKED:
-                return R.string.screensaver_settings_summary_dock;
+                return enabledOnBattery ? R.string.screensaver_settings_summary_dock
+                        : R.string.screensaver_settings_summary_dock_and_charging;
             case EITHER:
                 return R.string.screensaver_settings_summary_either_long;
             case NEVER:
diff --git a/src/com/android/settings/dream/WhenToDreamPicker.java b/src/com/android/settings/dream/WhenToDreamPicker.java
index 1c5e25e..13cdadf 100644
--- a/src/com/android/settings/dream/WhenToDreamPicker.java
+++ b/src/com/android/settings/dream/WhenToDreamPicker.java
@@ -32,12 +32,15 @@
 
     private static final String TAG = "WhenToDreamPicker";
     private DreamBackend mBackend;
+    private boolean mDreamsSupportedOnBattery;
 
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
 
         mBackend = DreamBackend.getInstance(context);
+        mDreamsSupportedOnBattery = getResources().getBoolean(
+                com.android.internal.R.bool.config_dreamsEnabledOnBattery);
     }
 
     @Override
@@ -69,11 +72,17 @@
     }
 
     private String[] entries() {
-        return getResources().getStringArray(R.array.when_to_start_screensaver_entries);
+        if (mDreamsSupportedOnBattery) {
+            return getResources().getStringArray(R.array.when_to_start_screensaver_entries);
+        }
+        return getResources().getStringArray(R.array.when_to_start_screensaver_entries_no_battery);
     }
 
     private String[] keys() {
-        return getResources().getStringArray(R.array.when_to_start_screensaver_values);
+        if (mDreamsSupportedOnBattery) {
+            return getResources().getStringArray(R.array.when_to_start_screensaver_values);
+        }
+        return getResources().getStringArray(R.array.when_to_start_screensaver_values_no_battery);
     }
 
     @Override
diff --git a/src/com/android/settings/dream/WhenToDreamPreferenceController.java b/src/com/android/settings/dream/WhenToDreamPreferenceController.java
index 02ae6a7..c3bae00 100644
--- a/src/com/android/settings/dream/WhenToDreamPreferenceController.java
+++ b/src/com/android/settings/dream/WhenToDreamPreferenceController.java
@@ -33,19 +33,24 @@
     private static final String WHEN_TO_START = "when_to_start";
     private final DreamBackend mBackend;
     private final boolean mDreamsDisabledByAmbientModeSuppression;
+    private final boolean mDreamsEnabledOnBattery;
 
     WhenToDreamPreferenceController(Context context) {
         this(context, context.getResources().getBoolean(
-                com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig));
+                com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig),
+                context.getResources().getBoolean(
+                        com.android.internal.R.bool.config_dreamsEnabledOnBattery));
     }
 
     @VisibleForTesting
     WhenToDreamPreferenceController(Context context,
-            boolean dreamsDisabledByAmbientModeSuppression) {
+            boolean dreamsDisabledByAmbientModeSuppression,
+            boolean dreamsEnabledOnBattery) {
         super(context);
 
         mBackend = DreamBackend.getInstance(context);
         mDreamsDisabledByAmbientModeSuppression = dreamsDisabledByAmbientModeSuppression;
+        mDreamsEnabledOnBattery = dreamsEnabledOnBattery;
     }
 
     @Override
@@ -57,7 +62,7 @@
             preference.setSummary(R.string.screensaver_settings_when_to_dream_bedtime);
         } else {
             final int resId = DreamSettings.getDreamSettingDescriptionResId(
-                    mBackend.getWhenToDreamSetting());
+                    mBackend.getWhenToDreamSetting(), mDreamsEnabledOnBattery);
             preference.setSummary(resId);
         }
     }
diff --git a/src/com/android/settings/network/InternetPreferenceController.java b/src/com/android/settings/network/InternetPreferenceController.java
index e5a8690..8589807 100644
--- a/src/com/android/settings/network/InternetPreferenceController.java
+++ b/src/com/android/settings/network/InternetPreferenceController.java
@@ -101,7 +101,7 @@
         mInternetUpdater = new InternetUpdater(context, lifecycle, this);
         mInternetType = mInternetUpdater.getInternetType();
         mLifecycleOwner = lifecycleOwner;
-        mMobileNetworkRepository = new MobileNetworkRepository(context, this);
+        mMobileNetworkRepository = MobileNetworkRepository.create(context, this);
         lifecycle.addObserver(this);
     }
 
@@ -163,7 +163,6 @@
     /** @OnLifecycleEvent(ON_PAUSE) */
     @OnLifecycleEvent(ON_PAUSE)
     public void onPause() {
-        mMobileNetworkRepository.removeRegister();
         mSummaryHelper.register(false);
     }
 
@@ -203,22 +202,38 @@
     @VisibleForTesting
     void updateCellularSummary() {
         CharSequence summary = null;
-        for (SubscriptionInfoEntity subInfo : mSubInfoEntityList) {
-            if (subInfo.isSubscriptionVisible && subInfo.isActiveDataSubscriptionId) {
-                summary = subInfo.uniqueName;
-                break;
-            } else if (subInfo.isDefaultDataSubscription) {
-                summary = mContext.getString(
-                        R.string.mobile_data_temp_using, subInfo.uniqueName);
+        SubscriptionInfoEntity activeSubInfo = null;
+        SubscriptionInfoEntity defaultSubInfo = null;
+
+        for (SubscriptionInfoEntity subInfo : getSubscriptionInfoList()) {
+            if (subInfo.isActiveDataSubscriptionId) {
+                activeSubInfo = subInfo;
+            }
+            if (subInfo.isDefaultDataSubscription) {
+                defaultSubInfo = subInfo;
             }
         }
-
-        if (summary == null) {
+        if (activeSubInfo == null) {
             return;
         }
+        activeSubInfo = activeSubInfo.isSubscriptionVisible ? activeSubInfo : defaultSubInfo;
+
+        if (activeSubInfo.equals(defaultSubInfo)) {
+            // DDS is active
+            summary = activeSubInfo.uniqueName;
+        } else {
+            summary = mContext.getString(
+                    R.string.mobile_data_temp_using, activeSubInfo.uniqueName);
+        }
+
         mPreference.setSummary(summary);
     }
 
+    @VisibleForTesting
+    protected List<SubscriptionInfoEntity> getSubscriptionInfoList() {
+        return mSubInfoEntityList;
+    }
+
     @Override
     public void onAvailableSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList) {
         if ((mSubInfoEntityList != null &&
diff --git a/src/com/android/settings/network/MobileNetworkRepository.java b/src/com/android/settings/network/MobileNetworkRepository.java
index 3b7a7ba..61ad25d 100644
--- a/src/com/android/settings/network/MobileNetworkRepository.java
+++ b/src/com/android/settings/network/MobileNetworkRepository.java
@@ -93,10 +93,15 @@
     private boolean mIsRemovable = false;
     private boolean mIsActive = false;
 
-    MobileNetworkRepository(Context context, MobileNetworkCallback mobileNetworkCallback) {
+    public static MobileNetworkRepository create(Context context,
+            MobileNetworkCallback mobileNetworkCallback) {
+        return new MobileNetworkRepository(context, mobileNetworkCallback);
+    }
+
+    private MobileNetworkRepository(Context context, MobileNetworkCallback mobileNetworkCallback) {
         mContext = context;
         mCallback = mobileNetworkCallback;
-        mMobileNetworkDatabase = MobileNetworkDatabase.createDatabase(context);
+        mMobileNetworkDatabase = MobileNetworkDatabase.getInstance(context);
         mSubscriptionInfoDao = mMobileNetworkDatabase.mSubscriptionInfoDao();
         mUiccInfoDao = mMobileNetworkDatabase.mUiccInfoDao();
         mMobileNetworkInfoDao = mMobileNetworkDatabase.mMobileNetworkInfoDao();
@@ -194,6 +199,10 @@
         return mMobileNetworkInfoEntityList;
     }
 
+    public SubscriptionInfoEntity getSubInfoById(String subId) {
+        return mSubscriptionInfoDao.querySubInfoById(subId);
+    }
+
     public int getSubInfosCount() {
         return mSubscriptionInfoDao.count();
     }
@@ -439,7 +448,7 @@
      * Callback for clients to get the latest info changes if the framework or content observers.
      * updates the relevant info.
      */
-    interface MobileNetworkCallback {
+    public interface MobileNetworkCallback {
         void onAvailableSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList);
 
         void onActiveSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList);
diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.java b/src/com/android/settings/network/MobileNetworkSummaryController.java
index 361a200..ab74988 100644
--- a/src/com/android/settings/network/MobileNetworkSummaryController.java
+++ b/src/com/android/settings/network/MobileNetworkSummaryController.java
@@ -87,7 +87,7 @@
         mMetricsFeatureProvider = FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
         mUserManager = context.getSystemService(UserManager.class);
         mLifecycleOwner = lifecycleOwner;
-        mMobileNetworkRepository = new MobileNetworkRepository(context, this);
+        mMobileNetworkRepository = MobileNetworkRepository.create(context, this);
         if (lifecycle != null) {
             lifecycle.addObserver(this);
         }
@@ -101,7 +101,6 @@
 
     @OnLifecycleEvent(ON_PAUSE)
     public void onPause() {
-        mMobileNetworkRepository.removeRegister();
     }
 
     @Override
diff --git a/src/com/android/settings/network/NetworkProviderCallsSmsController.java b/src/com/android/settings/network/NetworkProviderCallsSmsController.java
index cbc7f62..4abd2a2 100644
--- a/src/com/android/settings/network/NetworkProviderCallsSmsController.java
+++ b/src/com/android/settings/network/NetworkProviderCallsSmsController.java
@@ -71,7 +71,7 @@
         mIsRtlMode = context.getResources().getConfiguration().getLayoutDirection()
                 == View.LAYOUT_DIRECTION_RTL;
         mLifecycleOwner = lifecycleOwner;
-        mMobileNetworkRepository = new MobileNetworkRepository(context, this);
+        mMobileNetworkRepository = MobileNetworkRepository.create(context, this);
         if (lifecycle != null) {
             lifecycle.addObserver(this);
         }
@@ -85,7 +85,6 @@
 
     @OnLifecycleEvent(Event.ON_PAUSE)
     public void onPause() {
-        mMobileNetworkRepository.removeRegister();
     }
 
     @Override
diff --git a/src/com/android/settings/network/NetworkProviderDownloadedSimListController.java b/src/com/android/settings/network/NetworkProviderDownloadedSimListController.java
index 92a1e7c..fb861d8 100644
--- a/src/com/android/settings/network/NetworkProviderDownloadedSimListController.java
+++ b/src/com/android/settings/network/NetworkProviderDownloadedSimListController.java
@@ -72,7 +72,7 @@
         mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
         mPreferences = new ArrayMap<>();
         mLifecycleOwner = lifecycleOwner;
-        mMobileNetworkRepository = new MobileNetworkRepository(context, this);
+        mMobileNetworkRepository = MobileNetworkRepository.create(context, this);
         lifecycle.addObserver(this);
     }
 
diff --git a/src/com/android/settings/network/NetworkProviderSimListController.java b/src/com/android/settings/network/NetworkProviderSimListController.java
index 9bf2101..e4ea392 100644
--- a/src/com/android/settings/network/NetworkProviderSimListController.java
+++ b/src/com/android/settings/network/NetworkProviderSimListController.java
@@ -68,7 +68,7 @@
         mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
         mPreferences = new ArrayMap<>();
         mLifecycleOwner = lifecycleOwner;
-        mMobileNetworkRepository = new MobileNetworkRepository(context, this);
+        mMobileNetworkRepository = MobileNetworkRepository.create(context, this);
         lifecycle.addObserver(this);
     }
 
diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
index 82cc705..5131dde 100644
--- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt
+++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
@@ -16,6 +16,7 @@
 
 package com.android.settings.spa
 
+import android.content.Context
 import com.android.settings.spa.app.AllAppListPageProvider
 import com.android.settings.spa.app.AppsMainPageProvider
 import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
@@ -36,7 +37,7 @@
 import com.android.settingslib.spa.framework.common.SpaEnvironment
 import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListTemplate
 
-open class SettingsSpaEnvironment : SpaEnvironment() {
+open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) {
     override val pageProviderRepository = lazy {
         val togglePermissionAppListTemplate = TogglePermissionAppListTemplate(
             allProviders = listOf(
diff --git a/src/com/android/settings/spa/app/appinfo/AppButtons.kt b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
index 5bdaf68..c088fec 100644
--- a/src/com/android/settings/spa/app/appinfo/AppButtons.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
@@ -20,18 +20,25 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.remember
+import com.android.settingslib.applications.AppUtils
+import com.android.settingslib.spa.framework.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spa.widget.button.ActionButton
 import com.android.settingslib.spa.widget.button.ActionButtons
+import com.android.settingslib.spaprivileged.model.app.isSystemModule
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
 
 @Composable
 fun AppButtons(packageInfoPresenter: PackageInfoPresenter) {
-    val appButtonsHolder = remember { AppButtonsHolder(packageInfoPresenter) }
-    appButtonsHolder.Dialogs()
-    ActionButtons(actionButtons = appButtonsHolder.rememberActionsButtons().value)
+    val presenter = remember { AppButtonsPresenter(packageInfoPresenter) }
+    if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
+    presenter.Dialogs()
+    ActionButtons(actionButtons = presenter.rememberActionsButtons().value)
 }
 
-private class AppButtonsHolder(private val packageInfoPresenter: PackageInfoPresenter) {
+private class AppButtonsPresenter(private val packageInfoPresenter: PackageInfoPresenter) {
     private val appLaunchButton = AppLaunchButton(packageInfoPresenter)
     private val appInstallButton = AppInstallButton(packageInfoPresenter)
     private val appDisableButton = AppDisableButton(packageInfoPresenter)
@@ -39,6 +46,15 @@
     private val appClearButton = AppClearButton(packageInfoPresenter)
     private val appForceStopButton = AppForceStopButton(packageInfoPresenter)
 
+    val isAvailableFlow = flow { emit(isAvailable()) }
+
+    private suspend fun isAvailable(): Boolean = withContext(Dispatchers.IO) {
+        !packageInfoPresenter.userPackageManager.isSystemModule(packageInfoPresenter.packageName) &&
+            !AppUtils.isMainlineModule(
+                packageInfoPresenter.userPackageManager, packageInfoPresenter.packageName
+            )
+    }
+
     @Composable
     fun rememberActionsButtons() = remember {
         packageInfoPresenter.flow.map { packageInfo ->
diff --git a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
new file mode 100644
index 0000000..d13d108
--- /dev/null
+++ b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
@@ -0,0 +1,132 @@
+/*
+ * 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 android.net.NetworkStats
+import android.net.NetworkTemplate
+import android.os.Process
+import android.text.format.DateUtils
+import android.text.format.Formatter
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settings.Utils
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.datausage.AppDataUsage
+import com.android.settings.datausage.DataUsageUtils
+import com.android.settingslib.net.NetworkCycleDataForUid
+import com.android.settingslib.net.NetworkCycleDataForUidLoader
+import com.android.settingslib.spa.framework.compose.collectAsStateWithLifecycle
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.app.hasFlag
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.withContext
+
+@Composable
+fun AppDataUsagePreference(app: ApplicationInfo) {
+    val context = LocalContext.current
+    val presenter = remember { AppDataUsagePresenter(context, app) }
+    if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
+
+    Preference(object : PreferenceModel {
+        override val title = stringResource(R.string.data_usage_app_summary_title)
+        override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
+            initialValue = stringResource(R.string.computing_size),
+        )
+        override val enabled = presenter.isEnabled().toState()
+        override val onClick = presenter::startActivity
+    })
+}
+
+private class AppDataUsagePresenter(
+    private val context: Context,
+    private val app: ApplicationInfo,
+) {
+    val isAvailableFlow = flow { emit(isAvailable()) }
+
+    private suspend fun isAvailable(): Boolean = withContext(Dispatchers.IO) {
+        Utils.isBandwidthControlEnabled()
+    }
+
+    fun isEnabled() = app.hasFlag(ApplicationInfo.FLAG_INSTALLED)
+
+    val summaryFlow = flow { emit(getSummary()) }
+
+    private suspend fun getSummary() = withContext(Dispatchers.IO) {
+        val appUsageData = getAppUsageData()
+        val totalBytes = appUsageData.sumOf { it.totalUsage }
+        if (totalBytes == 0L) {
+            context.getString(R.string.no_data_usage)
+        } else {
+            val startTime = appUsageData.minOfOrNull { it.startTime } ?: System.currentTimeMillis()
+            context.getString(
+                R.string.data_summary_format,
+                Formatter.formatFileSize(context, totalBytes, Formatter.FLAG_IEC_UNITS),
+                DateUtils.formatDateTime(context, startTime, DATE_FORMAT),
+            )
+        }
+    }
+
+    private suspend fun getAppUsageData(): List<NetworkCycleDataForUid> =
+        withContext(Dispatchers.IO) {
+            createLoader().loadInBackground() ?: emptyList()
+        }
+
+    private fun createLoader(): NetworkCycleDataForUidLoader =
+        NetworkCycleDataForUidLoader.builder(context).apply {
+            setRetrieveDetail(false)
+            setNetworkTemplate(getTemplate())
+            addUid(app.uid)
+            if (Process.isApplicationUid(app.uid)) {
+                // Also add in network usage for the app's SDK sandbox
+                addUid(Process.toSdkSandboxUid(app.uid))
+            }
+        }.build()
+
+    private fun getTemplate(): NetworkTemplate = when {
+        DataUsageUtils.hasReadyMobileRadio(context) -> {
+            NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE)
+                .setMeteredness(NetworkStats.METERED_YES)
+                .build()
+        }
+        DataUsageUtils.hasWifiRadio(context) -> {
+            NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build()
+        }
+        else -> NetworkTemplate.Builder(NetworkTemplate.MATCH_ETHERNET).build()
+    }
+
+    fun startActivity() {
+        AppInfoDashboardFragment.startAppInfoFragment(
+            AppDataUsage::class.java,
+            app,
+            context,
+            SettingsEnums.APPLICATIONS_INSTALLED_APP_DETAILS,
+        )
+    }
+
+    private companion object {
+        const val DATE_FORMAT = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_ABBREV_MONTH
+    }
+}
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index b4b6945..a1f3c1c 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -16,6 +16,7 @@
 
 package com.android.settings.spa.app.appinfo
 
+import android.app.settings.SettingsEnums
 import android.content.pm.ApplicationInfo
 import android.os.Bundle
 import androidx.compose.runtime.Composable
@@ -50,6 +51,8 @@
         navArgument(USER_ID) { type = NavType.IntType },
     )
 
+    const val METRICS_CATEGORY = SettingsEnums.APPLICATIONS_INSTALLED_APP_DETAILS
+
     @Composable
     override fun Page(arguments: Bundle?) {
         val packageName = arguments!!.getString(PACKAGE_NAME)!!
@@ -90,10 +93,13 @@
 
         AppButtons(packageInfoPresenter)
 
+        AppSettingsPreference(app)
+        // TODO: all_services_settings
+        // TODO: notification_settings
         AppPermissionPreference(app)
         AppStoragePreference(app)
         // TODO: instant_app_launch_supported_domain_urls
-        // TODO: data_settings
+        AppDataUsagePreference(app)
         AppTimeSpentPreference(app)
         AppBatteryPreference(app)
         AppLocalePreference(app)
diff --git a/src/com/android/settings/spa/app/appinfo/AppLaunchButton.kt b/src/com/android/settings/spa/app/appinfo/AppLaunchButton.kt
index 8f398c7..9e82f53 100644
--- a/src/com/android/settings/spa/app/appinfo/AppLaunchButton.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppLaunchButton.kt
@@ -27,10 +27,10 @@
 
 class AppLaunchButton(packageInfoPresenter: PackageInfoPresenter) {
     private val context = packageInfoPresenter.context
-    private val packageManagerAsUser = packageInfoPresenter.packageManagerAsUser
+    private val userPackageManager = packageInfoPresenter.userPackageManager
 
     fun getActionButton(packageInfo: PackageInfo): ActionButton? =
-        packageManagerAsUser.getLaunchIntentForPackage(packageInfo.packageName)?.let { intent ->
+        userPackageManager.getLaunchIntentForPackage(packageInfo.packageName)?.let { intent ->
             launchButton(intent, packageInfo.applicationInfo)
         }
 
diff --git a/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt b/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
index 936dee6..4cc24b3 100644
--- a/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
@@ -16,7 +16,6 @@
 
 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
@@ -86,7 +85,7 @@
             AppLaunchSettings::class.java,
             app,
             context,
-            SettingsEnums.APPLICATIONS_INSTALLED_APP_DETAILS,
+            AppInfoSettingsProvider.METRICS_CATEGORY,
         )
     }
 }
diff --git a/src/com/android/settings/spa/app/appinfo/AppSettingsPreference.kt b/src/com/android/settings/spa/app/appinfo/AppSettingsPreference.kt
new file mode 100644
index 0000000..babd607
--- /dev/null
+++ b/src/com/android/settings/spa/app/appinfo/AppSettingsPreference.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager.ResolveInfoFlags
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settings.overlay.FeatureFactory
+import com.android.settingslib.spa.framework.compose.collectAsStateWithLifecycle
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import com.android.settingslib.spaprivileged.model.app.userId
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@Composable
+fun AppSettingsPreference(app: ApplicationInfo) {
+    val context = LocalContext.current
+    val coroutineScope = rememberCoroutineScope()
+    val presenter = remember { AppSettingsPresenter(context, app, coroutineScope) }
+    if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
+
+    Preference(object : PreferenceModel {
+        override val title = stringResource(R.string.app_settings_link)
+        override val onClick = presenter::startActivity
+    })
+}
+
+private class AppSettingsPresenter(
+    private val context: Context,
+    private val app: ApplicationInfo,
+    private val coroutineScope: CoroutineScope,
+) {
+    private val packageManager = context.packageManager
+
+    private val intentFlow = flow {
+        emit(resolveIntent())
+    }.shareIn(coroutineScope, SharingStarted.WhileSubscribed(), 1)
+
+    val isAvailableFlow = intentFlow.map { it != null }
+
+    fun startActivity() {
+        coroutineScope.launch {
+            intentFlow.collect { intent ->
+                if (intent != null) {
+                    FeatureFactory.getFactory(context).metricsFeatureProvider
+                        .action(
+                            SettingsEnums.PAGE_UNKNOWN,
+                            SettingsEnums.ACTION_OPEN_APP_SETTING,
+                            AppInfoSettingsProvider.METRICS_CATEGORY,
+                            null,
+                            0,
+                        )
+                    context.startActivityAsUser(intent, app.userHandle)
+                }
+            }
+        }
+    }
+
+    private suspend fun resolveIntent(): Intent? = withContext(Dispatchers.IO) {
+        val intent = Intent(Intent.ACTION_APPLICATION_PREFERENCES).apply {
+            `package` = app.packageName
+        }
+        packageManager.resolveActivityAsUser(intent, ResolveInfoFlags.of(0), app.userId)
+            ?.activityInfo
+            ?.let { activityInfo ->
+                Intent(intent.action).apply {
+                    setClassName(activityInfo.packageName, activityInfo.name)
+                }
+            }
+    }
+}
diff --git a/src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt b/src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt
index 265f882..e8b1018 100644
--- a/src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt
@@ -16,7 +16,6 @@
 
 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
@@ -70,6 +69,6 @@
         AppStorageSettings::class.java,
         app,
         context,
-        SettingsEnums.APPLICATIONS_INSTALLED_APP_DETAILS,
+        AppInfoSettingsProvider.METRICS_CATEGORY,
     )
 }
diff --git a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
index 050c048..4a6f9db 100644
--- a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
+++ b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
@@ -51,7 +51,7 @@
 ) {
     private val metricsFeatureProvider = FeatureFactory.getFactory(context).metricsFeatureProvider
     val userContext by lazy { context.asUser(UserHandle.of(userId)) }
-    val packageManagerAsUser: PackageManager by lazy { userContext.packageManager }
+    val userPackageManager: PackageManager by lazy { userContext.packageManager }
     private val _flow: MutableStateFlow<PackageInfo?> = MutableStateFlow(null)
 
     val flow: StateFlow<PackageInfo?> = _flow
@@ -92,7 +92,7 @@
     fun enable() {
         logAction(SettingsEnums.ACTION_SETTINGS_ENABLE_APP)
         coroutineScope.launch(Dispatchers.IO) {
-            packageManagerAsUser.setApplicationEnabledSetting(
+            userPackageManager.setApplicationEnabledSetting(
                 packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0
             )
             notifyChange()
@@ -103,7 +103,7 @@
     fun disable() {
         logAction(SettingsEnums.ACTION_SETTINGS_DISABLE_APP)
         coroutineScope.launch(Dispatchers.IO) {
-            packageManagerAsUser.setApplicationEnabledSetting(
+            userPackageManager.setApplicationEnabledSetting(
                 packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0
             )
             notifyChange()
@@ -124,7 +124,7 @@
     fun clearInstantApp() {
         logAction(SettingsEnums.ACTION_SETTINGS_CLEAR_INSTANT_APP)
         coroutineScope.launch(Dispatchers.IO) {
-            packageManagerAsUser.deletePackageAsUser(packageName, null, 0, userId)
+            userPackageManager.deletePackageAsUser(packageName, null, 0, userId)
             notifyChange()
         }
     }
diff --git a/tests/robotests/src/com/android/settings/dream/DreamSettingsTest.java b/tests/robotests/src/com/android/settings/dream/DreamSettingsTest.java
index 4d0adb4..e9e8072 100644
--- a/tests/robotests/src/com/android/settings/dream/DreamSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/dream/DreamSettingsTest.java
@@ -38,24 +38,31 @@
 public class DreamSettingsTest {
 
     private static final List<String> KEYS = Arrays.asList(
-        DreamSettings.WHILE_CHARGING_ONLY,
-        DreamSettings.WHILE_DOCKED_ONLY,
-        DreamSettings.EITHER_CHARGING_OR_DOCKED,
-        DreamSettings.NEVER_DREAM
+            DreamSettings.WHILE_CHARGING_ONLY,
+            DreamSettings.WHILE_DOCKED_ONLY,
+            DreamSettings.EITHER_CHARGING_OR_DOCKED,
+            DreamSettings.NEVER_DREAM
     );
 
     private static final @WhenToDream int[] SETTINGS = {
-        DreamBackend.WHILE_CHARGING,
-        DreamBackend.WHILE_DOCKED,
-        DreamBackend.EITHER,
-        DreamBackend.NEVER,
+            DreamBackend.WHILE_CHARGING,
+            DreamBackend.WHILE_DOCKED,
+            DreamBackend.EITHER,
+            DreamBackend.NEVER,
     };
 
     private static final int[] RES_IDS = {
-        R.string.screensaver_settings_summary_sleep,
-        R.string.screensaver_settings_summary_dock,
-        R.string.screensaver_settings_summary_either_long,
-        R.string.screensaver_settings_summary_never
+            R.string.screensaver_settings_summary_sleep,
+            R.string.screensaver_settings_summary_dock,
+            R.string.screensaver_settings_summary_either_long,
+            R.string.screensaver_settings_summary_never
+    };
+
+    private static final int[] RES_IDS_NO_BATTERY = {
+            R.string.screensaver_settings_summary_sleep,
+            R.string.screensaver_settings_summary_dock_and_charging,
+            R.string.screensaver_settings_summary_either_long,
+            R.string.screensaver_settings_summary_never
     };
 
     @Test
@@ -81,11 +88,17 @@
     @Test
     public void getDreamSettingDescriptionResId() {
         for (int i = 0; i < SETTINGS.length; i++) {
-            assertThat(DreamSettings.getDreamSettingDescriptionResId(SETTINGS[i]))
+            assertThat(DreamSettings.getDreamSettingDescriptionResId(
+                    SETTINGS[i], /* enabledOnBattery= */ false))
+                    .isEqualTo(RES_IDS_NO_BATTERY[i]);
+            assertThat(DreamSettings.getDreamSettingDescriptionResId(
+                    SETTINGS[i], /* enabledOnBattery= */ true))
                     .isEqualTo(RES_IDS[i]);
         }
         // Default
-        assertThat(DreamSettings.getDreamSettingDescriptionResId(-1))
+        assertThat(DreamSettings.getDreamSettingDescriptionResId(-1, /* enabledOnBattery= */ false))
+                .isEqualTo(R.string.screensaver_settings_summary_never);
+        assertThat(DreamSettings.getDreamSettingDescriptionResId(-1, /* enabledOnBattery= */ true))
                 .isEqualTo(R.string.screensaver_settings_summary_never);
     }
 
diff --git a/tests/robotests/src/com/android/settings/dream/WhenToDreamPickerTest.java b/tests/robotests/src/com/android/settings/dream/WhenToDreamPickerTest.java
index 16f8599..91ec299 100644
--- a/tests/robotests/src/com/android/settings/dream/WhenToDreamPickerTest.java
+++ b/tests/robotests/src/com/android/settings/dream/WhenToDreamPickerTest.java
@@ -28,6 +28,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
 import com.android.settingslib.dream.DreamBackend;
 
 import org.junit.Before;
@@ -37,9 +38,11 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
 import org.robolectric.util.ReflectionHelpers;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = SettingsShadowResources.class)
 public class WhenToDreamPickerTest {
 
     private WhenToDreamPicker mPicker;
@@ -53,10 +56,15 @@
         MockitoAnnotations.initMocks(this);
         final Context context = spy(ApplicationProvider.getApplicationContext());
 
+        SettingsShadowResources.overrideResource(
+                com.android.internal.R.bool.config_dreamsEnabledOnBattery,
+                true);
+
         when(context.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         FakeFeatureFactory.setupForTest();
 
-        mPicker = new WhenToDreamPicker();
+        mPicker = spy(new WhenToDreamPicker());
+        when(mPicker.getContext()).thenReturn(context);
         mPicker.onAttach(context);
 
         ReflectionHelpers.setField(mPicker, "mBackend", mBackend);
diff --git a/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java
index 458c5c6..6e687bb 100644
--- a/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java
@@ -64,7 +64,7 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mContext = spy(ApplicationProvider.getApplicationContext());
-        mController = new WhenToDreamPreferenceController(mContext, true);
+        mController = new WhenToDreamPreferenceController(mContext, true, true);
         ReflectionHelpers.setField(mController, "mBackend", mBackend);
         when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
         when(mPowerManager.isAmbientDisplaySuppressedForTokenByApp(anyString(), anyInt()))
@@ -88,7 +88,7 @@
         final Preference mockPref = mock(Preference.class);
         when(mockPref.getContext()).thenReturn(mContext);
         when(mBackend.getWhenToDreamSetting()).thenReturn(testSetting);
-        final int expectedResId = DreamSettings.getDreamSettingDescriptionResId(testSetting);
+        final int expectedResId = DreamSettings.getDreamSettingDescriptionResId(testSetting, true);
 
         mController.updateState(mockPref);
         verify(mockPref).setSummary(expectedResId);
diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
index a6c4f67..b5735ef 100644
--- a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
+++ b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
@@ -95,7 +95,7 @@
         doReturn(mSubscriptionManager).when(mContext).getSystemService(SubscriptionManager.class);
         doReturn(mEuiccManager).when(mContext).getSystemService(EuiccManager.class);
         doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
-        mMobileNetworkRepository = new MobileNetworkRepository(mContext, mMobileNetworkCallback);
+        mMobileNetworkRepository = MobileNetworkRepository.create(mContext, mMobileNetworkCallback);
         mLifecycleOwner = () -> mLifecycle;
         mLifecycle = new Lifecycle(mLifecycleOwner);
         mMobileNetworkRepository.addRegister(mLifecycleOwner);
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
new file mode 100644
index 0000000..271c0ed
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.ApplicationInfo
+import android.content.pm.ModuleInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.testutils.delay
+import com.android.settingslib.applications.AppUtils
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+import org.junit.After
+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.doReturn
+import org.mockito.Mockito.doThrow
+import org.mockito.MockitoSession
+import org.mockito.Spy
+import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppButtonsTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private lateinit var mockSession: MockitoSession
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var packageInfoPresenter: PackageInfoPresenter
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+
+    @Before
+    fun setUp() {
+        mockSession = ExtendedMockito.mockitoSession()
+            .initMocks(this)
+            .mockStatic(AppUtils::class.java)
+            .strictness(Strictness.LENIENT)
+            .startMocking()
+        whenever(packageInfoPresenter.context).thenReturn(context)
+        whenever(packageInfoPresenter.packageName).thenReturn(PACKAGE_NAME)
+        whenever(packageInfoPresenter.userPackageManager).thenReturn(packageManager)
+        doThrow(NameNotFoundException()).`when`(packageManager).getModuleInfo(PACKAGE_NAME, 0)
+        whenever(packageManager.getPackageInfo(PACKAGE_NAME, 0)).thenReturn(PACKAGE_INFO)
+        whenever(AppUtils.isMainlineModule(packageManager, PACKAGE_NAME)).thenReturn(false)
+    }
+
+    @After
+    fun tearDown() {
+        mockSession.finishMocking()
+    }
+
+    @Test
+    fun isSystemModule_notDisplayed() {
+        doReturn(ModuleInfo()).`when`(packageManager).getModuleInfo(PACKAGE_NAME, 0)
+
+        setContent()
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun isMainlineModule_notDisplayed() {
+        whenever(AppUtils.isMainlineModule(packageManager, PACKAGE_NAME)).thenReturn(true)
+
+        setContent()
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun isNormalApp_displayed() {
+        setContent()
+
+        composeTestRule.onRoot().assertIsDisplayed()
+    }
+
+    private fun setContent() {
+        composeTestRule.setContent {
+            val scope = rememberCoroutineScope()
+            LaunchedEffect(Unit) {
+                whenever(packageInfoPresenter.flow).thenReturn(flowOf(PACKAGE_INFO).stateIn(scope))
+            }
+
+            AppButtons(packageInfoPresenter)
+        }
+
+        composeTestRule.delay()
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "package.name"
+        val PACKAGE_INFO = PackageInfo().apply {
+            applicationInfo = ApplicationInfo()
+        }
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDataUsagePreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDataUsagePreferenceTest.kt
new file mode 100644
index 0000000..22876d1
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppDataUsagePreferenceTest.kt
@@ -0,0 +1,189 @@
+/*
+ * 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 android.net.NetworkTemplate
+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.hasText
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.printToLog
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.settings.R
+import com.android.settings.Utils
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.datausage.AppDataUsage
+import com.android.settings.testutils.waitUntilExists
+import com.android.settingslib.net.NetworkCycleDataForUid
+import com.android.settingslib.net.NetworkCycleDataForUidLoader
+import org.junit.After
+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.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoSession
+import org.mockito.Spy
+import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppDataUsagePreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private lateinit var mockSession: MockitoSession
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var builder: NetworkCycleDataForUidLoader.Builder<NetworkCycleDataForUidLoader>
+
+    @Mock
+    private lateinit var loader: NetworkCycleDataForUidLoader
+
+    @Before
+    fun setUp() {
+        mockSession = mockitoSession()
+            .initMocks(this)
+            .mockStatic(Utils::class.java)
+            .mockStatic(NetworkCycleDataForUidLoader::class.java)
+            .mockStatic(NetworkTemplate::class.java)
+            .mockStatic(AppInfoDashboardFragment::class.java)
+            .strictness(Strictness.LENIENT)
+            .startMocking()
+        whenever(Utils.isBandwidthControlEnabled()).thenReturn(true)
+        whenever(NetworkCycleDataForUidLoader.builder(context)).thenReturn(builder)
+        whenever(builder.build()).thenReturn(loader)
+    }
+
+    @After
+    fun tearDown() {
+        mockSession.finishMocking()
+    }
+
+    @Test
+    fun whenBandwidthControlDisabled_notDisplayed() {
+        whenever(Utils.isBandwidthControlEnabled()).thenReturn(false)
+
+        setContent()
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun whenAppNotInstalled_disabled() {
+        val notInstalledApp = ApplicationInfo()
+
+        setContent(notInstalledApp)
+
+        composeTestRule.onNodeWithText(context.getString(R.string.data_usage_app_summary_title))
+            .assertIsDisplayed()
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun whenAppInstalled_enabled() {
+        setContent(APP)
+
+        composeTestRule.onNodeWithText(context.getString(R.string.data_usage_app_summary_title))
+            .assertIsDisplayed()
+            .assertIsEnabled()
+    }
+
+    @Test
+    fun setCorrectValuesForBuilder() {
+        setContent()
+
+        verify(builder).setRetrieveDetail(false)
+        verify(builder).addUid(UID)
+    }
+
+    @Test
+    fun whenNoDataUsage() {
+        whenever(loader.loadInBackground()).thenReturn(emptyList())
+
+        setContent()
+
+        composeTestRule.onRoot().printToLog("AAA")
+        composeTestRule.onNodeWithText(context.getString(R.string.no_data_usage))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun whenHasDataUsage() {
+        val cycleData = mock(NetworkCycleDataForUid::class.java)
+        whenever(cycleData.totalUsage).thenReturn(123)
+        whenever(cycleData.startTime).thenReturn(1666666666666)
+        whenever(loader.loadInBackground()).thenReturn(listOf(cycleData))
+
+        setContent()
+
+        composeTestRule.waitUntilExists(hasText("123 B used since Oct 25"))
+    }
+
+    @Test
+    fun whenClick_startActivity() {
+        whenever(loader.loadInBackground()).thenReturn(emptyList())
+
+        setContent()
+        composeTestRule.onRoot().performClick()
+
+        ExtendedMockito.verify {
+            AppInfoDashboardFragment.startAppInfoFragment(
+                AppDataUsage::class.java,
+                APP,
+                context,
+                SettingsEnums.APPLICATIONS_INSTALLED_APP_DETAILS,
+            )
+        }
+    }
+
+    private fun setContent(app: ApplicationInfo = APP) {
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                AppDataUsagePreference(app)
+            }
+        }
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "packageName"
+        const val UID = 123
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            uid = UID
+            flags = ApplicationInfo.FLAG_INSTALLED
+        }
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppSettingsPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppSettingsPreferenceTest.kt
new file mode 100644
index 0000000..1184ee7
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppSettingsPreferenceTest.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+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.hasText
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.printToLog
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settings.testutils.waitUntilExists
+import com.android.settingslib.applications.AppUtils
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import com.android.settingslib.spaprivileged.model.app.userId
+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.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+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 AppSettingsPreferenceTest {
+    @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
+
+    @Before
+    fun setUp() {
+        whenever(context.packageManager).thenReturn(packageManager)
+    }
+
+    private fun mockResolveActivityAsUser(resolveInfo: ResolveInfo?) {
+        whenever(
+            packageManager.resolveActivityAsUser(any(), any<ResolveInfoFlags>(), eq(APP.userId))
+        ).thenReturn(resolveInfo)
+    }
+
+    @Test
+    fun callResolveActivityAsUser_withIntent() {
+        mockResolveActivityAsUser(null)
+
+        setContent()
+
+        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(packageManager).resolveActivityAsUser(
+            intentCaptor.capture(), any<ResolveInfoFlags>(), eq(APP.userId)
+        )
+        val intent = intentCaptor.value
+        assertThat(intent.action).isEqualTo(Intent.ACTION_APPLICATION_PREFERENCES)
+        assertThat(intent.`package`).isEqualTo(PACKAGE_NAME)
+    }
+
+    @Test
+    fun noResolveInfo_notDisplayed() {
+        mockResolveActivityAsUser(null)
+
+        setContent()
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun noSettingsActivity_notDisplayed() {
+        mockResolveActivityAsUser(ResolveInfo())
+
+        setContent()
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun hasSettingsActivity_displayed() {
+        mockResolveActivityAsUser(RESOLVE_INFO)
+
+        setContent()
+
+        composeTestRule.onNodeWithText(context.getString(R.string.app_settings_link))
+            .assertIsDisplayed()
+            .assertIsEnabled()
+    }
+
+    @Test
+    fun whenClick_startActivity() {
+        mockResolveActivityAsUser(RESOLVE_INFO)
+
+        setContent()
+        composeTestRule.onRoot().performClick()
+
+        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(context).startActivityAsUser(intentCaptor.capture(), eq(APP.userHandle))
+        val intent = intentCaptor.value
+        assertThat(intent.action).isEqualTo(Intent.ACTION_APPLICATION_PREFERENCES)
+        assertThat(intent.component).isEqualTo(ComponentName(PACKAGE_NAME, ACTIVITY_NAME))
+    }
+
+    private fun setContent() {
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                AppSettingsPreference(APP)
+            }
+        }
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "packageName"
+        const val ACTIVITY_NAME = "activityName"
+        const val UID = 123
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            uid = UID
+        }
+        val RESOLVE_INFO = ResolveInfo().apply {
+            activityInfo = ActivityInfo().apply {
+                packageName = PACKAGE_NAME
+                name = ACTIVITY_NAME
+            }
+        }
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/testutils/ComposeContentTestRuleExt.kt b/tests/spa_unit/src/com/android/settings/testutils/ComposeContentTestRuleExt.kt
index f3eb529..82df9cf 100644
--- a/tests/spa_unit/src/com/android/settings/testutils/ComposeContentTestRuleExt.kt
+++ b/tests/spa_unit/src/com/android/settings/testutils/ComposeContentTestRuleExt.kt
@@ -16,6 +16,7 @@
 
 package com.android.settings.testutils
 
+import androidx.compose.ui.test.ComposeTimeoutException
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 
@@ -23,3 +24,10 @@
 fun ComposeContentTestRule.waitUntilExists(matcher: SemanticsMatcher) = waitUntil {
     onAllNodes(matcher).fetchSemanticsNodes().isNotEmpty()
 }
+
+/** Blocks until the timeout is reached. */
+fun ComposeContentTestRule.delay(timeoutMillis: Long = 1_000) = try {
+    waitUntil(timeoutMillis) { false }
+} catch (_: ComposeTimeoutException) {
+    // Expected
+}
diff --git a/tests/unit/src/com/android/settings/network/InternetPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/InternetPreferenceControllerTest.java
index 65edb3e..8beeffb 100644
--- a/tests/unit/src/com/android/settings/network/InternetPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/network/InternetPreferenceControllerTest.java
@@ -40,16 +40,20 @@
 import android.os.Looper;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
+import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.settings.testutils.ResourcesUtils;
+import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -59,22 +63,44 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @RunWith(AndroidJUnit4.class)
 public class InternetPreferenceControllerTest {
 
     private static final String TEST_SUMMARY = "test summary";
     private static final String NOT_CONNECTED = "Not connected";
+    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 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";
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock
+    private SubscriptionInfoEntity mActiveSubInfo;
+    @Mock
+    private SubscriptionInfoEntity mDefaultDataSubInfo;
+    @Mock
     private ConnectivityManager mConnectivityManager;
+    @Mock
+    private LifecycleOwner mLifecycleOwner;
+
+    private LifecycleRegistry mLifecycleRegistry;
 
     private Context mContext;
-    private InternetPreferenceController mController;
+    private MockInternetPreferenceController mController;
     private PreferenceScreen mScreen;
     private Preference mPreference;
-    private LifecycleOwner mLifecycleOwner;
+    private List<SubscriptionInfoEntity> mSubscriptionInfoEntityList = new ArrayList<>();
 
     @Before
     public void setUp() {
@@ -85,13 +111,15 @@
         final WifiManager wifiManager = mock(WifiManager.class);
         when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(wifiManager);
         when(wifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
-
-        mController = new InternetPreferenceController(mContext, mock(Lifecycle.class),
-                mLifecycleOwner);
-        mController.sIconMap.put(INTERNET_WIFI, 0);
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
+        mLifecycleRegistry = new LifecycleRegistry(mLifecycleOwner);
+        when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry);
+        mController = new MockInternetPreferenceController(mContext, mock(Lifecycle.class),
+                mLifecycleOwner);
+        mController.sIconMap.put(INTERNET_WIFI, 0);
+
         final PreferenceManager preferenceManager = new PreferenceManager(mContext);
         mScreen = preferenceManager.createPreferenceScreen(mContext);
         mPreference = new Preference(mContext);
@@ -99,12 +127,45 @@
         mScreen.addPreference(mPreference);
     }
 
+    private class MockInternetPreferenceController extends
+            com.android.settings.network.InternetPreferenceController {
+        public MockInternetPreferenceController(Context context, Lifecycle lifecycle,
+                LifecycleOwner lifecycleOwner) {
+            super(context, lifecycle, lifecycleOwner);
+        }
+
+        private List<SubscriptionInfoEntity> mSubscriptionInfoEntity;
+
+        @Override
+        protected List<SubscriptionInfoEntity> getSubscriptionInfoList() {
+            return mSubscriptionInfoEntity;
+        }
+
+        public void setSubscriptionInfoList(List<SubscriptionInfoEntity> list) {
+            mSubscriptionInfoEntity = list;
+        }
+
+    }
+
+    private SubscriptionInfoEntity setupSubscriptionInfoEntity(String subId, int slotId,
+            int carrierId, String displayName, String mcc, String mnc, String countryIso,
+            int cardId, boolean isVisible, boolean isValid, boolean isActive, boolean isAvailable,
+            boolean isDefaultData, boolean isActiveData) {
+        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, isVisible,
+                "1234567890", true, "default", false, isValid, true, isActive, isAvailable, false,
+                false, isDefaultData, false, isActiveData);
+    }
+
     @Test
     public void isAvailable_shouldBeTrue() {
         assertThat(mController.isAvailable()).isTrue();
     }
 
     @Test
+    @UiThreadTest
     public void onResume_shouldRegisterCallback() {
         mController.onResume();
 
@@ -117,6 +178,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void onPause_shouldUnregisterCallback() {
         mController.onResume();
         mController.onPause();
@@ -149,33 +211,33 @@
 
     @Test
     public void updateCellularSummary_getNullSubscriptionInfo_shouldNotCrash() {
-        final SubscriptionManager subscriptionManager = mock(SubscriptionManager.class);
-        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(subscriptionManager);
-        when(subscriptionManager.getDefaultDataSubscriptionInfo()).thenReturn(null);
-        when(subscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(null);
+        mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
 
         mController.updateCellularSummary();
     }
 
     @Test
     public void updateCellularSummary_getActiveSubscriptionInfo_cbrs() {
+        mActiveSubInfo = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
+                SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, false, true, true, true, false, true);
+        mDefaultDataSubInfo = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
+                SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, false, true, true, true, true, false);
+        mSubscriptionInfoEntityList.add(mActiveSubInfo);
+        mSubscriptionInfoEntityList.add(mDefaultDataSubInfo);
+        mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
         mController.displayPreference(mScreen);
-        final SubscriptionManager subscriptionManager = mock(SubscriptionManager.class);
-        final SubscriptionInfo defaultSubInfo = mock(SubscriptionInfo.class);
-        final SubscriptionInfo activeSubInfo = mock(SubscriptionInfo.class);
-        final String expectedSummary =
-                ResourcesUtils.getResourcesString(mContext, "mobile_data_temp_using", "");
-
-        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(subscriptionManager);
-        when(subscriptionManager.getDefaultDataSubscriptionInfo()).thenReturn(defaultSubInfo);
-
-        when(subscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(activeSubInfo);
-        when(subscriptionManager.isSubscriptionVisible(activeSubInfo)).thenReturn(false);
 
         mController.updateCellularSummary();
-        assertThat(mPreference.getSummary()).isEqualTo("");
+        assertThat(mPreference.getSummary()).isEqualTo(DISPLAY_NAME_2);
 
-        when(subscriptionManager.isSubscriptionVisible(activeSubInfo)).thenReturn(true);
+        mActiveSubInfo = 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, true);
+        mSubscriptionInfoEntityList.add(mActiveSubInfo);
+        mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
+        mController.onAvailableSubInfoChanged(mSubscriptionInfoEntityList);
+        final String expectedSummary =
+                ResourcesUtils.getResourcesString(mContext, "mobile_data_temp_using",
+                        DISPLAY_NAME_1);
         mController.updateCellularSummary();
         assertThat(mPreference.getSummary()).isEqualTo(expectedSummary);
     }