From bb4d0250bbf44ac7aeb8501bee8a86cfdb6fe94e Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Wed, 12 Oct 2022 17:41:07 +0800 Subject: [PATCH] Add AppStoragePreference for Spa Also add new SettingsSpaUnitTests. Bug: 236346018 Test: Manual with App Info page Test: atest SettingsSpaUnitTests Test: Manual compare generated Settings AndroidManifest.xml Change-Id: I9f6b2ca446fd3d196792a876a6e4049c5cf97a1d --- AndroidManifest.xml | 18 +-- TEST_MAPPING | 5 + .../appinfo/AppInfoDashboardFragment.java | 14 ++ .../spa/app/appinfo/AppInfoSettings.kt | 1 + .../spa/app/appinfo/AppStoragePreference.kt | 75 ++++++++++ tests/spa_unit/Android.bp | 47 +++++++ tests/spa_unit/AndroidManifest.xml | 32 +++++ .../app/appinfo/AppStoragePreferenceTest.kt | 131 ++++++++++++++++++ 8 files changed, 314 insertions(+), 9 deletions(-) create mode 100644 src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt create mode 100644 tests/spa_unit/Android.bp create mode 100644 tests/spa_unit/AndroidManifest.xml create mode 100644 tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a351fb4aec9..f5da15fe711 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3555,7 +3555,7 @@ @@ -3685,7 +3685,7 @@ @@ -3940,7 +3940,7 @@ + android:authorities="${applicationId}.dashboard.SummaryProvider"> @@ -4369,13 +4369,13 @@ diff --git a/TEST_MAPPING b/TEST_MAPPING index 4865e19248b..836806cea11 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1,4 +1,9 @@ { + "presubmit": [ + { + "name": "SettingsSpaUnitTests" + } + ], "postsubmit": [ { "name": "SettingsUnitTests", diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java index 39e8ea8cfad..c1b49c2b53b 100755 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -630,6 +630,20 @@ public class AppInfoDashboardFragment extends DashboardFragment .launch(); } + /** Starts app info fragment from SPA pages. */ + public static void startAppInfoFragment( + Class destination, ApplicationInfo app, Context context, int sourceMetricsCategory) { + // start new fragment to display extended information + Bundle args = new Bundle(); + args.putString(ARG_PACKAGE_NAME, app.packageName); + args.putInt(ARG_PACKAGE_UID, app.uid); + new SubSettingLauncher(context) + .setDestination(destination.getName()) + .setArguments(args) + .setSourceMetricsCategory(sourceMetricsCategory) + .launch(); + } + private void onPackageRemoved() { getActivity().finishActivity(SUB_INFO_FRAGMENT); getActivity().finishAndRemoveTask(); diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt index 6acfac4145c..d71f889acee 100644 --- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt +++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt @@ -91,6 +91,7 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) { AppButtons(packageInfoPresenter) AppPermissionPreference(app) + AppStoragePreference(app) Category(title = stringResource(R.string.advanced_apps)) { DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app) diff --git a/src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt b/src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt new file mode 100644 index 00000000000..265f88299f3 --- /dev/null +++ b/src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt @@ -0,0 +1,75 @@ +/* + * 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.State +import androidx.compose.runtime.derivedStateOf +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.applications.AppStorageSettings +import com.android.settings.applications.appinfo.AppInfoDashboardFragment +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 com.android.settingslib.spaprivileged.template.app.getStorageSize + +@Composable +fun AppStoragePreference(app: ApplicationInfo) { + if (!app.hasFlag(ApplicationInfo.FLAG_INSTALLED)) return + val context = LocalContext.current + Preference( + model = object : PreferenceModel { + override val title = stringResource(R.string.storage_settings_for_app) + override val summary = getSummary(context, app) + override val onClick = { startStorageSettingsActivity(context, app) } + }, + singleLineSummary = true, + ) +} + +@Composable +private fun getSummary(context: Context, app: ApplicationInfo): State { + val sizeState = app.getStorageSize() + return remember { + derivedStateOf { + val size = sizeState.value + if (size.isBlank()) return@derivedStateOf context.getString(R.string.computing_size) + val storageType = context.getString( + when (app.hasFlag(ApplicationInfo.FLAG_EXTERNAL_STORAGE)) { + true -> R.string.storage_type_external + false -> R.string.storage_type_internal + } + ) + context.getString(R.string.storage_summary_format, size, storageType) + } + } +} + +private fun startStorageSettingsActivity(context: Context, app: ApplicationInfo) { + AppInfoDashboardFragment.startAppInfoFragment( + AppStorageSettings::class.java, + app, + context, + SettingsEnums.APPLICATIONS_INSTALLED_APP_DETAILS, + ) +} diff --git a/tests/spa_unit/Android.bp b/tests/spa_unit/Android.bp new file mode 100644 index 00000000000..ed83ab2039f --- /dev/null +++ b/tests/spa_unit/Android.bp @@ -0,0 +1,47 @@ +// +// 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 { + default_applicable_licenses: ["packages_apps_Settings_license"], +} + +android_test { + name: "SettingsSpaUnitTests", + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "Settings-core", + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + "androidx.test.ext.junit", + "androidx.test.runner", + "mockito-target-minus-junit4", + "truth-prebuilt", + ], + kotlincflags: [ + "-Xjvm-default=all", + "-opt-in=kotlin.RequiresOptIn", + ], + + instrumentation_for: "Settings-core", +} diff --git a/tests/spa_unit/AndroidManifest.xml b/tests/spa_unit/AndroidManifest.xml new file mode 100644 index 00000000000..5cf8ffd43b2 --- /dev/null +++ b/tests/spa_unit/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + 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 new file mode 100644 index 00000000000..394442d4e73 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.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.app.usage.StorageStats +import android.app.usage.StorageStatsManager +import android.content.Context +import android.content.pm.ApplicationInfo +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotDisplayed +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.storageStatsManager +import java.util.UUID +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 +import org.mockito.Mockito.any +import org.mockito.Mockito.eq +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidJUnit4::class) +class AppStoragePreferenceTest { + @JvmField + @Rule + val mockito: MockitoRule = MockitoJUnit.rule() + + @get:Rule + val composeTestRule = createComposeRule() + + @Spy + private var context: Context = ApplicationProvider.getApplicationContext() + + @Mock + private lateinit var storageStatsManager: StorageStatsManager + + @Before + fun setUp() { + whenever(context.storageStatsManager).thenReturn(storageStatsManager) + whenever( + storageStatsManager.queryStatsForPackage(eq(STORAGE_UUID), eq(PACKAGE_NAME), any()) + ).thenReturn(STATS) + } + + @Test + fun uninstalledApp_notDisplayed() { + val uninstalledApp = ApplicationInfo() + + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + AppStoragePreference(uninstalledApp) + } + } + + composeTestRule.onRoot().assertIsNotDisplayed() + } + + @Test + fun internalApp_displayed() { + val internalApp = ApplicationInfo().apply { + packageName = PACKAGE_NAME + flags = ApplicationInfo.FLAG_INSTALLED + storageUuid = STORAGE_UUID + } + + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + AppStoragePreference(internalApp) + } + } + + composeTestRule.onNodeWithText(context.getString(R.string.storage_settings_for_app)) + .assertIsDisplayed() + composeTestRule.onNodeWithText("123 B used in internal storage").assertIsDisplayed() + } + + @Test + fun externalApp_displayed() { + val externalApp = ApplicationInfo().apply { + packageName = PACKAGE_NAME + flags = ApplicationInfo.FLAG_INSTALLED or ApplicationInfo.FLAG_EXTERNAL_STORAGE + storageUuid = STORAGE_UUID + } + + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + AppStoragePreference(externalApp) + } + } + + composeTestRule.onNodeWithText(context.getString(R.string.storage_settings_for_app)) + .assertIsDisplayed() + composeTestRule.onNodeWithText("123 B used in external storage").assertIsDisplayed() + } + + companion object { + private const val PACKAGE_NAME = "package name" + private val STORAGE_UUID = UUID.randomUUID() + + private val STATS = StorageStats().apply { + codeBytes = 100 + dataBytes = 20 + cacheBytes = 3 + } + } +}