From ed6d6c9e0fc8f3961e2fe4e3eda75a76faec3bd5 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Tue, 9 May 2023 15:00:23 +0800 Subject: [PATCH] Migrate PlatformCompat App List to SPA So hidden modules are not shown in the list. This will also improve the developer experience. Bug: 273913035 Test: Manually with App Compatibility Changes Test: Unit test Change-Id: Ic78a51819159a0f848db6173f751b1be8c6b1e70 --- res/values/strings.xml | 2 - res/xml/development_settings.xml | 2 +- ...evelopmentOptionsActivityRequestCodes.java | 8 -- .../compat/PlatformCompatDashboard.java | 94 ++------------- .../settings/spa/SettingsSpaEnvironment.kt | 4 +- .../compat/PlatformCompatAppList.kt | 38 +++++++ .../compat/PlatformCompatAppListModel.kt | 72 ++++++++++++ .../PlatformCompatPreferenceController.kt | 34 ++++++ .../compat/PlatformCompatAppListModelTest.kt | 107 ++++++++++++++++++ 9 files changed, 267 insertions(+), 94 deletions(-) create mode 100644 src/com/android/settings/spa/development/compat/PlatformCompatAppList.kt create mode 100644 src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt create mode 100644 src/com/android/settings/spa/development/compat/PlatformCompatPreferenceController.kt create mode 100644 tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt diff --git a/res/values/strings.xml b/res/values/strings.xml index a78e74a2828..6ce7bdf22cc 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10542,8 +10542,6 @@ Default disabled changes Enabled for targetSdkVersion >= %d - - No apps available App compatibility changes can only be modified for debuggable apps. Install a debuggable app and try again. diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index b26005a65bb..32acac66ddb 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -258,7 +258,7 @@ android:key="platform_compat_dashboard" android:title="@string/platform_compat_dashboard_title" android:summary="@string/platform_compat_dashboard_summary" - android:fragment="com.android.settings.development.compat.PlatformCompatDashboard" + settings:controller="com.android.settings.spa.development.compat.PlatformCompatPreferenceController" /> finish()) - .setOnDismissListener(dialog -> finish()) - .setCancelable(false) - .show(); - return; - } - try { - final ApplicationInfo applicationInfo = getApplicationInfo(); - addPreferences(applicationInfo); - return; - } catch (PackageManager.NameNotFoundException e) { - mShouldStartAppPickerOnResume = true; - mSelectedApp = null; - } + Bundle arguments = getArguments(); + if (arguments == null) { + finish(); + return; + } + mSelectedApp = arguments.getString(COMPAT_APP); + try { + final ApplicationInfo applicationInfo = getApplicationInfo(); + addPreferences(applicationInfo); + } catch (PackageManager.NameNotFoundException ignored) { + finish(); } - startAppPicker(); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(COMPAT_APP, mSelectedApp); } private void addPreferences(ApplicationInfo applicationInfo) { @@ -266,12 +213,6 @@ public class PlatformCompatDashboard extends DashboardFragment { appPreference.setIcon(icon); appPreference.setSummary(getString(R.string.platform_compat_selected_app_summary, mSelectedApp, applicationInfo.targetSdkVersion)); - appPreference.setKey(mSelectedApp); - appPreference.setOnPreferenceClickListener( - preference -> { - startAppPicker(); - return true; - }); return appPreference; } @@ -294,17 +235,6 @@ public class PlatformCompatDashboard extends DashboardFragment { } } - private void startAppPicker() { - final Intent intent = new Intent(getContext(), AppPicker.class) - .putExtra(AppPicker.EXTRA_INCLUDE_NOTHING, false); - // If build is neither userdebug nor eng, only include debuggable apps - final boolean debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild(); - if (!debuggableBuild) { - intent.putExtra(AppPicker.EXTRA_DEBUGGABLE, true /* value */); - } - startActivityForResult(intent, REQUEST_COMPAT_CHANGE_APP); - } - private class CompatChangePreferenceChangeListener implements OnPreferenceChangeListener { private final long changeId; diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt index caf5b1532ce..b506005edba 100644 --- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt +++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt @@ -36,6 +36,7 @@ import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider import com.android.settings.spa.core.instrumentation.SpaLogProvider import com.android.settings.spa.development.UsageStatsPageProvider +import com.android.settings.spa.development.compat.PlatformCompatAppListPageProvider import com.android.settings.spa.home.HomePageProvider import com.android.settings.spa.network.NetworkAndInternetPageProvider import com.android.settings.spa.notification.AppListNotificationsPageProvider @@ -83,6 +84,7 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { LanguageAndInputPageProvider, AppLanguagesPageProvider, UsageStatsPageProvider, + PlatformCompatAppListPageProvider, BackgroundInstalledAppsPageProvider, CloneAppInfoSettingsProvider, NetworkAndInternetPageProvider, @@ -95,5 +97,5 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { override val logger = if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_ENABLE_SPA_METRICS)) SpaLogProvider - else object: SpaLogger {} + else object : SpaLogger {} } diff --git a/src/com/android/settings/spa/development/compat/PlatformCompatAppList.kt b/src/com/android/settings/spa/development/compat/PlatformCompatAppList.kt new file mode 100644 index 00000000000..5f3b4e700b3 --- /dev/null +++ b/src/com/android/settings/spa/development/compat/PlatformCompatAppList.kt @@ -0,0 +1,38 @@ +/* + * 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.development.compat + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.android.settings.R +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.compose.rememberContext +import com.android.settingslib.spaprivileged.template.app.AppListPage + +object PlatformCompatAppListPageProvider : SettingsPageProvider { + override val name = "PlatformCompatAppList" + + @Composable + override fun Page(arguments: Bundle?) { + AppListPage( + title = stringResource(R.string.platform_compat_dashboard_title), + listModel = rememberContext(::PlatformCompatAppListModel), + noItemMessage = stringResource(R.string.platform_compat_dialog_text_no_apps), + ) + } +} \ No newline at end of file diff --git a/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt b/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt new file mode 100644 index 00000000000..c6752b9c6d8 --- /dev/null +++ b/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt @@ -0,0 +1,72 @@ +/* + * 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.development.compat + +import android.app.settings.SettingsEnums +import android.content.Context +import android.content.pm.ApplicationInfo +import android.os.Build +import androidx.compose.runtime.Composable +import androidx.core.os.bundleOf +import com.android.settings.core.SubSettingLauncher +import com.android.settings.development.compat.PlatformCompatDashboard +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.framework.util.filterItem +import com.android.settingslib.spa.framework.util.mapItem +import com.android.settingslib.spaprivileged.model.app.AppListModel +import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.hasFlag +import com.android.settingslib.spaprivileged.model.app.userHandle +import com.android.settingslib.spaprivileged.template.app.AppListItem +import com.android.settingslib.spaprivileged.template.app.AppListItemModel +import kotlinx.coroutines.flow.Flow + +data class PlatformCompatAppRecord( + override val app: ApplicationInfo, +) : AppRecord + +class PlatformCompatAppListModel( + private val context: Context, +) : AppListModel { + + override fun transform(userIdFlow: Flow, appListFlow: Flow>) = + appListFlow.mapItem(::PlatformCompatAppRecord) + + override fun filter( + userIdFlow: Flow, option: Int, recordListFlow: Flow>, + ) = recordListFlow.filterItem { record -> + Build.IS_DEBUGGABLE || record.app.hasFlag(ApplicationInfo.FLAG_DEBUGGABLE) + } + + @Composable + override fun getSummary(option: Int, record: PlatformCompatAppRecord) = + stateOf(record.app.packageName) + + @Composable + override fun AppListItemModel.AppItem() { + AppListItem { navigateToAppCompat(app = record.app) } + } + + private fun navigateToAppCompat(app: ApplicationInfo) { + SubSettingLauncher(context) + .setDestination(PlatformCompatDashboard::class.qualifiedName) + .setSourceMetricsCategory(SettingsEnums.DEVELOPMENT) + .setArguments(bundleOf(PlatformCompatDashboard.COMPAT_APP to app.packageName)) + .setUserHandle(app.userHandle) + .launch() + } +} diff --git a/src/com/android/settings/spa/development/compat/PlatformCompatPreferenceController.kt b/src/com/android/settings/spa/development/compat/PlatformCompatPreferenceController.kt new file mode 100644 index 00000000000..c0a421cf31e --- /dev/null +++ b/src/com/android/settings/spa/development/compat/PlatformCompatPreferenceController.kt @@ -0,0 +1,34 @@ +/* + * 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.development.compat + +import android.content.Context +import androidx.preference.Preference +import com.android.settings.core.BasePreferenceController +import com.android.settings.spa.SpaActivity.Companion.startSpaActivity + +class PlatformCompatPreferenceController(context: Context, preferenceKey: String) : + BasePreferenceController(context, preferenceKey) { + override fun getAvailabilityStatus() = AVAILABLE + + override fun handlePreferenceTreeClick(preference: Preference): Boolean { + if (preference.key == mPreferenceKey) { + mContext.startSpaActivity(PlatformCompatAppListPageProvider.name) + return true + } + return false + } +} \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt b/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt new file mode 100644 index 00000000000..78aca852492 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt @@ -0,0 +1,107 @@ +/* + * 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.development.compat + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.PackageInfoFlags +import androidx.compose.runtime.State +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +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.any +import org.mockito.Mockito.anyInt +import org.mockito.Spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.`when` as whenever + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class PlatformCompatAppListModelTest { + @get:Rule + val composeTestRule = createComposeRule() + + @get:Rule + val mockito: MockitoRule = MockitoJUnit.rule() + + @Spy + private val context: Context = ApplicationProvider.getApplicationContext() + + @Mock + private lateinit var packageManager: PackageManager + + private lateinit var listModel: PlatformCompatAppListModel + + @Before + fun setUp() { + whenever(context.packageManager).thenReturn(packageManager) + whenever(packageManager.getInstalledPackagesAsUser(any(), anyInt())) + .thenReturn(emptyList()) + listModel = PlatformCompatAppListModel(context) + } + + @Test + fun transform() = runTest { + val recordListFlow = listModel.transform( + userIdFlow = flowOf(USER_ID), + appListFlow = flowOf(listOf(APP)), + ) + + val recordList = recordListFlow.first() + assertThat(recordList).hasSize(1) + val record = recordList[0] + assertThat(record.app).isSameInstanceAs(APP) + } + + @Test + fun getSummary() = runTest { + val summaryState = getSummaryState(APP) + + assertThat(summaryState.value).isEqualTo(PACKAGE_NAME) + } + + private fun getSummaryState(app: ApplicationInfo): State { + lateinit var summary: State + composeTestRule.setContent { + summary = listModel.getSummary( + option = 0, + record = PlatformCompatAppRecord(app), + ) + } + return summary + } + + private companion object { + const val USER_ID = 0 + const val PACKAGE_NAME = "package.name" + val APP = ApplicationInfo().apply { + packageName = PACKAGE_NAME + } + } +} \ No newline at end of file