From b4c4f6cbe6ef405b82acdceef7b33e3c4a9be954 Mon Sep 17 00:00:00 2001 From: Graciela Wissen Putri Date: Tue, 4 Jul 2023 15:10:29 +0000 Subject: [PATCH] [2/n] Add aspect ratio app list page under apps Apps > General > Screen Size To enable feature: adb shell device_config put window_manager enable_app_compat_user_aspect_ratio_settings true adb shell am force-stop com.android.settings Fix: 287448088 Test: Manual atest AspectRatioAppsPageProviderTest atest AspectRatioUtilsTest All CUJs passed in go/settings-cujs Change-Id: I4de6c3cbdbdfbc79ed839ec149fb633344dcd3a7 --- res/values/config.xml | 22 ++ res/values/strings.xml | 20 ++ res/xml/apps.xml | 11 + src/com/android/settings/Settings.java | 2 + ...erAspectRatioAppsPreferenceController.java | 48 ++++ .../appcompat/UserAspectRatioManager.java | 146 ++++++++++++ .../ManageApplications.java | 1 + .../ManageApplicationsUtil.kt | 5 + .../settings/spa/SettingsSpaEnvironment.kt | 2 + .../UserAspectRatioAppsPageProvider.kt | 214 ++++++++++++++++++ .../UserAspectRatioAppsPageProviderTest.kt | 196 ++++++++++++++++ .../appcompat/UserAspectRatioManagerTest.java | 129 +++++++++++ 12 files changed, 796 insertions(+) create mode 100644 src/com/android/settings/applications/appcompat/UserAspectRatioAppsPreferenceController.java create mode 100644 src/com/android/settings/applications/appcompat/UserAspectRatioManager.java create mode 100644 src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt create mode 100644 tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt create mode 100644 tests/unit/src/com/android/settings/applications/appcompat/UserAspectRatioManagerTest.java diff --git a/res/values/config.xml b/res/values/config.xml index 49dcce5b2a8..432b1cad01b 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -611,6 +611,28 @@ 3 + + + + @string/user_aspect_ratio_app_default + @string/user_aspect_ratio_half_screen + @string/user_aspect_ratio_16_9 + @string/user_aspect_ratio_4_3 + @string/user_aspect_ratio_3_2 + + + + + 0 + 1 + 4 + 3 + 5 + + + + Screen size + + Choose an aspect ratio for apps if they haven’t been optimized for your %1$s + + Suggested apps + + Apps you have overridden + + App default + + Half-screen + + 16:9 + + 3:2 + + 4:3 + Fingerprint sensor diff --git a/res/xml/apps.xml b/res/xml/apps.xml index 03212c914fe..386a07bde55 100644 --- a/res/xml/apps.xml +++ b/res/xml/apps.xml @@ -79,6 +79,17 @@ android:key="dashboard_tile_placeholder" android:order="10"/> + + + + mInfoHasLauncherEntryList; + private final Map mUserAspectRatioMap; + + public UserAspectRatioManager(@NonNull Context context) { + mContext = context; + mIPm = AppGlobals.getPackageManager(); + mInfoHasLauncherEntryList = context.getPackageManager().queryIntentActivities( + UserAspectRatioManager.LAUNCHER_ENTRY_INTENT, PackageManager.GET_META_DATA); + mUserAspectRatioMap = getUserMinAspectRatioMapping(); + } + + /** + * Whether user aspect ratio settings is enabled for device. + */ + public static boolean isFeatureEnabled(Context context) { + final boolean isBuildTimeFlagEnabled = context.getResources().getBoolean( + com.android.internal.R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled); + return isBuildTimeFlagEnabled && getValueFromDeviceConfig(); + } + + /** + * @return user-specific {@link PackageManager.UserMinAspectRatio} override for an app + */ + @PackageManager.UserMinAspectRatio + public int getUserMinAspectRatioValue(@NonNull String packageName, int uid) + throws RemoteException { + return mIPm.getUserMinAspectRatio(packageName, uid); + } + + /** + * @return corresponding string for {@link PackageManager.UserMinAspectRatio} value + */ + @NonNull + public String getUserMinAspectRatioEntry(@PackageManager.UserMinAspectRatio int aspectRatio) { + return mUserAspectRatioMap.getOrDefault( + aspectRatio, mContext.getString(R.string.user_aspect_ratio_app_default)); + } + + /** + * Whether an app's aspect ratio can be overridden by user. Only apps with launcher entry + * will be overridable. + */ + public boolean canDisplayAspectRatioUi(@NonNull ApplicationInfo app) { + boolean hasLauncherEntry = mInfoHasLauncherEntryList.stream() + .anyMatch(info -> info.activityInfo.packageName.equals(app.packageName)); + return hasLauncherEntry; + } + + private static boolean getValueFromDeviceConfig() { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_WINDOW_MANAGER, + KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS, + DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS); + } + + @NonNull + private Map getUserMinAspectRatioMapping() { + final String[] userMinAspectRatioStrings = mContext.getResources().getStringArray( + R.array.config_userAspectRatioOverrideEntries); + final int[] userMinAspectRatioValues = mContext.getResources().getIntArray( + R.array.config_userAspectRatioOverrideValues); + if (userMinAspectRatioStrings.length != userMinAspectRatioValues.length) { + throw new RuntimeException( + "config_userAspectRatioOverride options cannot be different length"); + } + + final Map userMinAspectRatioMap = new ArrayMap<>(); + for (int i = 0; i < userMinAspectRatioValues.length; i++) { + final int aspectRatioVal = userMinAspectRatioValues[i]; + switch (aspectRatioVal) { + // Only map known values of UserMinAspectRatio and ignore unknown entries + case PackageManager.USER_MIN_ASPECT_RATIO_UNSET: + case PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN: + case PackageManager.USER_MIN_ASPECT_RATIO_4_3: + case PackageManager.USER_MIN_ASPECT_RATIO_16_9: + case PackageManager.USER_MIN_ASPECT_RATIO_3_2: + userMinAspectRatioMap.put(aspectRatioVal, userMinAspectRatioStrings[i]); + } + } + if (!userMinAspectRatioMap.containsKey(PackageManager.USER_MIN_ASPECT_RATIO_UNSET)) { + throw new RuntimeException("config_userAspectRatioOverrideValues options must have" + + " USER_MIN_ASPECT_RATIO_UNSET value"); + } + return userMinAspectRatioMap; + } + + @VisibleForTesting + void addInfoHasLauncherEntry(@NonNull ResolveInfo infoHasLauncherEntry) { + mInfoHasLauncherEntryList.add(infoHasLauncherEntry); + } +} diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index 548ca553b40..d734a27f033 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -269,6 +269,7 @@ public class ManageApplications extends InstrumentedFragment public static final int LIST_TYPE_CLONED_APPS = 17; public static final int LIST_TYPE_NFC_TAG_APPS = 18; public static final int LIST_TYPE_TURN_SCREEN_ON = 19; + public static final int LIST_TYPE_USER_ASPECT_RATIO_APPS = 20; // List types that should show instant apps. public static final Set LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList( diff --git a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt index 6574f6926fc..8313686f29f 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt +++ b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt @@ -20,6 +20,7 @@ import android.content.Context import android.util.FeatureFlagUtils import com.android.settings.Settings.AlarmsAndRemindersActivity import com.android.settings.Settings.AppBatteryUsageActivity +import com.android.settings.Settings.UserAspectRatioAppListActivity import com.android.settings.Settings.ChangeNfcTagAppsActivity import com.android.settings.Settings.ChangeWifiStateActivity import com.android.settings.Settings.ClonedAppsListActivity @@ -40,6 +41,7 @@ import com.android.settings.applications.appinfo.AppLocaleDetails import com.android.settings.applications.manageapplications.ManageApplications.LIST_MANAGE_EXTERNAL_STORAGE import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_ALARMS_AND_REMINDERS import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_APPS_LOCALE +import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_USER_ASPECT_RATIO_APPS import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_BATTERY_OPTIMIZATION import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_CLONED_APPS import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_GAMES @@ -57,6 +59,7 @@ import com.android.settings.applications.manageapplications.ManageApplications.L import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WIFI_ACCESS import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WRITE_SETTINGS import com.android.settings.spa.app.AllAppListPageProvider +import com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider @@ -92,6 +95,7 @@ object ManageApplicationsUtil { ClonedAppsListActivity::class to LIST_TYPE_CLONED_APPS, ChangeNfcTagAppsActivity::class to LIST_TYPE_NFC_TAG_APPS, TurnScreenOnSettingsActivity::class to LIST_TYPE_TURN_SCREEN_ON, + UserAspectRatioAppListActivity::class to LIST_TYPE_USER_ASPECT_RATIO_APPS, ) @JvmField @@ -114,6 +118,7 @@ object ManageApplicationsUtil { LIST_TYPE_APPS_LOCALE -> AppLanguagesPageProvider.name LIST_TYPE_MAIN -> AllAppListPageProvider.name LIST_TYPE_NFC_TAG_APPS -> NfcTagAppsSettingsProvider.getAppListRoute() + LIST_TYPE_USER_ASPECT_RATIO_APPS -> UserAspectRatioAppsPageProvider.name else -> null } } diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt index b506005edba..db88784720e 100644 --- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt +++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt @@ -20,6 +20,7 @@ import android.content.Context import android.util.FeatureFlagUtils import com.android.settings.spa.app.AllAppListPageProvider import com.android.settings.spa.app.AppsMainPageProvider +import com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider import com.android.settings.spa.app.appinfo.CloneAppInfoSettingsProvider import com.android.settings.spa.app.backgroundinstall.BackgroundInstalledAppsPageProvider @@ -86,6 +87,7 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { UsageStatsPageProvider, PlatformCompatAppListPageProvider, BackgroundInstalledAppsPageProvider, + UserAspectRatioAppsPageProvider, CloneAppInfoSettingsProvider, NetworkAndInternetPageProvider, ) + togglePermissionAppListTemplate.createPageProviders(), diff --git a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt new file mode 100644 index 00000000000..34b6b663066 --- /dev/null +++ b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2023 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.appcompat + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.GET_ACTIVITIES +import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.settings.R +import com.android.settings.applications.appcompat.UserAspectRatioManager +import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.compose.rememberContext +import com.android.settingslib.spa.framework.compose.toState +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.util.asyncMap +import com.android.settingslib.spa.framework.util.filterItem +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.ui.SettingsBody +import com.android.settingslib.spa.widget.ui.SpinnerOption +import com.android.settingslib.spaprivileged.model.app.AppListModel +import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.userId +import com.android.settingslib.spaprivileged.template.app.AppList +import com.android.settingslib.spaprivileged.template.app.AppListInput +import com.android.settingslib.spaprivileged.template.app.AppListItem +import com.android.settingslib.spaprivileged.template.app.AppListItemModel +import com.android.settingslib.spaprivileged.template.app.AppListPage +import com.google.common.annotations.VisibleForTesting +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn + +object UserAspectRatioAppsPageProvider : SettingsPageProvider { + override val name = "UserAspectRatioAppsPage" + private val owner = createSettingsPage() + + override fun isEnabled(arguments: Bundle?): Boolean = + UserAspectRatioManager.isFeatureEnabled(SpaEnvironmentFactory.instance.appContext) + + @Composable + override fun Page(arguments: Bundle?) = + UserAspectRatioAppList() + + @Composable + @VisibleForTesting + fun EntryItem() = + Preference(object : PreferenceModel { + override val title = stringResource(R.string.screen_size_title) + override val summary = getSummary().toState() + override val onClick = navigator(name) + }) + + @VisibleForTesting + fun buildInjectEntry() = SettingsEntryBuilder + .createInject(owner) + .setSearchDataFn { null } + .setUiLayoutFn { EntryItem() } + + @Composable + @VisibleForTesting + fun getSummary(): String = stringResource(R.string.screen_size_summary, Build.MODEL) +} + +@Composable +fun UserAspectRatioAppList( + appList: @Composable AppListInput.() -> Unit + = { AppList() }, +) { + AppListPage( + title = stringResource(R.string.screen_size_title), + listModel = rememberContext(::UserAspectRatioAppListModel), + appList = appList, + header = { + Box(Modifier.padding(SettingsDimension.itemPadding)) { + SettingsBody(UserAspectRatioAppsPageProvider.getSummary()) + } + } + ) +} + +data class UserAspectRatioAppListItemModel( + override val app: ApplicationInfo, + val override: Int, + val suggested: Boolean, + val canDisplay: Boolean, +) : AppRecord + +class UserAspectRatioAppListModel(private val context: Context) + : AppListModel { + + private val packageManager = context.packageManager + private val userAspectRatioManager = UserAspectRatioManager(context) + + override fun getSpinnerOptions( + recordList: List + ): List { + val hasSuggested = recordList.any { it.suggested } + val hasOverride = recordList.any { it.override != USER_MIN_ASPECT_RATIO_UNSET } + val options = mutableListOf(SpinnerItem.All) + // Add suggested filter first as default + if (hasSuggested) options.add(0, SpinnerItem.Suggested) + if (hasOverride) options += SpinnerItem.Overridden + return options.map { + SpinnerOption( + id = it.ordinal, + text = context.getString(it.stringResId), + ) + } + } + + @Composable + override fun AppListItemModel.AppItem() { + val app = record.app + AppListItem( + onClick = AppInfoSettingsProvider.navigator(app) + ) + } + + override fun transform(userIdFlow: Flow, appListFlow: Flow>) = + userIdFlow.combine(appListFlow) { uid, appList -> + appList.asyncMap { app -> + UserAspectRatioAppListItemModel( + app = app, + suggested = !app.isSystemApp && getPackageAndActivityInfo( + app)?.isFixedOrientationOrAspectRatio() == true, + override = userAspectRatioManager.getUserMinAspectRatioValue( + app.packageName, uid), + canDisplay = userAspectRatioManager.canDisplayAspectRatioUi(app), + ) + } + } + + override fun filter( + userIdFlow: Flow, + option: Int, + recordListFlow: Flow> + ): Flow> = recordListFlow.filterItem( + when (SpinnerItem.values().getOrNull(option)) { + SpinnerItem.Suggested -> ({ it.canDisplay && it.suggested }) + SpinnerItem.Overridden -> ({ it.override != USER_MIN_ASPECT_RATIO_UNSET }) + else -> ({ it.canDisplay }) + } + ) + + @OptIn(ExperimentalLifecycleComposeApi::class) + @Composable + override fun getSummary(option: Int, record: UserAspectRatioAppListItemModel) : State = + remember(record.override) { + flow { + emit(userAspectRatioManager.getUserMinAspectRatioEntry(record.override)) + }.flowOn(Dispatchers.IO) + }.collectAsStateWithLifecycle(initialValue = stringResource(R.string.summary_placeholder)) + + private fun getPackageAndActivityInfo(app: ApplicationInfo): PackageInfo? = try { + packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId) + } catch (e: Exception) { + // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause + // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture + // packages, we need to catch the exception to alleviate the impact before PackageManager + // fixing this issue or provide a better api. + Log.e(TAG, "Exception while getPackageInfoAsUser", e) + null + } + + companion object { + private const val TAG = "AspectRatioAppsListModel" + private fun PackageInfo.isFixedOrientationOrAspectRatio() = + activities?.any { a -> a.isFixedOrientation || a.hasFixedAspectRatio() } ?: false + private val GET_ACTIVITIES_FLAGS = + PackageManager.PackageInfoFlags.of(GET_ACTIVITIES.toLong()) + } +} + +private enum class SpinnerItem(val stringResId: Int) { + Suggested(R.string.user_aspect_ratio_suggested_apps_label), + All(R.string.filter_all_apps), + Overridden(R.string.user_aspect_ratio_overridden_apps_label) +} \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt new file mode 100644 index 00000000000..e0eb5b2c86a --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2023 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.appcompat + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN +import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET +import android.os.Build +import androidx.compose.runtime.State +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.testutils.FakeNavControllerWrapper +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.android.settingslib.spaprivileged.template.app.AppListItemModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * To run this test: atest SettingsSpaUnitTests:UserAspectRatioAppsPageProviderTest + */ +@RunWith(AndroidJUnit4::class) +class UserAspectRatioAppsPageProviderTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val context: Context = ApplicationProvider.getApplicationContext() + private val fakeNavControllerWrapper = FakeNavControllerWrapper() + + @Test + fun aspectRatioAppsPageProvider_name() { + assertThat(UserAspectRatioAppsPageProvider.name).isEqualTo(EXPECTED_PROVIDER_NAME) + } + + @Test + fun injectEntry_title() { + setInjectEntry() + composeTestRule.onNodeWithText(context.getString(R.string.screen_size_title)) + .assertIsDisplayed() + } + + @Test + fun injectEntry_summary() { + setInjectEntry() + composeTestRule.onNodeWithText(context.getString(R.string.screen_size_summary, Build.MODEL)) + .assertIsDisplayed() + } + + @Test + fun injectEntry_onClick_navigate() { + setInjectEntry() + composeTestRule.onNodeWithText(context.getString(R.string.screen_size_title)).performClick() + assertThat(fakeNavControllerWrapper.navigateCalledWith).isEqualTo("UserAspectRatioAppsPage") + } + + private fun setInjectEntry() { + composeTestRule.setContent { + fakeNavControllerWrapper.Wrapper { + UserAspectRatioAppsPageProvider.buildInjectEntry().build().UiLayout() + } + } + } + + @Test + fun title_displayed() { + composeTestRule.setContent { + UserAspectRatioAppList {} + } + + composeTestRule.onNodeWithText(context.getString(R.string.screen_size_title)) + .assertIsDisplayed() + } + + @Test + fun item_labelDisplayed() { + setItemContent() + + composeTestRule.onNodeWithText(LABEL).assertIsDisplayed() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun aspectRatioAppListModel_transform() = runTest { + val listModel = UserAspectRatioAppListModel(context) + val recordListFlow = listModel.transform(flowOf(USER_ID), flowOf(listOf(APP))) + val recordList = recordListFlow.firstWithTimeoutOrNull()!! + + assertThat(recordList).hasSize(1) + assertThat(recordList[0].app).isSameInstanceAs(APP) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun aspectRatioAppListModel_filter() = runTest { + val listModel = UserAspectRatioAppListModel(context) + + val recordListFlow = listModel.filter(flowOf(USER_ID), 0, + flowOf(listOf(APP_RECORD_NOT_DISPLAYED, APP_RECORD_SUGGESTED))) + + val recordList = checkNotNull(recordListFlow.firstWithTimeoutOrNull()) + assertThat(recordList).containsExactly(APP_RECORD_SUGGESTED) + } + + private fun setItemContent() { + composeTestRule.setContent { + fakeNavControllerWrapper.Wrapper { + with(UserAspectRatioAppListModel(context)) { + AppListItemModel( + record = APP_RECORD_SUGGESTED, + label = LABEL, + summary = stateOf(SUMMARY) + ).AppItem() + } + } + } + } + + @Test + fun aspectRatioAppListModel_getSummaryDefault() { + val summaryState = setSummaryState(USER_MIN_ASPECT_RATIO_UNSET) + assertThat(summaryState.value) + .isEqualTo(context.getString(R.string.user_aspect_ratio_app_default)) + } + + @Test + fun aspectRatioAppListModel_getSummaryWhenSplitScreen() { + val summaryState = setSummaryState(USER_MIN_ASPECT_RATIO_SPLIT_SCREEN) + assertThat(summaryState.value) + .isEqualTo(context.getString(R.string.user_aspect_ratio_half_screen)) + } + + private fun setSummaryState(override: Int): State { + val listModel = UserAspectRatioAppListModel(context) + lateinit var summaryState: State + composeTestRule.setContent { + summaryState = listModel.getSummary(option = 0, + record = UserAspectRatioAppListItemModel( + app = APP, + override = override, + suggested = false, + canDisplay = true, + )) + } + return summaryState + } + + + private companion object { + private const val EXPECTED_PROVIDER_NAME = "UserAspectRatioAppsPage" + private const val PACKAGE_NAME = "package.name" + private const val USER_ID = 0 + private const val LABEL = "Label" + private const val SUMMARY = "Summary" + + private val APP = ApplicationInfo().apply { + packageName = PACKAGE_NAME + } + private val APP_RECORD_SUGGESTED = UserAspectRatioAppListItemModel( + APP, + override = USER_MIN_ASPECT_RATIO_UNSET, + suggested = true, + canDisplay = true + ) + private val APP_RECORD_NOT_DISPLAYED = UserAspectRatioAppListItemModel( + APP, + override = USER_MIN_ASPECT_RATIO_UNSET, + suggested = true, + canDisplay = false + ) + } +} \ No newline at end of file diff --git a/tests/unit/src/com/android/settings/applications/appcompat/UserAspectRatioManagerTest.java b/tests/unit/src/com/android/settings/applications/appcompat/UserAspectRatioManagerTest.java new file mode 100644 index 00000000000..36f2f54e2c5 --- /dev/null +++ b/tests/unit/src/com/android/settings/applications/appcompat/UserAspectRatioManagerTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.applications.appcompat; + +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; + +import static com.android.settings.applications.appcompat.UserAspectRatioManager.KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.provider.DeviceConfig; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.testutils.ResourcesUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * To run this test: atest SettingsUnitTests:UserAspectRatioManagerTest + */ +@RunWith(AndroidJUnit4.class) +public class UserAspectRatioManagerTest { + + private Context mContext; + private UserAspectRatioManager mUtils; + private String mOriginalFlag; + + @Before + public void setUp() { + mContext = spy(ApplicationProvider.getApplicationContext()); + mUtils = spy(new UserAspectRatioManager(mContext)); + mOriginalFlag = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, + KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS); + } + + @After + public void tearDown() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, + KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS, mOriginalFlag, true /* makeDefault */); + } + + @Test + public void testCanDisplayAspectRatioUi() { + final ApplicationInfo canDisplay = new ApplicationInfo(); + canDisplay.packageName = "com.app.candisplay"; + addResolveInfoLauncherEntry(canDisplay.packageName); + + assertTrue(mUtils.canDisplayAspectRatioUi(canDisplay)); + + final ApplicationInfo noLauncherEntry = new ApplicationInfo(); + noLauncherEntry.packageName = "com.app.nolauncherentry"; + + assertFalse(mUtils.canDisplayAspectRatioUi(noLauncherEntry)); + } + + @Test + public void testIsFeatureEnabled() { + assertFalse(UserAspectRatioManager.isFeatureEnabled(mContext)); + + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, + KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS, "true", false /* makeDefault */); + assertTrue(UserAspectRatioManager.isFeatureEnabled(mContext)); + } + + @Test + public void testGetUserMinAspectRatioEntry() { + // R.string.user_aspect_ratio_app_default + final String appDefault = ResourcesUtils.getResourcesString(mContext, + "user_aspect_ratio_app_default"); + assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_UNSET)) + .isEqualTo(appDefault); + // should always return default if value does not correspond to anything + assertThat(mUtils.getUserMinAspectRatioEntry(-1)) + .isEqualTo(appDefault); + // R.string.user_aspect_ratio_half_screen + assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_SPLIT_SCREEN)) + .isEqualTo(ResourcesUtils.getResourcesString(mContext, + "user_aspect_ratio_half_screen")); + // R.string.user_aspect_ratio_3_2 + assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_3_2)) + .isEqualTo(ResourcesUtils.getResourcesString(mContext, "user_aspect_ratio_3_2")); + // R,string.user_aspect_ratio_4_3 + assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_4_3)) + .isEqualTo(ResourcesUtils.getResourcesString(mContext, "user_aspect_ratio_4_3")); + // R.string.user_aspect_ratio_16_9 + assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_16_9)) + .isEqualTo(ResourcesUtils.getResourcesString(mContext, "user_aspect_ratio_16_9")); + } + + private void addResolveInfoLauncherEntry(String packageName) { + final ResolveInfo resolveInfo = mock(ResolveInfo.class); + final ActivityInfo activityInfo = mock(ActivityInfo.class); + activityInfo.packageName = packageName; + resolveInfo.activityInfo = activityInfo; + mUtils.addInfoHasLauncherEntry(resolveInfo); + } +}