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
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a351fb4..f5da15f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3555,7 +3555,7 @@
<provider
android:name="androidx.core.content.FileProvider"
- android:authorities="com.android.settings.files"
+ android:authorities="${applicationId}.files"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
@@ -3565,13 +3565,13 @@
<provider
android:name=".deviceinfo.legal.ModuleLicenseProvider"
- android:authorities="com.android.settings.module_licenses"
+ android:authorities="${applicationId}.module_licenses"
android:grantUriPermissions="true"
android:exported="false"/>
<provider
android:name=".emergency.EmergencyActionContentProvider"
- android:authorities="com.android.settings.emergency"
+ android:authorities="${applicationId}.emergency"
android:permission="android.permission.CALL_PRIVILEGED"
android:exported="true"/>
@@ -3685,7 +3685,7 @@
<provider
android:name=".search.SettingsSearchIndexablesProvider"
- android:authorities="com.android.settings"
+ android:authorities="${applicationId}"
android:multiprocess="false"
android:grantUriPermissions="true"
android:permission="android.permission.READ_SEARCH_INDEXABLES"
@@ -3697,7 +3697,7 @@
<provider
android:name=".dashboard.suggestions.SuggestionStateProvider"
- android:authorities="com.android.settings.suggestions.status"
+ android:authorities="${applicationId}.suggestions.status"
android:exported="true">
<intent-filter>
<action android:name="com.android.settings.action.SUGGESTION_STATE_PROVIDER" />
@@ -3940,7 +3940,7 @@
<provider
android:name=".dashboard.SummaryProvider"
- android:authorities="com.android.settings.dashboard.SummaryProvider">
+ android:authorities="${applicationId}.dashboard.SummaryProvider">
</provider>
<activity android:name=".backup.UserBackupSettingsActivity"
@@ -4327,7 +4327,7 @@
</activity>
<provider android:name=".slices.SettingsSliceProvider"
- android:authorities="com.android.settings.slices;android.settings.slices"
+ android:authorities="${applicationId}.slices;android.settings.slices"
android:exported="true"
android:grantUriPermissions="true" />
@@ -4369,13 +4369,13 @@
<provider
android:name=".homepage.contextualcards.CardContentProvider"
- android:authorities="com.android.settings.homepage.CardContentProvider"
+ android:authorities="${applicationId}.homepage.CardContentProvider"
android:exported="true"
android:permission="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA" />
<provider
android:name=".homepage.contextualcards.SettingsContextualCardProvider"
- android:authorities="com.android.settings.homepage.contextualcards"
+ android:authorities="${applicationId}.homepage.contextualcards"
android:permission="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA"
android:exported="true">
<intent-filter>
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4865e19..836806c 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 39e8ea8..c1b49c2 100755
--- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -630,6 +630,20 @@
.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 6acfac4..d71f889 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 @@
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 0000000..265f882
--- /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<String> {
+ 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 0000000..ed83ab2
--- /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 0000000..5cf8ffd
--- /dev/null
+++ b/tests/spa_unit/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.settings.tests.spa_unit">
+
+ <application>
+ <provider android:name="com.android.settings.slices.SettingsSliceProvider"
+ android:authorities="${applicationId}.slices"
+ tools:replace="android:authorities"/>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Tests for Settings SPA package"
+ android:targetPackage="com.android.settings.tests.spa_unit"/>
+</manifest>
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 0000000..394442d
--- /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
+ }
+ }
+}