Merge "Migrate PlatformCompat App List to SPA" into udc-qpr-dev am: 879ff5f271 am: 78981afdc2

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/23410196

Change-Id: I09189fc2dfe6789db9c7745dd43cf5168c2e9dfc
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Chaohui Wang
2023-06-16 09:24:26 +00:00
committed by Automerger Merge Worker
9 changed files with 267 additions and 94 deletions

View File

@@ -10546,8 +10546,6 @@
<string name="platform_compat_default_disabled_title">Default disabled changes</string> <string name="platform_compat_default_disabled_title">Default disabled changes</string>
<!-- Title for target SDK gated app compat changes category (do not translate 'targetSdkVersion') [CHAR LIMIT=50] --> <!-- Title for target SDK gated app compat changes category (do not translate 'targetSdkVersion') [CHAR LIMIT=50] -->
<string name="platform_compat_target_sdk_title">Enabled for targetSdkVersion &gt;= <xliff:g id="number" example="29">%d</xliff:g></string> <string name="platform_compat_target_sdk_title">Enabled for targetSdkVersion &gt;= <xliff:g id="number" example="29">%d</xliff:g></string>
<!-- Title for the dialog shown when no debuggable apps are available [CHAR LIMIT=30] -->
<string name="platform_compat_dialog_title_no_apps">No apps available</string>
<!-- Explanatory text shown when no debuggable apps are available [CHAR LIMIT=NONE] --> <!-- Explanatory text shown when no debuggable apps are available [CHAR LIMIT=NONE] -->
<string name="platform_compat_dialog_text_no_apps">App compatibility changes can only be modified for debuggable apps. Install a debuggable app and try again.</string> <string name="platform_compat_dialog_text_no_apps">App compatibility changes can only be modified for debuggable apps. Install a debuggable app and try again.</string>

View File

@@ -258,7 +258,7 @@
android:key="platform_compat_dashboard" android:key="platform_compat_dashboard"
android:title="@string/platform_compat_dashboard_title" android:title="@string/platform_compat_dashboard_title"
android:summary="@string/platform_compat_dashboard_summary" android:summary="@string/platform_compat_dashboard_summary"
android:fragment="com.android.settings.development.compat.PlatformCompatDashboard" settings:controller="com.android.settings.spa.development.compat.PlatformCompatPreferenceController"
/> />
<SwitchPreference <SwitchPreference

View File

@@ -25,12 +25,4 @@ public interface DevelopmentOptionsActivityRequestCodes {
int REQUEST_CODE_DEBUG_APP = 1; int REQUEST_CODE_DEBUG_APP = 1;
int REQUEST_MOCK_LOCATION_APP = 2; int REQUEST_MOCK_LOCATION_APP = 2;
int REQUEST_CODE_ANGLE_ALL_USE_ANGLE = 3;
int REQUEST_CODE_ANGLE_DRIVER_PKGS = 4;
int REQUEST_CODE_ANGLE_DRIVER_VALUES = 5;
int REQUEST_COMPAT_CHANGE_APP = 6;
} }

View File

@@ -17,21 +17,16 @@
package com.android.settings.development.compat; package com.android.settings.development.compat;
import static com.android.internal.compat.OverrideAllowedState.ALLOWED; import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes.REQUEST_COMPAT_CHANGE_APP;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.compat.Compatibility.ChangeConfig; import android.compat.Compatibility.ChangeConfig;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.ArraySet; import android.util.ArraySet;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
@@ -40,35 +35,28 @@ import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreference; import androidx.preference.SwitchPreference;
import com.android.internal.compat.AndroidBuildClassifier;
import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.compat.CompatibilityChangeInfo; import com.android.internal.compat.CompatibilityChangeInfo;
import com.android.internal.compat.IPlatformCompat; import com.android.internal.compat.IPlatformCompat;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.development.AppPicker;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
/** /**
* Dashboard for Platform Compat preferences. * Dashboard for Platform Compat preferences.
*/ */
public class PlatformCompatDashboard extends DashboardFragment { public class PlatformCompatDashboard extends DashboardFragment {
private static final String TAG = "PlatformCompatDashboard"; private static final String TAG = "PlatformCompatDashboard";
private static final String COMPAT_APP = "compat_app"; public static final String COMPAT_APP = "compat_app";
private IPlatformCompat mPlatformCompat; private IPlatformCompat mPlatformCompat;
private CompatibilityChangeInfo[] mChanges; private CompatibilityChangeInfo[] mChanges;
private AndroidBuildClassifier mAndroidBuildClassifier = new AndroidBuildClassifier();
private boolean mShouldStartAppPickerOnResume = true;
@VisibleForTesting @VisibleForTesting
String mSelectedApp; String mSelectedApp;
@@ -108,32 +96,6 @@ public class PlatformCompatDashboard extends DashboardFragment {
} catch (RemoteException e) { } catch (RemoteException e) {
throw new RuntimeException("Could not list changes!", e); throw new RuntimeException("Could not list changes!", e);
} }
if (icicle != null) {
mShouldStartAppPickerOnResume = false;
mSelectedApp = icicle.getString(COMPAT_APP);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_COMPAT_CHANGE_APP) {
mShouldStartAppPickerOnResume = false;
switch (resultCode) {
case Activity.RESULT_OK:
mSelectedApp = data.getAction();
break;
case Activity.RESULT_CANCELED:
if (TextUtils.isEmpty(mSelectedApp)) {
finish();
}
break;
case AppPicker.RESULT_NO_MATCHING_APPS:
mSelectedApp = null;
break;
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
} }
@Override @Override
@@ -142,34 +104,19 @@ public class PlatformCompatDashboard extends DashboardFragment {
if (isFinishingOrDestroyed()) { if (isFinishingOrDestroyed()) {
return; return;
} }
if (!mShouldStartAppPickerOnResume) { Bundle arguments = getArguments();
if (TextUtils.isEmpty(mSelectedApp)) { if (arguments == null) {
new AlertDialog.Builder(getContext()) finish();
.setTitle(R.string.platform_compat_dialog_title_no_apps)
.setMessage(R.string.platform_compat_dialog_text_no_apps)
.setPositiveButton(R.string.okay, (dialog, which) -> finish())
.setOnDismissListener(dialog -> finish())
.setCancelable(false)
.show();
return; return;
} }
mSelectedApp = arguments.getString(COMPAT_APP);
try { try {
final ApplicationInfo applicationInfo = getApplicationInfo(); final ApplicationInfo applicationInfo = getApplicationInfo();
addPreferences(applicationInfo); addPreferences(applicationInfo);
return; } catch (PackageManager.NameNotFoundException ignored) {
} catch (PackageManager.NameNotFoundException e) { finish();
mShouldStartAppPickerOnResume = true;
mSelectedApp = null;
} }
} }
startAppPicker();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(COMPAT_APP, mSelectedApp);
}
private void addPreferences(ApplicationInfo applicationInfo) { private void addPreferences(ApplicationInfo applicationInfo) {
getPreferenceScreen().removeAll(); getPreferenceScreen().removeAll();
@@ -266,12 +213,6 @@ public class PlatformCompatDashboard extends DashboardFragment {
appPreference.setIcon(icon); appPreference.setIcon(icon);
appPreference.setSummary(getString(R.string.platform_compat_selected_app_summary, appPreference.setSummary(getString(R.string.platform_compat_selected_app_summary,
mSelectedApp, applicationInfo.targetSdkVersion)); mSelectedApp, applicationInfo.targetSdkVersion));
appPreference.setKey(mSelectedApp);
appPreference.setOnPreferenceClickListener(
preference -> {
startAppPicker();
return true;
});
return appPreference; 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 class CompatChangePreferenceChangeListener implements OnPreferenceChangeListener {
private final long changeId; private final long changeId;

View File

@@ -37,6 +37,7 @@ import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider
import com.android.settings.spa.core.instrumentation.SpaLogProvider import com.android.settings.spa.core.instrumentation.SpaLogProvider
import com.android.settings.spa.development.UsageStatsPageProvider 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.home.HomePageProvider
import com.android.settings.spa.network.NetworkAndInternetPageProvider import com.android.settings.spa.network.NetworkAndInternetPageProvider
import com.android.settings.spa.notification.AppListNotificationsPageProvider import com.android.settings.spa.notification.AppListNotificationsPageProvider
@@ -84,6 +85,7 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) {
LanguageAndInputPageProvider, LanguageAndInputPageProvider,
AppLanguagesPageProvider, AppLanguagesPageProvider,
UsageStatsPageProvider, UsageStatsPageProvider,
PlatformCompatAppListPageProvider,
BackgroundInstalledAppsPageProvider, BackgroundInstalledAppsPageProvider,
CloneAppInfoSettingsProvider, CloneAppInfoSettingsProvider,
NetworkAndInternetPageProvider, NetworkAndInternetPageProvider,

View File

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

View File

@@ -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<PlatformCompatAppRecord> {
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
appListFlow.mapItem(::PlatformCompatAppRecord)
override fun filter(
userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<PlatformCompatAppRecord>>,
) = 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<PlatformCompatAppRecord>.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()
}
}

View File

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

View File

@@ -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<PackageInfoFlags>(), 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<String> {
lateinit var summary: State<String>
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
}
}
}