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
This commit is contained in:
Chaohui Wang
2022-10-12 17:41:07 +08:00
parent 99b2bffe53
commit bb4d0250bb
8 changed files with 314 additions and 9 deletions

View File

@@ -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>

View File

@@ -1,4 +1,9 @@
{
"presubmit": [
{
"name": "SettingsSpaUnitTests"
}
],
"postsubmit": [
{
"name": "SettingsUnitTests",

View File

@@ -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();

View File

@@ -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)

View File

@@ -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,
)
}

47
tests/spa_unit/Android.bp Normal file
View File

@@ -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",
}

View File

@@ -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>

View File

@@ -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
}
}
}