diff --git a/res/values/config.xml b/res/values/config.xml index 432b1cad01b..d8f04f4942e 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -611,23 +611,24 @@ 3 - - @string/user_aspect_ratio_app_default @string/user_aspect_ratio_half_screen + @string/user_aspect_ratio_device_size @string/user_aspect_ratio_16_9 @string/user_aspect_ratio_4_3 @string/user_aspect_ratio_3_2 - 0 1 + 2 4 3 5 diff --git a/res/values/strings.xml b/res/values/strings.xml index e7b5d138683..0804084cce5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -12081,25 +12081,30 @@ other {Apps installed more than # months ago} } - - - Screen size - - Choose an aspect ratio for apps if they haven’t been optimized for your %1$s + + + Aspect ratio + + Choose an aspect ratio to view this app if it hasn\'t been designed to fit your %1$s Suggested apps Apps you have overridden App default - - Half-screen + + Half screen + + Device aspect ratio 16:9 3:2 4:3 + + The app will restart when you change aspect ratio. You may lose unsaved changes. + Fingerprint sensor diff --git a/res/xml/apps.xml b/res/xml/apps.xml index 386a07bde55..73bcbc9b9e7 100644 --- a/res/xml/apps.xml +++ b/res/xml/apps.xml @@ -81,7 +81,7 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioAppsPreferenceController.java b/src/com/android/settings/applications/appcompat/UserAspectRatioAppsPreferenceController.java index 6ec2528da4a..ff68fb015f2 100644 --- a/src/com/android/settings/applications/appcompat/UserAspectRatioAppsPreferenceController.java +++ b/src/com/android/settings/applications/appcompat/UserAspectRatioAppsPreferenceController.java @@ -43,6 +43,6 @@ public class UserAspectRatioAppsPreferenceController extends BasePreferenceContr @Override public CharSequence getSummary() { - return mContext.getResources().getString(R.string.screen_size_summary, Build.MODEL); + return mContext.getResources().getString(R.string.aspect_ratio_summary, Build.MODEL); } } diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java new file mode 100644 index 00000000000..17bca72714d --- /dev/null +++ b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java @@ -0,0 +1,217 @@ +/* + * 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.Intent.FLAG_ACTIVITY_CLEAR_TOP; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +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_DISPLAY_SIZE; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; + +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; +import com.android.settings.applications.AppInfoWithHeader; +import com.android.settingslib.widget.ActionButtonsPreference; +import com.android.settingslib.widget.SelectorWithWidgetPreference; + +import java.util.ArrayList; +import java.util.List; + +/** + * App specific activity to show aspect ratio overrides + */ +public class UserAspectRatioDetails extends AppInfoWithHeader implements + SelectorWithWidgetPreference.OnClickListener { + private static final String TAG = UserAspectRatioDetails.class.getSimpleName(); + + private static final String KEY_HEADER_BUTTONS = "header_view"; + private static final String KEY_PREF_HALF_SCREEN = "half_screen_pref"; + private static final String KEY_PREF_DISPLAY_SIZE = "display_size_pref"; + private static final String KEY_PREF_16_9 = "16_9_pref"; + private static final String KEY_PREF_4_3 = "4_3_pref"; + @VisibleForTesting + static final String KEY_PREF_DEFAULT = "app_default_pref"; + @VisibleForTesting + static final String KEY_PREF_3_2 = "3_2_pref"; + + private final List mAspectRatioPreferences = new ArrayList<>(); + + @NonNull private UserAspectRatioManager mUserAspectRatioManager; + @NonNull private String mSelectedKey = KEY_PREF_DEFAULT; + + @Override + public void onCreate(@NonNull Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mUserAspectRatioManager = new UserAspectRatioManager(getContext()); + initPreferences(); + try { + final int userAspectRatio = mUserAspectRatioManager + .getUserMinAspectRatioValue(mPackageName, mUserId); + mSelectedKey = getSelectedKey(userAspectRatio); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get user min aspect ratio"); + } + refreshUi(); + } + + @Override + public void onRadioButtonClicked(@NonNull SelectorWithWidgetPreference selected) { + final String selectedKey = selected.getKey(); + if (mSelectedKey.equals(selectedKey)) { + return; + } + final int userAspectRatio = getSelectedUserMinAspectRatio(selectedKey); + try { + getAspectRatioManager().setUserMinAspectRatio(mPackageName, mUserId, userAspectRatio); + } catch (RemoteException e) { + Log.e(TAG, "Unable to set user min aspect ratio"); + return; + } + // Only update to selected aspect ratio if nothing goes wrong + mSelectedKey = selectedKey; + updateAllPreferences(mSelectedKey); + Log.d(TAG, "Killing application process " + mPackageName); + try { + final IActivityManager am = ActivityManager.getService(); + am.stopAppForUser(mPackageName, mUserId); + } catch (RemoteException e) { + Log.e(TAG, "Unable to stop application " + mPackageName); + } + } + + @Override + public int getMetricsCategory() { + // TODO(b/292566895): add metrics for logging + return 0; + } + + @Override + protected boolean refreshUi() { + if (mPackageInfo == null || mPackageInfo.applicationInfo == null) { + return false; + } + updateAllPreferences(mSelectedKey); + return true; + } + + @Override + protected AlertDialog createDialog(int id, int errorCode) { + return null; + } + + private void launchApplication() { + Intent launchIntent = mPm.getLaunchIntentForPackage(mPackageName) + .addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); + if (launchIntent != null) { + getContext().startActivityAsUser(launchIntent, new UserHandle(mUserId)); + } + } + + @PackageManager.UserMinAspectRatio + private int getSelectedUserMinAspectRatio(@NonNull String selectedKey) { + switch (selectedKey) { + case KEY_PREF_HALF_SCREEN: + return USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; + case KEY_PREF_DISPLAY_SIZE: + return USER_MIN_ASPECT_RATIO_DISPLAY_SIZE; + case KEY_PREF_3_2: + return USER_MIN_ASPECT_RATIO_3_2; + case KEY_PREF_4_3: + return USER_MIN_ASPECT_RATIO_4_3; + case KEY_PREF_16_9: + return USER_MIN_ASPECT_RATIO_16_9; + default: + return USER_MIN_ASPECT_RATIO_UNSET; + } + } + + @NonNull + private String getSelectedKey(@PackageManager.UserMinAspectRatio int userMinAspectRatio) { + switch (userMinAspectRatio) { + case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN: + return KEY_PREF_HALF_SCREEN; + case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE: + return KEY_PREF_DISPLAY_SIZE; + case USER_MIN_ASPECT_RATIO_3_2: + return KEY_PREF_3_2; + case USER_MIN_ASPECT_RATIO_4_3: + return KEY_PREF_4_3; + case USER_MIN_ASPECT_RATIO_16_9: + return KEY_PREF_16_9; + default: + return KEY_PREF_DEFAULT; + } + } + + private void initPreferences() { + addPreferencesFromResource(R.xml.user_aspect_ratio_details); + + ((ActionButtonsPreference) findPreference(KEY_HEADER_BUTTONS)) + .setButton1Text(R.string.launch_instant_app) + .setButton1Icon(R.drawable.ic_settings_open) + .setButton1OnClickListener(v -> launchApplication()); + + addPreference(KEY_PREF_DEFAULT, USER_MIN_ASPECT_RATIO_UNSET); + addPreference(KEY_PREF_DISPLAY_SIZE, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE); + addPreference(KEY_PREF_HALF_SCREEN, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN); + addPreference(KEY_PREF_16_9, USER_MIN_ASPECT_RATIO_16_9); + addPreference(KEY_PREF_4_3, USER_MIN_ASPECT_RATIO_4_3); + addPreference(KEY_PREF_3_2, USER_MIN_ASPECT_RATIO_3_2); + } + + private void addPreference(@NonNull String key, + @PackageManager.UserMinAspectRatio int aspectRatio) { + final SelectorWithWidgetPreference pref = findPreference(key); + if (pref == null) { + return; + } + if (!mUserAspectRatioManager.containsAspectRatioOption(aspectRatio)) { + pref.setVisible(false); + return; + } + pref.setTitle(mUserAspectRatioManager.getUserMinAspectRatioEntry(aspectRatio)); + pref.setOnClickListener(this); + mAspectRatioPreferences.add(pref); + } + + private void updateAllPreferences(@NonNull String selectedKey) { + for (SelectorWithWidgetPreference pref : mAspectRatioPreferences) { + pref.setChecked(selectedKey.equals(pref.getKey())); + } + } + + @VisibleForTesting + UserAspectRatioManager getAspectRatioManager() { + return mUserAspectRatioManager; + } +} diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java b/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java index 35bd7a93a1d..c5740e28ed0 100644 --- a/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java +++ b/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java @@ -28,6 +28,7 @@ import android.provider.DeviceConfig; import android.util.ArrayMap; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.settings.R; @@ -91,6 +92,32 @@ public class UserAspectRatioManager { aspectRatio, mContext.getString(R.string.user_aspect_ratio_app_default)); } + /** + * @return corresponding aspect ratio string for package name and user + */ + @NonNull + public String getUserMinAspectRatioEntry(@NonNull String packageName, int uid) + throws RemoteException { + final int aspectRatio = getUserMinAspectRatioValue(packageName, uid); + return getUserMinAspectRatioEntry(aspectRatio); + } + + /** + * Whether user aspect ratio option is specified in + * {@link R.array.config_userAspectRatioOverrideValues} + */ + public boolean containsAspectRatioOption(@PackageManager.UserMinAspectRatio int option) { + return mUserAspectRatioMap.containsKey(option); + } + + /** + * Sets user-specified {@link PackageManager.UserMinAspectRatio} override for an app + */ + public void setUserMinAspectRatio(@NonNull String packageName, int uid, + @PackageManager.UserMinAspectRatio int aspectRatio) throws RemoteException { + mIPm.setUserMinAspectRatio(packageName, uid, aspectRatio); + } + /** * Whether an app's aspect ratio can be overridden by user. Only apps with launcher entry * will be overridable. @@ -122,14 +149,17 @@ public class UserAspectRatioManager { final Map userMinAspectRatioMap = new ArrayMap<>(); for (int i = 0; i < userMinAspectRatioValues.length; i++) { final int aspectRatioVal = userMinAspectRatioValues[i]; + final String aspectRatioString = getAspectRatioStringOrDefault( + userMinAspectRatioStrings[i], aspectRatioVal); 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_DISPLAY_SIZE: 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]); + userMinAspectRatioMap.put(aspectRatioVal, aspectRatioString); } } if (!userMinAspectRatioMap.containsKey(PackageManager.USER_MIN_ASPECT_RATIO_UNSET)) { @@ -139,6 +169,29 @@ public class UserAspectRatioManager { return userMinAspectRatioMap; } + @NonNull + private String getAspectRatioStringOrDefault(@Nullable String aspectRatioString, + @PackageManager.UserMinAspectRatio int aspectRatioVal) { + if (aspectRatioString != null) { + return aspectRatioString; + } + // Options are customized per device and if strings are set to @null, use default + switch (aspectRatioVal) { + case PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN: + return mContext.getString(R.string.user_aspect_ratio_half_screen); + case PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE: + return mContext.getString(R.string.user_aspect_ratio_device_size); + case PackageManager.USER_MIN_ASPECT_RATIO_4_3: + return mContext.getString(R.string.user_aspect_ratio_4_3); + case PackageManager.USER_MIN_ASPECT_RATIO_16_9: + return mContext.getString(R.string.user_aspect_ratio_16_9); + case PackageManager.USER_MIN_ASPECT_RATIO_3_2: + return mContext.getString(R.string.user_aspect_ratio_3_2); + default: + return mContext.getString(R.string.user_aspect_ratio_app_default); + } + } + @VisibleForTesting void addInfoHasLauncherEntry(@NonNull ResolveInfo infoHasLauncherEntry) { mInfoHasLauncherEntryList.add(infoHasLauncherEntry); diff --git a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt new file mode 100644 index 00000000000..3680715705e --- /dev/null +++ b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt @@ -0,0 +1,80 @@ +/* + * 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 androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +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.UserAspectRatioDetails +import com.android.settings.applications.appcompat.UserAspectRatioManager +import com.android.settings.applications.appinfo.AppInfoDashboardFragment +import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn + +@OptIn(ExperimentalLifecycleComposeApi::class) +@Composable +fun UserAspectRatioAppPreference(app: ApplicationInfo) { + val context = LocalContext.current + val presenter = remember { UserAspectRatioAppPresenter(context, app) } + if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return + + Preference(object : PreferenceModel { + override val title = stringResource(R.string.aspect_ratio_title) + override val summary = presenter.summaryFlow.collectAsStateWithLifecycle( + initialValue = stringResource(R.string.summary_placeholder), + ) + override val onClick = presenter::startActivity + }) +} + +class UserAspectRatioAppPresenter( + private val context: Context, + private val app: ApplicationInfo, +) { + private val manager = UserAspectRatioManager(context) + + val isAvailableFlow = flow { + emit(UserAspectRatioManager.isFeatureEnabled(context) + && manager.canDisplayAspectRatioUi(app)) + }.flowOn(Dispatchers.IO) + + fun startActivity() = + navigateToAppAspectRatioSettings(context, app) + + val summaryFlow = flow { + emit(manager.getUserMinAspectRatioEntry(app.packageName, context.userId)) + }.flowOn(Dispatchers.IO) +} + +fun navigateToAppAspectRatioSettings(context: Context, app: ApplicationInfo) { + AppInfoDashboardFragment.startAppInfoFragment( + UserAspectRatioDetails::class.java, + app, + context, + AppInfoSettingsProvider.METRICS_CATEGORY, + ) +} \ No newline at end of file diff --git a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt index 34b6b663066..ff904921ec6 100644 --- a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt +++ b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt @@ -36,7 +36,6 @@ 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 @@ -81,7 +80,7 @@ object UserAspectRatioAppsPageProvider : SettingsPageProvider { @VisibleForTesting fun EntryItem() = Preference(object : PreferenceModel { - override val title = stringResource(R.string.screen_size_title) + override val title = stringResource(R.string.aspect_ratio_title) override val summary = getSummary().toState() override val onClick = navigator(name) }) @@ -94,7 +93,7 @@ object UserAspectRatioAppsPageProvider : SettingsPageProvider { @Composable @VisibleForTesting - fun getSummary(): String = stringResource(R.string.screen_size_summary, Build.MODEL) + fun getSummary(): String = stringResource(R.string.aspect_ratio_summary, Build.MODEL) } @Composable @@ -103,7 +102,7 @@ fun UserAspectRatioAppList( = { AppList() }, ) { AppListPage( - title = stringResource(R.string.screen_size_title), + title = stringResource(R.string.aspect_ratio_title), listModel = rememberContext(::UserAspectRatioAppListModel), appList = appList, header = { @@ -148,7 +147,7 @@ class UserAspectRatioAppListModel(private val context: Context) override fun AppListItemModel.AppItem() { val app = record.app AppListItem( - onClick = AppInfoSettingsProvider.navigator(app) + onClick = { navigateToAppAspectRatioSettings(context, app) } ) } diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt index d59a4f72c86..e6df933df1f 100644 --- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt +++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt @@ -35,6 +35,7 @@ import com.android.settings.R import com.android.settings.applications.AppInfoBase import com.android.settings.applications.appinfo.AppInfoDashboardFragment import com.android.settings.spa.SpaActivity.Companion.startSpaActivity +import com.android.settings.spa.app.appcompat.UserAspectRatioAppPreference import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider @@ -150,6 +151,7 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) { } Category(title = stringResource(R.string.advanced_apps)) { + UserAspectRatioAppPreference(app) DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app) ModifySystemSettingsAppListProvider.InfoPageEntryItem(app) PictureInPictureListProvider.InfoPageEntryItem(app) diff --git a/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java new file mode 100644 index 00000000000..31ff76c7d11 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java @@ -0,0 +1,97 @@ +/* + * 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 com.android.settings.applications.appcompat.UserAspectRatioDetails.KEY_PREF_3_2; +import static com.android.settings.applications.appcompat.UserAspectRatioDetails.KEY_PREF_DEFAULT; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.IActivityManager; +import android.content.Context; +import android.os.RemoteException; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.testutils.shadow.ShadowActivityManager; +import com.android.settingslib.widget.SelectorWithWidgetPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +/** + * To run test: atest SettingsRoboTests:UserAspectRatioDetailsTest + */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowActivityManager.class}) +public class UserAspectRatioDetailsTest { + + @Mock + private UserAspectRatioManager mUserAspectRatioManager; + @Mock + private IActivityManager mAm; + + private SelectorWithWidgetPreference mRadioButtonPref; + private Context mContext; + private UserAspectRatioDetails mFragment; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = spy(ApplicationProvider.getApplicationContext()); + mFragment = spy(new UserAspectRatioDetails()); + when(mFragment.getContext()).thenReturn(mContext); + when(mFragment.getAspectRatioManager()).thenReturn(mUserAspectRatioManager); + ShadowActivityManager.setService(mAm); + mRadioButtonPref = new SelectorWithWidgetPreference(mContext); + } + + @Test + public void onRadioButtonClicked_prefChange_shouldStopActivity() throws RemoteException { + // Default was already selected + mRadioButtonPref.setKey(KEY_PREF_DEFAULT); + mFragment.onRadioButtonClicked(mRadioButtonPref); + // Preference changed + mRadioButtonPref.setKey(KEY_PREF_3_2); + mFragment.onRadioButtonClicked(mRadioButtonPref); + // Only triggered once when preference change + verify(mAm).stopAppForUser(any(), anyInt()); + } + + @Test + public void onRadioButtonClicked_prefChange_shouldSetAspectRatio() throws RemoteException { + // Default was already selected + mRadioButtonPref.setKey(KEY_PREF_DEFAULT); + mFragment.onRadioButtonClicked(mRadioButtonPref); + // Preference changed + mRadioButtonPref.setKey(KEY_PREF_3_2); + mFragment.onRadioButtonClicked(mRadioButtonPref); + // Only triggered once when preference changes + verify(mUserAspectRatioManager).setUserMinAspectRatio( + any(), anyInt(), anyInt()); + } +} diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreferenceTest.kt new file mode 100644 index 00000000000..342405acf9d --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreferenceTest.kt @@ -0,0 +1,204 @@ +/* + * 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.ActivityInfo +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.os.Build +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotDisplayed +import androidx.compose.ui.test.hasTextExactly +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performClick +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.dx.mockito.inline.extended.ExtendedMockito +import android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER +import com.android.settings.R +import com.android.settings.applications.appinfo.AppInfoDashboardFragment +import com.android.settings.applications.appcompat.UserAspectRatioDetails +import com.android.settings.applications.appcompat.UserAspectRatioManager +import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider +import com.android.settings.testutils.TestDeviceConfig +import com.android.settingslib.spa.testutils.delay +import com.android.settingslib.spa.testutils.waitUntilExists +import org.junit.After +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.Mockito.eq +import org.mockito.Mockito.mock +import org.mockito.MockitoSession +import org.mockito.Spy +import org.mockito.quality.Strictness +import org.mockito.Mockito.`when` as whenever + +/** + * To run this test: atest SettingsSpaUnitTests:UserAspectRatioAppPreferenceTest + */ +@RunWith(AndroidJUnit4::class) +class UserAspectRatioAppPreferenceTest { + @get:Rule + val composeTestRule = createComposeRule() + + private lateinit var mockSession: MockitoSession + + @Spy + private val context: Context = ApplicationProvider.getApplicationContext() + + @Spy + private val resources = context.resources + + private val aspectRatioEnabledConfig = + TestDeviceConfig(NAMESPACE_WINDOW_MANAGER, "enable_app_compat_user_aspect_ratio_settings") + + private lateinit var userAspectRatioManager: UserAspectRatioManager + + @Mock + private lateinit var packageManager: PackageManager + + @Before + fun setUp() { + mockSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .mockStatic(UserAspectRatioDetails::class.java) + .mockStatic(AppInfoDashboardFragment::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + whenever(context.resources).thenReturn(resources) + whenever(context.packageManager).thenReturn(packageManager) + userAspectRatioManager = mock(UserAspectRatioManager::class.java) + } + + @After + fun tearDown() { + aspectRatioEnabledConfig.reset() + mockSession.finishMocking() + } + + @Test + fun whenConfigIsFalse_notDisplayed() { + setConfig(false) + + setContent() + + composeTestRule.onRoot().assertIsNotDisplayed() + } + + @Test + fun whenCannotDisplayAspectRatioUi_notDisplayed() { + setContent() + + composeTestRule.onRoot().assertIsNotDisplayed() + } + + @Test + fun whenCanDisplayAspectRatioUiAndConfigFalse_notDisplayed() { + setConfig(false) + whenever(packageManager.queryIntentActivities(any(), anyInt())) + .thenReturn(listOf(RESOLVE_INFO)) + + setContent() + + composeTestRule.onRoot().assertIsNotDisplayed() + } + + @Test + fun whenCannotDisplayAspectRatioUiAndConfigTrue_notDisplayed() { + setConfig(true) + + setContent() + + composeTestRule.onRoot().assertIsNotDisplayed() + } + + @Test + fun whenCanDisplayAspectRatioUiAndConfigTrue_Displayed() { + setConfig(true) + whenever(packageManager.queryIntentActivities(any(), anyInt())) + .thenReturn(listOf(RESOLVE_INFO)) + + setContent() + + composeTestRule.onNode( + hasTextExactly( + context.getString(R.string.aspect_ratio_title), + context.getString(R.string.user_aspect_ratio_app_default) + ), + ).assertIsDisplayed().assertIsEnabled() + } + + @Test + fun onClick_startActivity() { + setConfig(true) + whenever(packageManager.queryIntentActivities(any(), anyInt())) + .thenReturn(listOf(RESOLVE_INFO)) + + setContent() + composeTestRule.onRoot().performClick() + + ExtendedMockito.verify { + AppInfoDashboardFragment.startAppInfoFragment( + UserAspectRatioDetails::class.java, + APP, + context, + AppInfoSettingsProvider.METRICS_CATEGORY, + ) + } + } + + private fun setConfig(enabled: Boolean) { + whenever(resources.getBoolean( + com.android.internal.R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled + )).thenReturn(enabled) + aspectRatioEnabledConfig.override(enabled) + } + + private fun setContent() { + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + UserAspectRatioAppPreference(APP) + } + } + composeTestRule.delay() + } + + private companion object { + const val PACKAGE_NAME = "package.name" + const val UID = 123 + val APP = ApplicationInfo().apply { + packageName = PACKAGE_NAME + uid = UID + } + private val RESOLVE_INFO = ResolveInfo().apply { + activityInfo = ActivityInfo().apply { + packageName = PACKAGE_NAME + } + } + } +} \ 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 index e0eb5b2c86a..0d2869c6cdf 100644 --- 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 @@ -60,21 +60,21 @@ class UserAspectRatioAppsPageProviderTest { @Test fun injectEntry_title() { setInjectEntry() - composeTestRule.onNodeWithText(context.getString(R.string.screen_size_title)) + composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_title)) .assertIsDisplayed() } @Test fun injectEntry_summary() { setInjectEntry() - composeTestRule.onNodeWithText(context.getString(R.string.screen_size_summary, Build.MODEL)) + composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_summary, Build.MODEL)) .assertIsDisplayed() } @Test fun injectEntry_onClick_navigate() { setInjectEntry() - composeTestRule.onNodeWithText(context.getString(R.string.screen_size_title)).performClick() + composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_title)).performClick() assertThat(fakeNavControllerWrapper.navigateCalledWith).isEqualTo("UserAspectRatioAppsPage") } @@ -92,7 +92,7 @@ class UserAspectRatioAppsPageProviderTest { UserAspectRatioAppList {} } - composeTestRule.onNodeWithText(context.getString(R.string.screen_size_title)) + composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_title)) .assertIsDisplayed() }