From d01497a5de29205ae7e6ce6ceb05cdc86dda24f6 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Mon, 28 Mar 2022 22:34:11 +0800 Subject: [PATCH 01/10] Fix "learn more" link is not clickable Use function setLearnMoreAction instead of ClickableSpan in NFC FooterPreference. Bug: 226299551 Test: maunal test Change-Id: I69c494b3e6fc92940d498d8be0c9ef5041b1bb3b --- .../settings/nfc/DefaultPaymentSettings.java | 35 ++++--------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/src/com/android/settings/nfc/DefaultPaymentSettings.java b/src/com/android/settings/nfc/DefaultPaymentSettings.java index 08b843d48f5..5224d92fb4e 100644 --- a/src/com/android/settings/nfc/DefaultPaymentSettings.java +++ b/src/com/android/settings/nfc/DefaultPaymentSettings.java @@ -29,13 +29,9 @@ import android.text.Spanned; import android.text.TextUtils; import android.text.style.AlignmentSpan; import android.text.style.BulletSpan; -import android.text.style.ClickableSpan; import android.text.style.RelativeSizeSpan; -import android.view.View; -import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.R; @@ -58,7 +54,7 @@ public class DefaultPaymentSettings extends DefaultAppPickerFragment { private PaymentBackend mPaymentBackend; private List mAppInfos; - private Preference mFooterPreference; + private FooterPreference mFooterPreference; @Override public int getMetricsCategory() { @@ -249,31 +245,12 @@ public class DefaultPaymentSettings extends DefaultAppPickerFragment { } private void setupFooterPreference() { - final String textNfcDefaultPaymentFooter = getResources().getString( - R.string.nfc_default_payment_footer); - final String textMoreDetails = getResources().getString(R.string.nfc_more_details); - - final SpannableString spannableString = new SpannableString( - textNfcDefaultPaymentFooter + System.lineSeparator() - + System.lineSeparator() + textMoreDetails); - final ClickableSpan clickableSpan = new ClickableSpan() { - @Override - public void onClick(@NonNull View widget) { - Intent howItWorksIntent = new Intent(getActivity(), HowItWorks.class); - startActivity(howItWorksIntent); - } - }; - - if (textNfcDefaultPaymentFooter != null && textMoreDetails != null) { - spannableString.setSpan(clickableSpan, textNfcDefaultPaymentFooter.length() + 1, - textNfcDefaultPaymentFooter.length() + textMoreDetails.length() + 2, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - mFooterPreference = new FooterPreference(getContext()); - mFooterPreference.setLayoutResource(R.layout.preference_footer); - mFooterPreference.setTitle(spannableString); - mFooterPreference.setSelectable(false); + mFooterPreference.setTitle(getResources().getString(R.string.nfc_default_payment_footer)); mFooterPreference.setIcon(R.drawable.ic_info_outline_24dp); + mFooterPreference.setLearnMoreAction(v -> { + final Intent howItWorksIntent = new Intent(getActivity(), HowItWorks.class); + getContext().startActivity(howItWorksIntent); + }); } } From ec589193c328a4bd6fdb325b1588cc1a39c693f2 Mon Sep 17 00:00:00 2001 From: Joshua Mccloskey Date: Mon, 28 Mar 2022 21:52:24 +0000 Subject: [PATCH 02/10] Updated UDFPS strings. Test: Manual. Fixes: 226490774 Change-Id: I6aeed72ab87e9b8b937f1acda12499c9af87462c --- res/drawable/ic_guarantee.xml | 26 +++++++++++++++++++ .../fingerprint_enroll_introduction.xml | 21 +++++++++++++++ res/values/strings.xml | 10 +++++-- .../FingerprintEnrollEnrolling.java | 4 +-- .../FingerprintEnrollIntroduction.java | 7 +++++ .../FingerprintEnrollParentalConsent.java | 8 +++++- 6 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 res/drawable/ic_guarantee.xml diff --git a/res/drawable/ic_guarantee.xml b/res/drawable/ic_guarantee.xml new file mode 100644 index 00000000000..7aa61243de0 --- /dev/null +++ b/res/drawable/ic_guarantee.xml @@ -0,0 +1,26 @@ + + + + \ No newline at end of file diff --git a/res/layout/fingerprint_enroll_introduction.xml b/res/layout/fingerprint_enroll_introduction.xml index d4914e64bbb..aaed5be01d3 100644 --- a/res/layout/fingerprint_enroll_introduction.xml +++ b/res/layout/fingerprint_enroll_introduction.xml @@ -165,6 +165,27 @@ style="@style/BiometricEnrollIntroMessage" /> + + + + + + + Your phone can be unlocked when you don\u2019t intend to, like if someone holds it up to your finger. Your child\u2019s phone can be unlocked when they don\u2019t intend to, like if someone holds it up to their finger. + + For best results, use a screen protector that\u2019s Made for Google certified. With other screen protectors, your fingerprint may not work. + + For best results, use a screen protector that\u2019s Made for Google certified. With other screen protectors, your child\u2019s fingerprint may not work. @@ -1120,8 +1124,10 @@ Touch & hold each time the fingerprint icon moves. This helps capture more of your fingerprint. Place the tip of your finger on the sensor - - Finally, use the edges of your finger + + Place the left edge of your finger + + Place the right edge of your finger Place the side of your fingerprint on the sensor and hold, then switch to the other side diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 70140c42c8c..a6806e02db2 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -376,7 +376,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { break; case STAGE_LEFT_EDGE: - setHeaderText(R.string.security_settings_udfps_enroll_edge_title); + setHeaderText(R.string.security_settings_udfps_enroll_left_edge_title); if (!mHaveShownUdfpsLeftEdgeLottie && mIllustrationLottie != null) { mHaveShownUdfpsLeftEdgeLottie = true; setDescriptionText(""); @@ -395,7 +395,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { } break; case STAGE_RIGHT_EDGE: - setHeaderText(R.string.security_settings_udfps_enroll_edge_title); + setHeaderText(R.string.security_settings_udfps_enroll_right_edge_title); if (!mHaveShownUdfpsRightEdgeLottie && mIllustrationLottie != null) { mHaveShownUdfpsRightEdgeLottie = true; setDescriptionText(""); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java index 82b76c3e33d..6fe14e6a9cd 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java @@ -91,10 +91,12 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { final TextView footerMessage3 = findViewById(R.id.footer_message_3); final TextView footerMessage4 = findViewById(R.id.footer_message_4); final TextView footerMessage5 = findViewById(R.id.footer_message_5); + final TextView footerMessage6 = findViewById(R.id.footer_message_6); footerMessage2.setText(getFooterMessage2()); footerMessage3.setText(getFooterMessage3()); footerMessage4.setText(getFooterMessage4()); footerMessage5.setText(getFooterMessage5()); + footerMessage6.setText(getFooterMessage6()); final TextView footerTitle1 = findViewById(R.id.footer_title_1); final TextView footerTitle2 = findViewById(R.id.footer_title_2); @@ -163,6 +165,11 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5; } + @StringRes + protected int getFooterMessage6() { + return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_6; + } + @Override protected boolean isDisabledByAdmin() { return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollParentalConsent.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollParentalConsent.java index 22212f27d83..c33ae175a09 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollParentalConsent.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollParentalConsent.java @@ -44,7 +44,8 @@ public class FingerprintEnrollParentalConsent extends FingerprintEnrollIntroduct R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_2, R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_3, R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_4, - R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_5 + R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_5, + R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_6 }; @Override @@ -116,6 +117,11 @@ public class FingerprintEnrollParentalConsent extends FingerprintEnrollIntroduct return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_5; } + @StringRes + protected int getFooterMessage6() { + return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_6; + } + @Override protected int getHeaderResDefault() { return R.string.security_settings_fingerprint_enroll_consent_introduction_title; From 70470586a6accf7af2a165fc59008bc54774026e Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Tue, 29 Mar 2022 14:37:53 +0800 Subject: [PATCH 03/10] Fix XML preference controller with androidx LifecycleObserver Controller can implements LifecycleObserver to observe the lifecycle. Before this fix, preference controller which defined in the XML file must implement com.android.settingslib.core.lifecycle.LifecycleObserver (which is deprecated, and is a subclass of androidx.lifecycle.LifecycleObserver). After this fix, preference controller which defined in the XML file and implemented androidx.lifecycle.LifecycleObserver will successful observe the lifecycle. Fix: 149338098 Test: robotest & manual Change-Id: If9e48e44267de8e89a5e8f45d256719130936320 --- src/com/android/settings/dashboard/DashboardFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index bff8226910e..256a6201cb7 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -27,6 +27,7 @@ import android.util.Log; import androidx.annotation.CallSuper; import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.LifecycleObserver; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceManager; @@ -43,7 +44,6 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.ProviderTile; import com.android.settingslib.drawer.Tile; From f14248310f7fc72227c933077dafbfb52f114d76 Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Wed, 30 Mar 2022 16:35:46 +0800 Subject: [PATCH 04/10] Update strings of clear app data dialog Update body and positive button strings. Bug: 219932186 Test: visual Change-Id: I34f4be091d1e5a076de8ee7a2221a3b5c90747a4 --- res/values/strings.xml | 6 ++++-- .../android/settings/applications/AppStorageSettings.java | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 3d7cfdbad3b..acba8535560 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4758,11 +4758,13 @@ Delete app data? - This app\u2019s data will be permanently deleted. This includes files, settings, databases, and other app data. - + This app\u2019s data, including files and settings, will be permanently deleted from this device + OK Cancel + + Delete diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java index 91eeace0fd3..c3dc3062bba 100644 --- a/src/com/android/settings/applications/AppStorageSettings.java +++ b/src/com/android/settings/applications/AppStorageSettings.java @@ -491,7 +491,8 @@ public class AppStorageSettings extends AppInfoWithHeader return new AlertDialog.Builder(getActivity()) .setTitle(getActivity().getText(R.string.clear_data_dlg_title)) .setMessage(getActivity().getText(R.string.clear_data_dlg_text)) - .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { + .setPositiveButton(R.string.dlg_delete, + new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // Clear user data here initiateClearUserData(); From baf2ef0be690dc03e12b4f4bdc7d95dd2b5f034b Mon Sep 17 00:00:00 2001 From: Quang Luong Date: Fri, 21 Jan 2022 14:56:07 -0800 Subject: [PATCH 05/10] Do update signal icon for -1 level in AddAppNetworksFragment The level of a WifiEntry may be -1 (WIFI_LEVEL_UNREACHABLE) due to race conditions. Thus, only update the signal icon if the level is a valid value [0, 4] Bug: 201488249 Test: m Change-Id: I87f1c108d262bb5a7575c9ad434d2cfb953134af (cherry picked from commit 9d66b22ee045637f7e331971e20f13d18b3435f0) --- .../settings/wifi/addappnetworks/AddAppNetworksFragment.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/com/android/settings/wifi/addappnetworks/AddAppNetworksFragment.java b/src/com/android/settings/wifi/addappnetworks/AddAppNetworksFragment.java index 46f44143bc7..3f5ef4805b8 100644 --- a/src/com/android/settings/wifi/addappnetworks/AddAppNetworksFragment.java +++ b/src/com/android/settings/wifi/addappnetworks/AddAppNetworksFragment.java @@ -414,6 +414,9 @@ public class AddAppNetworksFragment extends InstrumentedFragment implements } private void updateSingleNetworkSignalIcon(int level) { + if (level == WifiEntry.WIFI_LEVEL_UNREACHABLE) { + return; + } // TODO: Check level of the network to show signal icon. final Drawable wifiIcon = mActivity.getDrawable( Utils.getWifiIconResource(level)).mutate(); From 9be043e750940cae72c8780b4dbd6d66c8719442 Mon Sep 17 00:00:00 2001 From: tom hsu Date: Tue, 8 Mar 2022 17:40:16 +0800 Subject: [PATCH 06/10] [Panlingual] Revamp the panlingual UI in Settings. - Create a Activity to contain AppLocaleDetail and LocalePickerWithRegion - Update the Entry from apps language page Bug: 223089715 Test: local test pass Change-Id: Id01e93f3df32412c7323ca577a149009eb1862ad Merged-In: Id01e93f3df32412c7323ca577a149009eb1862ad --- AndroidManifest.xml | 4 +- res/layout/app_locale_picker.xml | 33 ++ res/xml/app_locale_details.xml | 16 +- src/com/android/settings/Settings.java | 2 - .../appinfo/AppLocaleDetails.java | 403 +++++----------- .../ManageApplications.java | 6 +- .../localepicker/AppLocalePickerActivity.java | 100 ++++ .../LocalePickerWithRegionActivity.java | 34 +- .../appinfo/AppLocaleDetailsTest.java | 432 ------------------ 9 files changed, 273 insertions(+), 757 deletions(-) create mode 100644 res/layout/app_locale_picker.xml create mode 100644 src/com/android/settings/localepicker/AppLocalePickerActivity.java delete mode 100644 tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 214d32a3c17..70266b655bd 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -851,7 +851,7 @@ @@ -859,8 +859,6 @@ - + + + + + + + + + \ No newline at end of file diff --git a/res/xml/app_locale_details.xml b/res/xml/app_locale_details.xml index 40ca58243ec..05e72ee7c9c 100644 --- a/res/xml/app_locale_details.xml +++ b/res/xml/app_locale_details.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/app_locale_picker_title"> + - - - - - - - - diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 57d7d105188..10a47718e6f 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -108,8 +108,6 @@ public class Settings extends SettingsActivity { public static class InputMethodAndSubtypeEnablerActivity extends SettingsActivity { /* empty */ } public static class SpellCheckersSettingsActivity extends SettingsActivity { /* empty */ } public static class LocalePickerActivity extends SettingsActivity { /* empty */ } - /** Activity for the App locale details settings. */ - public static class AppLocalePickerActivity extends SettingsActivity { /* empty */ } public static class LanguageAndInputSettingsActivity extends SettingsActivity { /* empty */ } public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ } public static class DarkThemeSettingsActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/applications/appinfo/AppLocaleDetails.java b/src/com/android/settings/applications/appinfo/AppLocaleDetails.java index d50a67b8305..5f75b6b3a6d 100644 --- a/src/com/android/settings/applications/appinfo/AppLocaleDetails.java +++ b/src/com/android/settings/applications/appinfo/AppLocaleDetails.java @@ -22,73 +22,64 @@ import android.app.LocaleConfig; import android.app.LocaleManager; import android.app.settings.SettingsEnums; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.os.Bundle; import android.os.LocaleList; -import android.telephony.TelephonyManager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import androidx.annotation.VisibleForTesting; -import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; -import androidx.preference.PreferenceGroup; import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.widget.LayoutPreference; -import com.android.settingslib.widget.RadioButtonPreference; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; import java.util.Locale; /** - * A fragment to show the current app locale info and help the user to select the expected locale. + * TODO(b/223503670): Implement the unittest. + * A fragment to show the current app locale info. */ -public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreference.OnClickListener { +public class AppLocaleDetails extends SettingsPreferenceFragment { private static final String TAG = "AppLocaleDetails"; - private static final String CATEGORY_KEY_SUGGESTED_LANGUAGES = - "category_key_suggested_languages"; - private static final String CATEGORY_KEY_ALL_LANGUAGES = - "category_key_all_languages"; private static final String KEY_APP_DESCRIPTION = "app_locale_description"; - @VisibleForTesting - static final String KEY_SYSTEM_DEFAULT_LOCALE = "system_default_locale"; private boolean mCreated = false; - @VisibleForTesting - AppLocaleDetailsHelper mAppLocaleDetailsHelper; - - private PreferenceGroup mGroupOfSuggestedLocales; - private PreferenceGroup mGroupOfSupportedLocales; + private String mPackageName; private LayoutPreference mPrefOfDescription; - private RadioButtonPreference mDefaultPreference; + + /** + * Create a instance of AppLocaleDetails. + * @param packageName Indicates which application need to show the locale picker. + */ + public static AppLocaleDetails newInstance(String packageName) { + AppLocaleDetails appLocaleDetails = new AppLocaleDetails(); + Bundle bundle = new Bundle(); + bundle.putString(AppInfoBase.ARG_PACKAGE_NAME, packageName); + appLocaleDetails.setArguments(bundle); + return appLocaleDetails; + } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.app_locale_details); - mAppLocaleDetailsHelper = new AppLocaleDetailsHelper(getContext(), mPackageName); + Bundle bundle = getArguments(); + mPackageName = bundle.getString(AppInfoBase.ARG_PACKAGE_NAME, ""); - mGroupOfSuggestedLocales = - getPreferenceScreen().findPreference(CATEGORY_KEY_SUGGESTED_LANGUAGES); - mGroupOfSupportedLocales = - getPreferenceScreen().findPreference(CATEGORY_KEY_ALL_LANGUAGES); - mPrefOfDescription = getPreferenceScreen().findPreference(KEY_APP_DESCRIPTION); - - mDefaultPreference = (RadioButtonPreference) getPreferenceScreen() - .findPreference(KEY_SYSTEM_DEFAULT_LOCALE); - mDefaultPreference.setOnClickListener(this); + if (mPackageName.isEmpty()) { + Log.d(TAG, "No package name."); + finish(); + } } // Override here so we don't have an empty screen @@ -96,8 +87,8 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // if we don't have a package info, show a page saying this is unsupported - if (mPackageInfo == null) { + // if we don't have a package, show a page saying this is unsupported + if (mPackageName.isEmpty()) { return inflater.inflate(R.layout.manage_applications_apps_unsupported, null); } return super.onCreateView(inflater, container, savedInstanceState); @@ -105,46 +96,19 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen @Override public void onResume() { - // Update Locales first, before refresh ui. - mAppLocaleDetailsHelper.handleAllLocalesData(); - super.onResume(); - mDefaultPreference.setSummary(Locale.getDefault().getDisplayName(Locale.getDefault())); - } - - @Override - protected boolean refreshUi() { refreshUiInternal(); - return true; + super.onResume(); } - @VisibleForTesting - void refreshUiInternal() { - if (mAppLocaleDetailsHelper.getSupportedLocales().isEmpty()) { + private void refreshUiInternal() { + if (!hasAppSupportedLocales()) { Log.d(TAG, "No supported language."); - mGroupOfSuggestedLocales.setVisible(false); - mGroupOfSupportedLocales.setVisible(false); mPrefOfDescription.setVisible(true); TextView description = (TextView) mPrefOfDescription.findViewById(R.id.description); description.setText(getContext().getString(R.string.no_multiple_language_supported, Locale.getDefault().getDisplayName(Locale.getDefault()))); return; } - resetLocalePreferences(); - Locale appLocale = AppLocaleDetailsHelper.getAppDefaultLocale(getContext(), mPackageName); - // Sets up default locale preference. - mGroupOfSuggestedLocales.addPreference(mDefaultPreference); - mDefaultPreference.setChecked(appLocale == null); - // Sets up suggested locales of per app. - setLanguagesPreference(mGroupOfSuggestedLocales, - mAppLocaleDetailsHelper.getSuggestedLocales(), appLocale); - // Sets up supported locales of per app. - setLanguagesPreference(mGroupOfSupportedLocales, - mAppLocaleDetailsHelper.getSupportedLocales(), appLocale); - } - - private void resetLocalePreferences() { - mGroupOfSuggestedLocales.removeAll(); - mGroupOfSupportedLocales.removeAll(); } @Override @@ -152,22 +116,6 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen return SettingsEnums.APPS_LOCALE_LIST; } - @Override - protected AlertDialog createDialog(int id, int errorCode) { - return null; - } - - @Override - public void onRadioButtonClicked(RadioButtonPreference pref) { - String key = pref.getKey(); - if (KEY_SYSTEM_DEFAULT_LOCALE.equals(key)) { - mAppLocaleDetailsHelper.setAppDefaultLocale(LocaleList.forLanguageTags("")); - } else { - mAppLocaleDetailsHelper.setAppDefaultLocale(key); - } - refreshUi(); - } - @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -176,32 +124,98 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen return; } mCreated = true; - if (mPackageInfo == null) { + if (mPackageName == null) { return; } // Creates a head icon button of app on this page. final Activity activity = getActivity(); + ApplicationInfo applicationInfo = + getApplicationInfo(mPackageName, getContext().getUserId()); final Preference pref = EntityHeaderController .newInstance(activity, this, null /* header */) .setRecyclerView(getListView(), getSettingsLifecycle()) - .setIcon(Utils.getBadgedIcon(getContext(), mPackageInfo.applicationInfo)) - .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm)) - .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo)) + .setIcon(Utils.getBadgedIcon(getContext(), applicationInfo)) + .setLabel(applicationInfo.loadLabel(getContext().getPackageManager())) + .setIsInstantApp(AppUtils.isInstant(applicationInfo)) .setPackageName(mPackageName) - .setUid(mPackageInfo.applicationInfo.uid) + .setUid(applicationInfo.uid) .setHasAppInfoLink(true) .setButtonActions(ActionType.ACTION_NONE, ActionType.ACTION_NONE) .done(activity, getPrefContext()); getPreferenceScreen().addPreference(pref); } + private ApplicationInfo getApplicationInfo(String packageName, int userId) { + ApplicationInfo applicationInfo; + try { + applicationInfo = getContext().getPackageManager() + .getApplicationInfoAsUser(packageName, /* flags= */ 0, userId); + return applicationInfo; + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Application info not found for: " + packageName); + return null; + } + } + + private boolean hasAppSupportedLocales() { + LocaleList localeList = getPackageLocales(); + return (localeList != null && localeList.size() > 0) || getAssetLocales().length > 0; + } + + private String[] getAssetLocales() { + try { + PackageManager packageManager = getContext().getPackageManager(); + String[] locales = packageManager.getResourcesForApplication( + packageManager.getPackageInfo(mPackageName, PackageManager.MATCH_ALL) + .applicationInfo).getAssets().getNonSystemLocales(); + if (locales == null) { + Log.i(TAG, "[" + mPackageName + "] locales are null."); + } + if (locales.length <= 0) { + Log.i(TAG, "[" + mPackageName + "] locales length is 0."); + return new String[0]; + } + String locale = locales[0]; + Log.i(TAG, "First asset locale - [" + mPackageName + "] " + locale); + return locales; + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); + } + return new String[0]; + } + + private LocaleList getPackageLocales() { + try { + LocaleConfig localeConfig = + new LocaleConfig(getContext().createPackageContext(mPackageName, 0)); + if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) { + return localeConfig.getSupportedLocales(); + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); + } + return null; + } + + /** Gets per app's default locale */ + public static Locale getAppDefaultLocale(Context context, String packageName) { + LocaleManager localeManager = context.getSystemService(LocaleManager.class); + try { + LocaleList localeList = (localeManager == null) + ? null : localeManager.getApplicationLocales(packageName); + return localeList == null ? null : localeList.get(0); + } catch (IllegalArgumentException e) { + Log.w(TAG, "package name : " + packageName + " is not correct. " + e); + } + return null; + } + /** * TODO (b209962418) Do a performance test to low end device. * @return Return the summary to show the current app's language. */ public static CharSequence getSummary(Context context, String packageName) { - Locale appLocale = - AppLocaleDetailsHelper.getAppDefaultLocale(context, packageName); + Locale appLocale = getAppDefaultLocale(context, packageName); if (appLocale == null) { Locale systemLocale = Locale.getDefault(); return context.getString(R.string.preference_of_system_locale_summary, @@ -210,217 +224,4 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen return appLocale.getDisplayName(appLocale); } } - - private void setLanguagesPreference(PreferenceGroup group, - Collection locales, Locale appLocale) { - if (locales == null) { - return; - } - - for (Locale locale : locales) { - if (locale == null) { - continue; - } - - RadioButtonPreference pref = new RadioButtonPreference(getContext()); - pref.setTitle(locale.getDisplayName(locale)); - pref.setKey(locale.toLanguageTag()); - // Will never be checked if appLocale is null - // aka if there is no per-app locale - pref.setChecked(locale.equals(appLocale)); - pref.setOnClickListener(this); - group.addPreference(pref); - } - } - - @VisibleForTesting - static class AppLocaleDetailsHelper { - private String mPackageName; - private Context mContext; - private TelephonyManager mTelephonyManager; - private LocaleManager mLocaleManager; - - private Collection mProcessedSuggestedLocales = new ArrayList<>(); - private Collection mProcessedSupportedLocales = new ArrayList<>(); - - private Collection mAppSupportedLocales = new ArrayList<>(); - - AppLocaleDetailsHelper(Context context, String packageName) { - mContext = context; - mPackageName = packageName; - mTelephonyManager = context.getSystemService(TelephonyManager.class); - mLocaleManager = context.getSystemService(LocaleManager.class); - mAppSupportedLocales = getAppSupportedLocales(); - } - - /** Handle suggested and supported locales for UI display. */ - public void handleAllLocalesData() { - clearLocalesData(); - handleSuggestedLocales(); - handleSupportedLocales(); - } - - /** Gets suggested locales in the app. */ - public Collection getSuggestedLocales() { - return mProcessedSuggestedLocales; - } - - /** Gets supported locales in the app. */ - public Collection getSupportedLocales() { - return mProcessedSupportedLocales; - } - - @VisibleForTesting - void handleSuggestedLocales() { - Locale appLocale = getAppDefaultLocale(mContext, mPackageName); - // 1st locale in suggested languages group. - for (Locale supportedlocale : mAppSupportedLocales) { - if (compareLocale(supportedlocale, appLocale)) { - mProcessedSuggestedLocales.add(appLocale); - break; - } - } - - // 2nd and 3rd locale in suggested languages group. - String simCountry = mTelephonyManager.getSimCountryIso().toUpperCase(Locale.US); - String networkCountry = mTelephonyManager.getNetworkCountryIso().toUpperCase(Locale.US); - mAppSupportedLocales.forEach(supportedlocale -> { - String localeCountry = supportedlocale.getCountry().toUpperCase(Locale.US); - if (!compareLocale(supportedlocale, appLocale) - && isCountrySuggestedLocale(localeCountry, simCountry, networkCountry)) { - mProcessedSuggestedLocales.add(supportedlocale); - } - }); - - // Other locales in suggested languages group. - Collection supportedSystemLocales = new HashSet<>(); - getCurrentSystemLocales().forEach(systemLocale -> { - mAppSupportedLocales.forEach(supportedLocale -> { - if (compareLocale(systemLocale, supportedLocale)) { - supportedSystemLocales.add(supportedLocale); - } - }); - }); - supportedSystemLocales.removeAll(mProcessedSuggestedLocales); - mProcessedSuggestedLocales.addAll(supportedSystemLocales); - } - - @VisibleForTesting - static boolean compareLocale(Locale source, Locale target) { - if (source == null && target == null) { - return true; - } else if (source != null && target != null) { - return LocaleList.matchesLanguageAndScript(source, target); - } else { - return false; - } - } - - private static boolean isCountrySuggestedLocale(String localeCountry, - String simCountry, - String networkCountry) { - return ((!simCountry.isEmpty() && simCountry.equals(localeCountry)) - || (!networkCountry.isEmpty() && networkCountry.equals(localeCountry))); - } - - @VisibleForTesting - void handleSupportedLocales() { - mProcessedSupportedLocales.addAll(mAppSupportedLocales); - - if (mProcessedSuggestedLocales != null || !mProcessedSuggestedLocales.isEmpty()) { - mProcessedSuggestedLocales.retainAll(mProcessedSupportedLocales); - mProcessedSupportedLocales.removeAll(mProcessedSuggestedLocales); - } - } - - private void clearLocalesData() { - mProcessedSuggestedLocales.clear(); - mProcessedSupportedLocales.clear(); - } - - private Collection getAppSupportedLocales() { - Collection appSupportedLocales = new ArrayList<>(); - LocaleList localeList = getPackageLocales(); - - if (localeList != null && localeList.size() > 0) { - for (int i = 0; i < localeList.size(); i++) { - appSupportedLocales.add(localeList.get(i)); - } - } else { - String[] languages = getAssetLocales(); - for (String language : languages) { - appSupportedLocales.add(Locale.forLanguageTag(language)); - } - } - return appSupportedLocales; - } - - /** Gets per app's default locale */ - public static Locale getAppDefaultLocale(Context context, String packageName) { - LocaleManager localeManager = context.getSystemService(LocaleManager.class); - try { - LocaleList localeList = (localeManager == null) - ? null : localeManager.getApplicationLocales(packageName); - return localeList == null ? null : localeList.get(0); - } catch (IllegalArgumentException e) { - Log.w(TAG, "package name : " + packageName + " is not correct. " + e); - } - return null; - } - - /** Sets per app's default language to system. */ - public void setAppDefaultLocale(String languageTag) { - if (languageTag.isEmpty()) { - Log.w(TAG, "[setAppDefaultLocale] No language tag."); - return; - } - setAppDefaultLocale(LocaleList.forLanguageTags(languageTag)); - } - - /** Sets per app's default language to system. */ - public void setAppDefaultLocale(LocaleList localeList) { - if (mLocaleManager == null) { - Log.w(TAG, "LocaleManager is null, and cannot set the app locale up."); - return; - } - mLocaleManager.setApplicationLocales(mPackageName, localeList); - } - - @VisibleForTesting - Collection getCurrentSystemLocales() { - LocaleList localeList = Resources.getSystem().getConfiguration().getLocales(); - Collection systemLocales = new ArrayList<>(); - for (int i = 0; i < localeList.size(); i++) { - systemLocales.add(localeList.get(i)); - } - return systemLocales; - } - - @VisibleForTesting - String[] getAssetLocales() { - try { - PackageManager packageManager = mContext.getPackageManager(); - return packageManager.getResourcesForApplication( - packageManager.getPackageInfo(mPackageName, PackageManager.MATCH_ALL) - .applicationInfo).getAssets().getNonSystemLocales(); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); - } - return new String[0]; - } - - @VisibleForTesting - LocaleList getPackageLocales() { - try { - LocaleConfig localeConfig = - new LocaleConfig(mContext.createPackageContext(mPackageName, 0)); - if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) { - return localeConfig.getSupportedLocales(); - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Can not found the package name : " + mPackageName + " / " + e); - } - return null; - } - } } diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index a6ce6fbeb84..b8a0d22f9b3 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -120,6 +120,7 @@ import com.android.settings.core.InstrumentedFragment; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.profileselector.ProfileSelectFragment; import com.android.settings.fuelgauge.HighPowerDetail; +import com.android.settings.localepicker.AppLocalePickerActivity; import com.android.settings.notification.ConfigureNotificationSettings; import com.android.settings.notification.NotificationBackend; import com.android.settings.notification.app.AppNotificationSettings; @@ -635,8 +636,9 @@ public class ManageApplications extends InstrumentedFragment R.string.media_management_apps_title); break; case LIST_TYPE_APPS_LOCALE: - startAppInfoFragment(AppLocaleDetails.class, - R.string.app_locale_picker_title); + Intent intent = new Intent(getContext(), AppLocalePickerActivity.class); + intent.setData(Uri.parse("package:" + mCurrentPkgName)); + startActivity(intent); break; // TODO: Figure out if there is a way where we can spin up the profile's settings // process ahead of time, to avoid a long load of data when user clicks on a managed diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java new file mode 100644 index 00000000000..80d3336c181 --- /dev/null +++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.localepicker; + +import android.app.FragmentTransaction; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.MenuItem; + +import com.android.internal.app.LocalePickerWithRegion; +import com.android.internal.app.LocaleStore; +import com.android.settings.R; +import com.android.settings.applications.appinfo.AppLocaleDetails; +import com.android.settings.core.SettingsBaseActivity; + +/** + * TODO(b/223503670): Add unit test for AppLocalePickerActivity. + * A activity to show the locale picker and information page. + */ +public class AppLocalePickerActivity extends SettingsBaseActivity + implements LocalePickerWithRegion.LocaleSelectedListener { + private static final String TAG = AppLocalePickerActivity.class.getSimpleName(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + String packageName = getIntent().getData().getSchemeSpecificPart(); + if (TextUtils.isEmpty(packageName)) { + Log.d(TAG, "There is no package name."); + finish(); + return; + } + + getActionBar().setDisplayHomeAsUpEnabled(true); + setContentView(R.layout.app_locale_picker); + + // Create App locale info detail part. + AppLocaleDetails appLocaleDetails = AppLocaleDetails.newInstance(packageName); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.app_locale_detail, appLocaleDetails) + .commit(); + + // Create Locale picker part. + final LocalePickerWithRegion selector = LocalePickerWithRegion.createLanguagePicker( + this, AppLocalePickerActivity.this, false /* translate only */); + // LocalePickerWithRegion use android.app.ListFragment. Thus, it can not user + // getSupportFragmentManager() to add this into container. + getFragmentManager() + .beginTransaction() + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + .replace(R.id.app_locale_picker_with_region, selector) + .commit(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + handleBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onBackPressed() { + handleBackPressed(); + } + + private void handleBackPressed() { + if (getFragmentManager().getBackStackEntryCount() > 1) { + super.onBackPressed(); + } else { + setResult(RESULT_CANCELED); + finish(); + } + } + + @Override + public void onLocaleSelected(LocaleStore.LocaleInfo locale) { + // TODO When locale is selected, this shall set per app language here. + finish(); + } +} + diff --git a/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java b/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java index bcc55e3a2db..70f738395be 100644 --- a/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java +++ b/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java @@ -17,8 +17,11 @@ package com.android.settings.localepicker; import android.app.FragmentTransaction; +import android.app.LocaleManager; import android.content.Intent; import android.os.Bundle; +import android.os.LocaleList; +import android.util.Log; import android.view.MenuItem; import com.android.internal.app.LocalePickerWithRegion; @@ -31,6 +34,7 @@ public class LocalePickerWithRegionActivity extends SettingsBaseActivity implements LocalePickerWithRegion.LocaleSelectedListener { private static final String PARENT_FRAGMENT_NAME = "localeListEditor"; + private static final String TAG = "Calvin"; @Override public void onCreate(Bundle savedInstanceState) { @@ -47,6 +51,25 @@ public class LocalePickerWithRegionActivity extends SettingsBaseActivity .commit(); } + public void setAppDefaultLocale(String languageTag) { + if (languageTag.isEmpty()) { + Log.w(TAG, "[setAppDefaultLocale] No language tag."); + return; + } + setAppDefaultLocale(LocaleList.forLanguageTags(languageTag)); + } + + /** Sets per app's default language to system. */ + public void setAppDefaultLocale(LocaleList localeList) { + LocaleManager mLocaleManager = getSystemService(LocaleManager.class); + if (mLocaleManager == null) { + Log.w(TAG, "LocaleManager is null, and cannot set the app locale up."); + return; + } + mLocaleManager.setApplicationLocales("com.android.vending", localeList); + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { @@ -58,9 +81,16 @@ public class LocalePickerWithRegionActivity extends SettingsBaseActivity @Override public void onLocaleSelected(LocaleStore.LocaleInfo locale) { - final Intent intent = new Intent(); + /*final Intent intent = new Intent(); intent.putExtra(LocaleListEditor.INTENT_LOCALE_KEY, locale); - setResult(RESULT_OK, intent); + setResult(RESULT_OK, intent);*/ + if(locale != null) { + Log.d("Calvin", "onLocaleSelected " + locale.getLocale().toLanguageTag()); + setAppDefaultLocale(locale.getLocale().toLanguageTag()); + } else { + Log.d("Calvin", "onLocaleSelected null"); + setAppDefaultLocale(""); + } finish(); } diff --git a/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java b/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java deleted file mode 100644 index aa0daad44ff..00000000000 --- a/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright (C) 2021 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.appinfo; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.LocaleManager; -import android.content.Context; -import android.os.LocaleList; -import android.os.Looper; -import android.telephony.TelephonyManager; - -import androidx.test.annotation.UiThreadTest; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.android.settingslib.widget.RadioButtonPreference; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; - -/** - * Unittest for ApplocaleDetails - * TODO Need to add a unittest for the UI preference component. - */ -@RunWith(AndroidJUnit4.class) -public class AppLocaleDetailsTest { - private static final String APP_PACKAGE_NAME = "app_package_name"; - - @Mock - private TelephonyManager mTelephonyManager; - @Mock - private LocaleManager mLocaleManager; - - private Context mContext; - private Collection mSystemLocales; - private LocaleList mAppLocale; - private String[] mAssetLocales; - private LocaleList mPackageLocales; - - @Before - @UiThreadTest - public void setUp() { - MockitoAnnotations.initMocks(this); - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mContext = spy(ApplicationProvider.getApplicationContext()); - when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); - when(mContext.getSystemService(LocaleManager.class)).thenReturn(mLocaleManager); - - setupInitialLocales( - /* appLocale= */ "en-gb", - /* simCountry= */ "tw", - /* networkCountry= */ "jp", - /* systemLocales= */ "en-gb, ru, ja-jp, ne, zh-tw", - /* packageLocales= */ "pa, cn, zh-tw, en-gb, ja-jp", - /* assetLocales= */ new String[]{"en-gb", "ne", "ms", "pa", "zh-tw", "ja-jp"}); - } - - @Test - @UiThreadTest - public void onRadioButtonClicked_setCurrentLocaleToSystem() { - AppLocaleDetails appLocaleDetails = new AppLocaleDetails() { - @Override - void refreshUiInternal() {} - }; - DummyAppLocaleDetailsHelper helper = - spy(new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME)); - appLocaleDetails.mAppLocaleDetailsHelper = helper; - RadioButtonPreference pref = new RadioButtonPreference(mContext); - pref.setKey(AppLocaleDetails.KEY_SYSTEM_DEFAULT_LOCALE); - - appLocaleDetails.onRadioButtonClicked(pref); - - verify(helper).setAppDefaultLocale(LocaleList.forLanguageTags("")); - } - - @Test - @UiThreadTest - public void onRadioButtonClicked_setCurrentLocaleForUserSelected() { - AppLocaleDetails appLocaleDetails = new AppLocaleDetails() { - @Override - void refreshUiInternal() {} - }; - DummyAppLocaleDetailsHelper helper = - spy(new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME)); - appLocaleDetails.mAppLocaleDetailsHelper = helper; - RadioButtonPreference pref = new RadioButtonPreference(mContext); - pref.setKey("en"); - - appLocaleDetails.onRadioButtonClicked(pref); - - verify(helper).setAppDefaultLocale("en"); - } - - @Test - @UiThreadTest - public void handleAllLocalesData_localeManagerIsNull_noCrash() { - when(mContext.getSystemService(LocaleManager.class)).thenReturn(null); - - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - - helper.handleAllLocalesData(); - } - - @Test - @UiThreadTest - public void handleAllLocalesData_1stLocaleIsAppLocaleAndHasSimAndNetwork() { - Locale simCountryLocale = new Locale("zh", "TW"); - Locale networkCountryLocale = new Locale("ja", "JP"); - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - - helper.handleAllLocalesData(); - - Collection suggestedLocales = helper.getSuggestedLocales(); - Locale locale = suggestedLocales.iterator().next(); - assertTrue(locale.equals(mAppLocale.get(0))); - assertTrue(suggestedLocales.contains(simCountryLocale)); - assertTrue(suggestedLocales.contains(networkCountryLocale)); - } - - @Test - @UiThreadTest - public void - handleAllLocalesData_noAppAndNoSupportedSimLocale_suggestedLocaleIsSupported() { - Locale testEnAssetLocale = new Locale("en", "GB"); - Locale testJaAssetLocale = new Locale("ja", "JP"); - setupInitialLocales( - /* appLocale= */ "", - /* simCountry= */ "tw", - /* networkCountry= */ "", - /* systemLocales= */ "en-gb, ru, ja-jp, ne, zh-tw", - /* packageLocales= */ "", - /* assetLocales= */ new String[]{"en-gb", "ne", "ms", "pa", "ja-jp"}); - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - - helper.handleAllLocalesData(); - - Collection suggestedLocales = helper.getSuggestedLocales(); - assertTrue(suggestedLocales.contains(testEnAssetLocale)); - assertTrue(suggestedLocales.contains(testJaAssetLocale)); - } - - @Test - @UiThreadTest - public void handleAllLocalesData_noAppButHasSupportedSimLocale_1stSuggestedLocaleIsSim() { - Locale simLocale = new Locale("zh", "tw"); - setupInitialLocales( - /* appLocale= */ "", - /* simCountry= */ "tw", - /* networkCountry= */ "", - /* systemLocales= */ "en-gb, ru, ja-jp, ne, zh-tw", - /* packageLocales= */ "", - /* assetLocales= */ new String[]{"en-gb", "ne", "ms", "pa", "ja-jp", "zh-tw"}); - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - - helper.handleAllLocalesData(); - - Collection suggestedLocales = helper.getSuggestedLocales(); - Locale locale = suggestedLocales.iterator().next(); - assertTrue(locale.equals(simLocale)); - } - - @Test - @UiThreadTest - public void - handleAllLocalesData_noAppButHasSupportedNetworkLocale_1stSuggestedLocaleIsNetwork() { - Locale networkLocale = new Locale("ja", "JP"); - setupInitialLocales( - /* appLocale= */ "", - /* simCountry= */ "", - /* networkCountry= */ "jp", - /* systemLocales= */ "en-gb, ru, ja-jp, ne, zh-tw", - /* packageLocales= */ "", - /* assetLocales= */ new String[]{"en-gb", "ne", "ms", "pa", "ja-jp"}); - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - - helper.handleAllLocalesData(); - - Collection suggestedLocales = helper.getSuggestedLocales(); - Locale locale = suggestedLocales.iterator().next(); - assertTrue(locale.equals(networkLocale)); - } - - @Test - @UiThreadTest - public void handleAllLocalesData_noAppSimOrNetworkLocale_suggestedLocalesHasSystemLocale() { - setupInitialLocales( - /* appLocale= */ "", - /* simCountry= */ "", - /* networkCountry= */ "", - /* systemLocales= */ "en-gb, ru, ja-jp, ne, zh-tw", - /* packageLocales= */ "", - /* assetLocales= */ new String[]{"en-gb", "ne", "ms", "pa", "zh-tw", "ja-jp"}); - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - helper.handleAllLocalesData(); - - Collection suggestedLocales = helper.getSuggestedLocales(); - assertTrue(suggestedLocales.contains(Locale.forLanguageTag("ne"))); - // ru language is not present in the asset locales - assertFalse(suggestedLocales.contains(Locale.forLanguageTag("ru"))); - } - - @Test - @UiThreadTest - public void handleAllLocalesData_noAppButHasSimAndNetworkLocale_1stLocaleIsSimLocale() { - Locale simCountryLocale = new Locale("zh", "TW"); - setupInitialLocales( - /* appLocale= */ "", - /* simCountry= */ "tw", - /* networkCountry= */ "jp", - /* systemLocales= */ "en-gb, ru, ja-jp, ne, zh-tw", - /* packageLocales= */ "", - /* assetLocales= */ new String[]{"en-gb", "ne", "ms", "pa", "zh-tw", "ja-jp"}); - - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - helper.handleAllLocalesData(); - - Collection suggestedLocales = helper.getSuggestedLocales(); - Locale locale = suggestedLocales.iterator().next(); - assertTrue(locale.equals(simCountryLocale)); - } - - @Test - @UiThreadTest - public void handleAllLocalesData_noSupportedLocale_noSuggestedLocales() { - Locale networkCountryLocale = new Locale("en", "GB"); - setupInitialLocales( - /* appLocale= */ "", - /* simCountry= */ "", - /* networkCountry= */ "gb", - /* systemLocales= */ "en, uk, jp, ne", - /* packageLocales= */ "", - /* assetLocales= */ new String[]{}); - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - - helper.handleAllLocalesData(); - - Collection suggestedLocales = helper.getSuggestedLocales(); - assertTrue(suggestedLocales.size() == 0); - } - - @Test - @UiThreadTest - public void handleAllLocalesData_hasPackageAndSystemLocales_1stLocaleIs1stOneInSystemLocales() { - setupInitialLocales( - /* appLocale= */ "", - /* simCountry= */ "", - /* networkCountry= */ "", - /* systemLocales= */ "en, uk, jp, ne", - /* packageLocales= */ "pa, cn, tw, en", - /* assetLocales= */ new String[]{}); - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - - helper.handleAllLocalesData(); - - Collection suggestedLocales = helper.getSuggestedLocales(); - Locale locale = suggestedLocales.iterator().next(); - Locale systemLocale = mSystemLocales.iterator().next(); - assertTrue(locale.equals(systemLocale)); - } - - @Test - @UiThreadTest - public void handleAllLocalesData_sameLocaleButDifferentRegion_notShowDuplicatedLocale() { - setupInitialLocales( - /* appLocale= */ "", - /* simCountry= */ "", - /* networkCountry= */ "", - /* systemLocales= */ "en-us, en-gb, jp, ne", - /* packageLocales= */ "pa, cn, tw, en-us, en-gb", - /* assetLocales= */ new String[]{}); - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - - helper.handleAllLocalesData(); - - Collection suggestedLocales = helper.getSuggestedLocales(); - assertFalse(hasDuplicatedResult(suggestedLocales)); - } - - private boolean hasDuplicatedResult(Collection locales) { - Set tempSet = new HashSet<>(); - for (Locale locale : locales) { - if (!tempSet.add(locale)) { - return true; - } - } - return false; - } - - @Test - @UiThreadTest - public void handleAllLocalesData_supportLocaleListIsNotEmpty() { - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - - helper.handleAllLocalesData(); - - assertFalse(helper.getSupportedLocales().isEmpty()); - } - - @Test - @UiThreadTest - public void handleAllLocalesData_compareLocale() { - //Use LocaleList.matchScore() to compare two locales. - assertTrue(DummyAppLocaleDetailsHelper.compareLocale(Locale.forLanguageTag("en-US"), - Locale.forLanguageTag("en-CA"))); - assertTrue(DummyAppLocaleDetailsHelper.compareLocale(Locale.forLanguageTag("zh-CN"), - Locale.forLanguageTag("zh"))); - assertTrue(DummyAppLocaleDetailsHelper.compareLocale(Locale.forLanguageTag("zh-CN"), - Locale.forLanguageTag("zh-Hans"))); - assertTrue(DummyAppLocaleDetailsHelper.compareLocale(Locale.forLanguageTag("zh-TW"), - Locale.forLanguageTag("zh-Hant"))); - - //Use Locale.equals() to compare two locales. - assertFalse(Locale.forLanguageTag("en-US").equals(Locale.forLanguageTag("en-CA"))); - assertFalse(Locale.forLanguageTag("zh-CN").equals(Locale.forLanguageTag("zh"))); - assertFalse(Locale.forLanguageTag("zh-CN").equals(Locale.forLanguageTag("zh-Hans"))); - assertFalse(Locale.forLanguageTag("zh-TW").equals(Locale.forLanguageTag("zh-Hant"))); - } - - /** - * Sets the initial Locale data - * - * @param appLocale Application locale, it shall be a language tag. - * example: "en" - * - * @param simCountry The ISO-3166-1 alpha-2 country code equivalent for the SIM - * provider's country code. - * example: "us" - * - * @param networkCountry The ISO-3166-1 alpha-2 country code equivalent of the MCC - * (Mobile Country Code) of the current registered operato - * or the cell nearby. - * example: "us" - * - * @param systemLocales System locales, a locale list by a multiple language tags with comma. - * example: "en, uk, jp" - * - * @param packageLocales PackageManager locales, a locale list by a multiple language tags with - * comma. - * example: "en, uk, jp" - * - * @param assetLocales Asset locales, a locale list by a multiple language tags with String - * array. - * example: new String[] {"en", "ne", "ms", "pa"} - */ - private void setupInitialLocales(String appLocale, - String simCountry, - String networkCountry, - String systemLocales, - String packageLocales, - String[] assetLocales) { - mAppLocale = LocaleList.forLanguageTags(appLocale); - // forLanguageTags does not filter space to the input string. If there is any space included - // in string, this will make locale fail to generate. - systemLocales = systemLocales.replaceAll("\\s+", ""); - LocaleList listOfSystemLocales = LocaleList.forLanguageTags(systemLocales); - mSystemLocales = new ArrayList<>(); - for (int i = 0; i < listOfSystemLocales.size(); i++) { - mSystemLocales.add(listOfSystemLocales.get(i)); - } - mAssetLocales = assetLocales; - packageLocales = packageLocales.replaceAll("\\s+", ""); - mPackageLocales = LocaleList.forLanguageTags(packageLocales); - when(mTelephonyManager.getSimCountryIso()).thenReturn(simCountry); - when(mTelephonyManager.getNetworkCountryIso()).thenReturn(networkCountry); - when(mLocaleManager.getApplicationLocales(anyString())).thenReturn(mAppLocale); - } - - public class DummyAppLocaleDetailsHelper - extends AppLocaleDetails.AppLocaleDetailsHelper { - - DummyAppLocaleDetailsHelper(Context context, String packageName) { - super(context, packageName); - } - - @Override - String[] getAssetLocales() { - return mAssetLocales; - } - - @Override - Collection getCurrentSystemLocales() { - return mSystemLocales; - } - - @Override - LocaleList getPackageLocales() { - return mPackageLocales; - } - } -} From 523bd808627ab1c183c4bac4e6f5cd160ccb3650 Mon Sep 17 00:00:00 2001 From: SongFerngWang Date: Tue, 29 Mar 2022 22:23:11 +0800 Subject: [PATCH 07/10] [MEP] The progress dialog use the same string for psim and esim cases 1. The progress dialog use the same string for psim and esim cases 2. Add the radius on progress dialog Bug: 227284982 Test: manual test. Change-Id: Ia54a4bad94c88bb4efb0bd2f39627bf94092e3bc --- .../sim_progress_dialog_rounded_bg.xml | 27 +++++++++++++++++++ .../telephony/ProgressDialogFragment.java | 3 +++ .../ToggleSubscriptionDialogActivity.java | 12 ++++----- 3 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 res/drawable/sim_progress_dialog_rounded_bg.xml diff --git a/res/drawable/sim_progress_dialog_rounded_bg.xml b/res/drawable/sim_progress_dialog_rounded_bg.xml new file mode 100644 index 00000000000..b29ee433823 --- /dev/null +++ b/res/drawable/sim_progress_dialog_rounded_bg.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/src/com/android/settings/network/telephony/ProgressDialogFragment.java b/src/com/android/settings/network/telephony/ProgressDialogFragment.java index 0d1b65716db..29f269b8566 100644 --- a/src/com/android/settings/network/telephony/ProgressDialogFragment.java +++ b/src/com/android/settings/network/telephony/ProgressDialogFragment.java @@ -26,6 +26,8 @@ import android.os.Bundle; import android.text.TextUtils; import android.view.KeyEvent; +import com.android.settings.R; + /** Fragment to show a progress dialog. */ public class ProgressDialogFragment extends DialogFragment { private static final String ARG_TITLE = "title"; @@ -83,6 +85,7 @@ public class ProgressDialogFragment extends DialogFragment { @SuppressWarnings("deprecation") // ProgressDialog is deprecated but is intended UX for now public Dialog onCreateDialog(Bundle savedInstanceState) { ProgressDialog dialog = new ProgressDialog(getActivity()); + dialog.getWindow().setBackgroundDrawableResource(R.drawable.sim_progress_dialog_rounded_bg); dialog.setCancelable(false); dialog.setCanceledOnTouchOutside(false); dialog.setMessage(getArguments().getString(ARG_TITLE)); diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java index cc2986d5344..2616a69ba1d 100644 --- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java +++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java @@ -217,19 +217,17 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc } case DIALOG_TAG_ENABLE_SIM_CONFIRMATION: Log.i(TAG, "User confirmed to enable the subscription."); + showProgressDialog( + getString( + R.string.sim_action_switch_sub_dialog_progress, + SubscriptionUtil.getUniqueSubscriptionDisplayName( + mSubInfo, this))); if (mIsEsimOperation) { - showProgressDialog( - getString( - R.string.sim_action_switch_sub_dialog_progress, - SubscriptionUtil.getUniqueSubscriptionDisplayName( - mSubInfo, this))); mSwitchToEuiccSubscriptionSidecar.run(mSubInfo.getSubscriptionId(), UiccSlotUtil.INVALID_PORT_ID, removedSubInfo); return; } - showProgressDialog( - getString(R.string.sim_action_enabling_sim_without_carrier_name)); mSwitchToRemovableSlotSidecar.run(UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, removedSubInfo); break; From 9acf3d78467afe7b43fd8316bacd367fddf40066 Mon Sep 17 00:00:00 2001 From: danielwbhuang Date: Thu, 31 Mar 2022 14:46:47 +0800 Subject: [PATCH 08/10] [Panlingual] Settings UI revamp - Navigation handling Entry: Settings > Apps > All apps > any app Bug: 223090378 Test: local Change-Id: Ied357b60c103f25540981cc0cb7abadb020f7995 Merged-In: Ied357b60c103f25540981cc0cb7abadb020f7995 --- .../AppLocalePreferenceController.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java b/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java index eaa2ec00d76..38e655c7fc6 100644 --- a/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java @@ -17,14 +17,20 @@ package com.android.settings.applications.appinfo; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.text.TextUtils; import android.util.FeatureFlagUtils; +import android.util.Log; import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.applications.AppLocaleUtil; +import com.android.settings.localepicker.AppLocalePickerActivity; import java.util.List; @@ -59,6 +65,23 @@ public class AppLocalePreferenceController extends AppInfoPreferenceControllerBa return AppLocaleDetails.getSummary(mContext, mParent.getAppEntry().info.packageName); } + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), mPreferenceKey)) { + return false; + } + + if (mParent != null) { + Intent intent = new Intent(mContext, AppLocalePickerActivity.class); + intent.setData(Uri.parse("package:" + mParent.getAppEntry().info.packageName)); + mContext.startActivity(intent); + return true; + } else { + Log.d(TAG, "mParent is null"); + return false; + } + } + @VisibleForTesting boolean canDisplayLocaleUi() { return AppLocaleUtil From 963abde2dc6a95336a8cefddca366f93fef48fa8 Mon Sep 17 00:00:00 2001 From: danielwbhuang Date: Thu, 31 Mar 2022 18:05:04 +0800 Subject: [PATCH 09/10] [Panlingual] Handle onLocaleSelected. 1. When a new locale is selected, onLocaleSelected(...) will get the LocaleInfo. 2. Set LocaleList to LocaleManager for apps. 3. Check isSystemLocale(). Bug: 223090738 Test: local test pass Change-Id: I20bc2e2349d0bad123661e0964a4e271dd319147 Merged-In: I20bc2e2349d0bad123661e0964a4e271dd319147 --- .../localepicker/AppLocalePickerActivity.java | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java index 80d3336c181..b3c56eacddc 100644 --- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java +++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java @@ -17,7 +17,9 @@ package com.android.settings.localepicker; import android.app.FragmentTransaction; +import android.app.LocaleManager; import android.os.Bundle; +import android.os.LocaleList; import android.text.TextUtils; import android.util.Log; import android.view.MenuItem; @@ -36,11 +38,13 @@ public class AppLocalePickerActivity extends SettingsBaseActivity implements LocalePickerWithRegion.LocaleSelectedListener { private static final String TAG = AppLocalePickerActivity.class.getSimpleName(); + private String mPackageName; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - String packageName = getIntent().getData().getSchemeSpecificPart(); - if (TextUtils.isEmpty(packageName)) { + mPackageName = getIntent().getData().getSchemeSpecificPart(); + if (TextUtils.isEmpty(mPackageName)) { Log.d(TAG, "There is no package name."); finish(); return; @@ -50,7 +54,7 @@ public class AppLocalePickerActivity extends SettingsBaseActivity setContentView(R.layout.app_locale_picker); // Create App locale info detail part. - AppLocaleDetails appLocaleDetails = AppLocaleDetails.newInstance(packageName); + AppLocaleDetails appLocaleDetails = AppLocaleDetails.newInstance(mPackageName); getSupportFragmentManager() .beginTransaction() .replace(R.id.app_locale_detail, appLocaleDetails) @@ -92,9 +96,23 @@ public class AppLocalePickerActivity extends SettingsBaseActivity } @Override - public void onLocaleSelected(LocaleStore.LocaleInfo locale) { - // TODO When locale is selected, this shall set per app language here. + public void onLocaleSelected(LocaleStore.LocaleInfo localeInfo) { + if (localeInfo == null || localeInfo.getLocale() == null || localeInfo.isSystemLocale()) { + setAppDefaultLocale(""); + } else { + setAppDefaultLocale(localeInfo.getLocale().getLanguage()); + } finish(); } + + /** Sets the app's locale to the supplied language tag */ + private void setAppDefaultLocale(String languageTag) { + LocaleManager localeManager = getSystemService(LocaleManager.class); + if (localeManager == null) { + Log.w(TAG, "LocaleManager is null, cannot set default app locale"); + return; + } + localeManager.setApplicationLocales(mPackageName, LocaleList.forLanguageTags(languageTag)); + } } From f02c069f3d04135303f52ed1681e42a71109884a Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Tue, 22 Mar 2022 23:03:59 +0800 Subject: [PATCH 10/10] Extract inner helper from AccessibilitySettings to public * Need to be used in 'Connected device' page for hearing aid device Bug: 225117933 Bug: 227172850 Test: make RunSettingsRoboTests ROBOTEST_FILTER=RestrictedPreferenceHelperTest Change-Id: Icda456aa9c7597dc113775d1359acb0a8430768c --- .../accessibility/AccessibilitySettings.java | 315 +--------------- .../RestrictedPreferenceHelper.java | 337 ++++++++++++++++++ .../AccessibilitySettingsTest.java | 33 -- .../RestrictedPreferenceHelperTest.java | 133 +++++++ 4 files changed, 472 insertions(+), 346 deletions(-) create mode 100644 src/com/android/settings/accessibility/RestrictedPreferenceHelper.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index 680ddb74dac..ef7c2ba02e1 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -16,21 +16,13 @@ package com.android.settings.accessibility; -import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM; - import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityShortcutInfo; -import android.app.AppOpsManager; -import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.graphics.Color; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; @@ -40,22 +32,17 @@ import android.util.ArrayMap; import android.view.accessibility.AccessibilityManager; import androidx.annotation.VisibleForTesting; -import androidx.core.content.ContextCompat; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.internal.content.PackageMonitor; import com.android.settings.R; -import com.android.settings.Utils; import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; -import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedPreference; -import com.android.settingslib.accessibility.AccessibilityUtils; import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexableRaw; @@ -63,7 +50,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Set; /** Activity with the accessibility settings. */ @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) @@ -71,9 +57,6 @@ public class AccessibilitySettings extends DashboardFragment { private static final String TAG = "AccessibilitySettings"; - // Index of the first preference in a preference category. - private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1; - // Preference categories private static final String CATEGORY_SCREEN_READER = "screen_reader_category"; private static final String CATEGORY_CAPTIONS = "captions_category"; @@ -249,8 +232,7 @@ public class AccessibilitySettings extends DashboardFragment { * @param serviceEnabled Whether the accessibility service is enabled. * @return The service summary */ - @VisibleForTesting - static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info, + public static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info, boolean serviceEnabled) { if (serviceEnabled && info.crashed) { return context.getText(R.string.accessibility_summary_state_stopped); @@ -289,8 +271,7 @@ public class AccessibilitySettings extends DashboardFragment { * @param serviceEnabled Whether the accessibility service is enabled. * @return The service description */ - @VisibleForTesting - static CharSequence getServiceDescription(Context context, AccessibilityServiceInfo info, + public static CharSequence getServiceDescription(Context context, AccessibilityServiceInfo info, boolean serviceEnabled) { if (serviceEnabled && info.crashed) { return context.getText(R.string.accessibility_description_state_stopped); @@ -506,296 +487,4 @@ public class AccessibilitySettings extends DashboardFragment { context); } }; - - /** - * This class helps setup RestrictedPreference. - */ - @VisibleForTesting - static class RestrictedPreferenceHelper { - private final Context mContext; - private final DevicePolicyManager mDpm; - private final PackageManager mPm; - private final AppOpsManager mAppOps; - - RestrictedPreferenceHelper(Context context) { - mContext = context; - mDpm = context.getSystemService(DevicePolicyManager.class); - mPm = context.getPackageManager(); - mAppOps = context.getSystemService(AppOpsManager.class); - } - - /** - * Creates the list of {@link RestrictedPreference} with the installedServices arguments. - * - * @param installedServices The list of {@link AccessibilityServiceInfo}s of the - * installed accessibility services - * @return The list of {@link RestrictedPreference} - */ - @VisibleForTesting - List createAccessibilityServicePreferenceList( - List installedServices) { - - final Set enabledServices = - AccessibilityUtils.getEnabledServicesFromSettings(mContext); - final List permittedServices = mDpm.getPermittedAccessibilityServices( - UserHandle.myUserId()); - final int installedServicesSize = installedServices.size(); - - final List preferenceList = new ArrayList<>( - installedServicesSize); - - for (int i = 0; i < installedServicesSize; ++i) { - final AccessibilityServiceInfo info = installedServices.get(i); - final ResolveInfo resolveInfo = info.getResolveInfo(); - final String packageName = resolveInfo.serviceInfo.packageName; - final ComponentName componentName = new ComponentName(packageName, - resolveInfo.serviceInfo.name); - - final String key = componentName.flattenToString(); - final CharSequence title = resolveInfo.loadLabel(mPm); - final boolean serviceEnabled = enabledServices.contains(componentName); - final CharSequence summary = getServiceSummary(mContext, info, serviceEnabled); - final String fragment = getAccessibilityServiceFragmentTypeName(info); - - Drawable icon = resolveInfo.loadIcon(mPm); - if (resolveInfo.getIconResource() == 0) { - icon = ContextCompat.getDrawable(mContext, - R.drawable.ic_accessibility_generic); - } - - final RestrictedPreference preference = createRestrictedPreference(key, title, - summary, icon, fragment, packageName, - resolveInfo.serviceInfo.applicationInfo.uid); - - setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); - - final String prefKey = preference.getKey(); - final int imageRes = info.getAnimatedImageRes(); - final CharSequence intro = info.loadIntro(mPm); - final CharSequence description = getServiceDescription(mContext, info, - serviceEnabled); - final String htmlDescription = info.loadHtmlDescription(mPm); - final String settingsClassName = info.getSettingsActivityName(); - final String tileServiceClassName = info.getTileServiceName(); - - putBasicExtras(preference, prefKey, title, intro, description, imageRes, - htmlDescription, componentName); - putServiceExtras(preference, resolveInfo, serviceEnabled); - putSettingsExtras(preference, packageName, settingsClassName); - putTileServiceExtras(preference, packageName, tileServiceClassName); - - preferenceList.add(preference); - } - return preferenceList; - } - - /** - * Create the list of {@link RestrictedPreference} with the installedShortcuts arguments. - * - * @param installedShortcuts The list of {@link AccessibilityShortcutInfo}s of the - * installed accessibility shortcuts - * @return The list of {@link RestrictedPreference} - */ - @VisibleForTesting - List createAccessibilityActivityPreferenceList( - List installedShortcuts) { - final Set enabledServices = - AccessibilityUtils.getEnabledServicesFromSettings(mContext); - final List permittedServices = mDpm.getPermittedAccessibilityServices( - UserHandle.myUserId()); - - final int installedShortcutsSize = installedShortcuts.size(); - final List preferenceList = new ArrayList<>( - installedShortcutsSize); - - for (int i = 0; i < installedShortcutsSize; ++i) { - final AccessibilityShortcutInfo info = installedShortcuts.get(i); - final ActivityInfo activityInfo = info.getActivityInfo(); - final ComponentName componentName = info.getComponentName(); - - final String key = componentName.flattenToString(); - final CharSequence title = activityInfo.loadLabel(mPm); - final String summary = info.loadSummary(mPm); - final String fragment = - LaunchAccessibilityActivityPreferenceFragment.class.getName(); - - Drawable icon = activityInfo.loadIcon(mPm); - if (activityInfo.getIconResource() == 0) { - icon = ContextCompat.getDrawable(mContext, R.drawable.ic_accessibility_generic); - } - - final RestrictedPreference preference = createRestrictedPreference(key, title, - summary, icon, fragment, componentName.getPackageName(), - activityInfo.applicationInfo.uid); - final boolean serviceEnabled = enabledServices.contains(componentName); - - setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); - - final String prefKey = preference.getKey(); - final CharSequence intro = info.loadIntro(mPm); - final String description = info.loadDescription(mPm); - final int imageRes = info.getAnimatedImageRes(); - final String htmlDescription = info.loadHtmlDescription(mPm); - final String settingsClassName = info.getSettingsActivityName(); - final String tileServiceClassName = info.getTileServiceName(); - - putBasicExtras(preference, prefKey, title, intro, description, imageRes, - htmlDescription, componentName); - putSettingsExtras(preference, componentName.getPackageName(), settingsClassName); - putTileServiceExtras(preference, componentName.getPackageName(), - tileServiceClassName); - - preferenceList.add(preference); - } - return preferenceList; - } - - private String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) { - // Shorten the name to avoid exceeding 100 characters in one line. - final String volumeShortcutToggleAccessibilityServicePreferenceFragment = - VolumeShortcutToggleAccessibilityServicePreferenceFragment.class.getName(); - - switch (AccessibilityUtil.getAccessibilityServiceFragmentType(info)) { - case AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE: - return volumeShortcutToggleAccessibilityServicePreferenceFragment; - case AccessibilityServiceFragmentType.INVISIBLE_TOGGLE: - return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName(); - case AccessibilityServiceFragmentType.TOGGLE: - return ToggleAccessibilityServicePreferenceFragment.class.getName(); - default: - // impossible status - throw new AssertionError(); - } - } - - private RestrictedPreference createRestrictedPreference(String key, CharSequence title, - CharSequence summary, Drawable icon, String fragment, String packageName, int uid) { - final RestrictedPreference preference = new RestrictedPreference(mContext, packageName, - uid); - - preference.setKey(key); - preference.setTitle(title); - preference.setSummary(summary); - preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE)); - preference.setFragment(fragment); - preference.setIconSize(ICON_SIZE_MEDIUM); - preference.setPersistent(false); // Disable SharedPreferences. - preference.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX); - - return preference; - } - - private void setRestrictedPreferenceEnabled(RestrictedPreference preference, - final List permittedServices, boolean serviceEnabled) { - // permittedServices null means all accessibility services are allowed. - boolean serviceAllowed = permittedServices == null || permittedServices.contains( - preference.getPackageName()); - boolean appOpsAllowed; - if (serviceAllowed) { - try { - final int mode = mAppOps.noteOpNoThrow( - AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, - preference.getUid(), preference.getPackageName()); - appOpsAllowed = mode == AppOpsManager.MODE_ALLOWED; - serviceAllowed = appOpsAllowed; - } catch (Exception e) { - // Allow service in case if app ops is not available in testing. - appOpsAllowed = true; - } - } else { - appOpsAllowed = false; - } - if (serviceAllowed || serviceEnabled) { - preference.setEnabled(true); - } else { - // Disable accessibility service that are not permitted. - final EnforcedAdmin admin = - RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed( - mContext, preference.getPackageName(), UserHandle.myUserId()); - - if (admin != null) { - preference.setDisabledByAdmin(admin); - } else if (!appOpsAllowed) { - preference.setDisabledByAppOps(true); - } else { - preference.setEnabled(false); - } - } - } - - /** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */ - private void putBasicExtras(RestrictedPreference preference, String prefKey, - CharSequence title, CharSequence intro, CharSequence summary, int imageRes, - String htmlDescription, ComponentName componentName) { - final Bundle extras = preference.getExtras(); - extras.putString(EXTRA_PREFERENCE_KEY, prefKey); - extras.putCharSequence(EXTRA_TITLE, title); - extras.putCharSequence(EXTRA_INTRO, intro); - extras.putCharSequence(EXTRA_SUMMARY, summary); - extras.putParcelable(EXTRA_COMPONENT_NAME, componentName); - extras.putInt(EXTRA_ANIMATED_IMAGE_RES, imageRes); - extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription); - } - - /** - * Puts the service extras into {@link RestrictedPreference}'s getExtras(). - * - *

Note: Called by {@link AccessibilityServiceInfo}.

- * - * @param preference The preference we are configuring. - * @param resolveInfo The service resolve info. - * @param serviceEnabled Whether the accessibility service is enabled. - */ - private void putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo, - Boolean serviceEnabled) { - final Bundle extras = preference.getExtras(); - - extras.putParcelable(EXTRA_RESOLVE_INFO, resolveInfo); - extras.putBoolean(EXTRA_CHECKED, serviceEnabled); - } - - /** - * Puts the settings extras into {@link RestrictedPreference}'s getExtras(). - * - *

Note: Called when settings UI is needed.

- * - * @param preference The preference we are configuring. - * @param packageName Package of accessibility feature. - * @param settingsClassName The component name of an activity that allows the user to modify - * the settings for this accessibility feature. - */ - private void putSettingsExtras(RestrictedPreference preference, String packageName, - String settingsClassName) { - final Bundle extras = preference.getExtras(); - - if (!TextUtils.isEmpty(settingsClassName)) { - extras.putString(EXTRA_SETTINGS_TITLE, - mContext.getText(R.string.accessibility_menu_item_settings).toString()); - extras.putString(EXTRA_SETTINGS_COMPONENT_NAME, - new ComponentName(packageName, settingsClassName).flattenToString()); - } - } - - /** - * Puts the information about a particular application - * {@link android.service.quicksettings.TileService} into {@link RestrictedPreference}'s - * getExtras(). - * - *

Note: Called when a tooltip of - * {@link android.service.quicksettings.TileService} is needed.

- * - * @param preference The preference we are configuring. - * @param packageName Package of accessibility feature. - * @param tileServiceClassName The component name of tileService is associated with this - * accessibility feature. - */ - private void putTileServiceExtras(RestrictedPreference preference, String packageName, - String tileServiceClassName) { - final Bundle extras = preference.getExtras(); - if (!TextUtils.isEmpty(tileServiceClassName)) { - extras.putString(EXTRA_TILE_SERVICE_COMPONENT_NAME, - new ComponentName(packageName, tileServiceClassName).flattenToString()); - } - } - } } diff --git a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java new file mode 100644 index 00000000000..3e42e21c932 --- /dev/null +++ b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityShortcutInfo; +import android.app.AppOpsManager; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.UserHandle; +import android.text.TextUtils; + +import androidx.core.content.ContextCompat; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtilsInternal; +import com.android.settingslib.RestrictedPreference; +import com.android.settingslib.accessibility.AccessibilityUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * This class helps setup RestrictedPreference for accessibility. + */ +public class RestrictedPreferenceHelper { + // Index of the first preference in a preference category. + private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1; + + private final Context mContext; + private final DevicePolicyManager mDpm; + private final PackageManager mPm; + private final AppOpsManager mAppOps; + + public RestrictedPreferenceHelper(Context context) { + mContext = context; + mDpm = context.getSystemService(DevicePolicyManager.class); + mPm = context.getPackageManager(); + mAppOps = context.getSystemService(AppOpsManager.class); + } + + /** + * Creates the list of {@link RestrictedPreference} with the installedServices arguments. + * + * @param installedServices The list of {@link AccessibilityServiceInfo}s of the + * installed accessibility services + * @return The list of {@link RestrictedPreference} + */ + public List createAccessibilityServicePreferenceList( + List installedServices) { + + final Set enabledServices = + AccessibilityUtils.getEnabledServicesFromSettings(mContext); + final List permittedServices = mDpm.getPermittedAccessibilityServices( + UserHandle.myUserId()); + final int installedServicesSize = installedServices.size(); + + final List preferenceList = new ArrayList<>( + installedServicesSize); + + for (int i = 0; i < installedServicesSize; ++i) { + final AccessibilityServiceInfo info = installedServices.get(i); + final ResolveInfo resolveInfo = info.getResolveInfo(); + final String packageName = resolveInfo.serviceInfo.packageName; + final ComponentName componentName = new ComponentName(packageName, + resolveInfo.serviceInfo.name); + + final String key = componentName.flattenToString(); + final CharSequence title = resolveInfo.loadLabel(mPm); + final boolean serviceEnabled = enabledServices.contains(componentName); + final CharSequence summary = AccessibilitySettings.getServiceSummary( + mContext, info, serviceEnabled); + final String fragment = getAccessibilityServiceFragmentTypeName(info); + + Drawable icon = resolveInfo.loadIcon(mPm); + if (resolveInfo.getIconResource() == 0) { + icon = ContextCompat.getDrawable(mContext, + R.drawable.ic_accessibility_generic); + } + + final RestrictedPreference preference = createRestrictedPreference(key, title, + summary, icon, fragment, packageName, + resolveInfo.serviceInfo.applicationInfo.uid); + + setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); + + final String prefKey = preference.getKey(); + final int imageRes = info.getAnimatedImageRes(); + final CharSequence intro = info.loadIntro(mPm); + final CharSequence description = AccessibilitySettings.getServiceDescription( + mContext, info, serviceEnabled); + final String htmlDescription = info.loadHtmlDescription(mPm); + final String settingsClassName = info.getSettingsActivityName(); + final String tileServiceClassName = info.getTileServiceName(); + + putBasicExtras(preference, prefKey, title, intro, description, imageRes, + htmlDescription, componentName); + putServiceExtras(preference, resolveInfo, serviceEnabled); + putSettingsExtras(preference, packageName, settingsClassName); + putTileServiceExtras(preference, packageName, tileServiceClassName); + + preferenceList.add(preference); + } + return preferenceList; + } + + /** + * Creates the list of {@link RestrictedPreference} with the installedShortcuts arguments. + * + * @param installedShortcuts The list of {@link AccessibilityShortcutInfo}s of the + * installed accessibility shortcuts + * @return The list of {@link RestrictedPreference} + */ + public List createAccessibilityActivityPreferenceList( + List installedShortcuts) { + final Set enabledServices = + AccessibilityUtils.getEnabledServicesFromSettings(mContext); + final List permittedServices = mDpm.getPermittedAccessibilityServices( + UserHandle.myUserId()); + + final int installedShortcutsSize = installedShortcuts.size(); + final List preferenceList = new ArrayList<>( + installedShortcutsSize); + + for (int i = 0; i < installedShortcutsSize; ++i) { + final AccessibilityShortcutInfo info = installedShortcuts.get(i); + final ActivityInfo activityInfo = info.getActivityInfo(); + final ComponentName componentName = info.getComponentName(); + + final String key = componentName.flattenToString(); + final CharSequence title = activityInfo.loadLabel(mPm); + final String summary = info.loadSummary(mPm); + final String fragment = + LaunchAccessibilityActivityPreferenceFragment.class.getName(); + + Drawable icon = activityInfo.loadIcon(mPm); + if (activityInfo.getIconResource() == 0) { + icon = ContextCompat.getDrawable(mContext, R.drawable.ic_accessibility_generic); + } + + final RestrictedPreference preference = createRestrictedPreference(key, title, + summary, icon, fragment, componentName.getPackageName(), + activityInfo.applicationInfo.uid); + final boolean serviceEnabled = enabledServices.contains(componentName); + + setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); + + final String prefKey = preference.getKey(); + final CharSequence intro = info.loadIntro(mPm); + final String description = info.loadDescription(mPm); + final int imageRes = info.getAnimatedImageRes(); + final String htmlDescription = info.loadHtmlDescription(mPm); + final String settingsClassName = info.getSettingsActivityName(); + final String tileServiceClassName = info.getTileServiceName(); + + putBasicExtras(preference, prefKey, title, intro, description, imageRes, + htmlDescription, componentName); + putSettingsExtras(preference, componentName.getPackageName(), settingsClassName); + putTileServiceExtras(preference, componentName.getPackageName(), + tileServiceClassName); + + preferenceList.add(preference); + } + return preferenceList; + } + + private String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) { + final int type = AccessibilityUtil.getAccessibilityServiceFragmentType(info); + switch (type) { + case AccessibilityUtil.AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE: + return VolumeShortcutToggleAccessibilityServicePreferenceFragment.class.getName(); + case AccessibilityUtil.AccessibilityServiceFragmentType.INVISIBLE_TOGGLE: + return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName(); + case AccessibilityUtil.AccessibilityServiceFragmentType.TOGGLE: + return ToggleAccessibilityServicePreferenceFragment.class.getName(); + default: + throw new IllegalArgumentException( + "Unsupported accessibility fragment type " + type); + } + } + + private RestrictedPreference createRestrictedPreference(String key, CharSequence title, + CharSequence summary, Drawable icon, String fragment, String packageName, int uid) { + final RestrictedPreference preference = new RestrictedPreference(mContext, packageName, + uid); + + preference.setKey(key); + preference.setTitle(title); + preference.setSummary(summary); + preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE)); + preference.setFragment(fragment); + preference.setIconSize(ICON_SIZE_MEDIUM); + preference.setPersistent(false); // Disable SharedPreferences. + preference.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX); + + return preference; + } + + private void setRestrictedPreferenceEnabled(RestrictedPreference preference, + final List permittedServices, boolean serviceEnabled) { + // permittedServices null means all accessibility services are allowed. + boolean serviceAllowed = permittedServices == null || permittedServices.contains( + preference.getPackageName()); + boolean appOpsAllowed; + if (serviceAllowed) { + try { + final int mode = mAppOps.noteOpNoThrow( + AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, + preference.getUid(), preference.getPackageName()); + appOpsAllowed = mode == AppOpsManager.MODE_ALLOWED; + serviceAllowed = appOpsAllowed; + } catch (Exception e) { + // Allow service in case if app ops is not available in testing. + appOpsAllowed = true; + } + } else { + appOpsAllowed = false; + } + if (serviceAllowed || serviceEnabled) { + preference.setEnabled(true); + } else { + // Disable accessibility service that are not permitted. + final RestrictedLockUtils.EnforcedAdmin admin = + RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed( + mContext, preference.getPackageName(), UserHandle.myUserId()); + + if (admin != null) { + preference.setDisabledByAdmin(admin); + } else if (!appOpsAllowed) { + preference.setDisabledByAppOps(true); + } else { + preference.setEnabled(false); + } + } + } + + /** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */ + private void putBasicExtras(RestrictedPreference preference, String prefKey, + CharSequence title, CharSequence intro, CharSequence summary, int imageRes, + String htmlDescription, ComponentName componentName) { + final Bundle extras = preference.getExtras(); + extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY, prefKey); + extras.putCharSequence(AccessibilitySettings.EXTRA_TITLE, title); + extras.putCharSequence(AccessibilitySettings.EXTRA_INTRO, intro); + extras.putCharSequence(AccessibilitySettings.EXTRA_SUMMARY, summary); + extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName); + extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, imageRes); + extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription); + } + + /** + * Puts the service extras into {@link RestrictedPreference}'s getExtras(). + * + *

Note: Called by {@link AccessibilityServiceInfo}.

+ * + * @param preference The preference we are configuring. + * @param resolveInfo The service resolve info. + * @param serviceEnabled Whether the accessibility service is enabled. + */ + private void putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo, + Boolean serviceEnabled) { + final Bundle extras = preference.getExtras(); + + extras.putParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO, resolveInfo); + extras.putBoolean(AccessibilitySettings.EXTRA_CHECKED, serviceEnabled); + } + + /** + * Puts the settings extras into {@link RestrictedPreference}'s getExtras(). + * + *

Note: Called when settings UI is needed.

+ * + * @param preference The preference we are configuring. + * @param packageName Package of accessibility feature. + * @param settingsClassName The component name of an activity that allows the user to modify + * the settings for this accessibility feature. + */ + private void putSettingsExtras(RestrictedPreference preference, String packageName, + String settingsClassName) { + final Bundle extras = preference.getExtras(); + + if (!TextUtils.isEmpty(settingsClassName)) { + extras.putString(AccessibilitySettings.EXTRA_SETTINGS_TITLE, + mContext.getText(R.string.accessibility_menu_item_settings).toString()); + extras.putString(AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME, + new ComponentName(packageName, settingsClassName).flattenToString()); + } + } + + /** + * Puts the information about a particular application + * {@link android.service.quicksettings.TileService} into {@link RestrictedPreference}'s + * getExtras(). + * + *

Note: Called when a tooltip of + * {@link android.service.quicksettings.TileService} is needed.

+ * + * @param preference The preference we are configuring. + * @param packageName Package of accessibility feature. + * @param tileServiceClassName The component name of tileService is associated with this + * accessibility feature. + */ + private void putTileServiceExtras(RestrictedPreference preference, String packageName, + String tileServiceClassName) { + final Bundle extras = preference.getExtras(); + if (!TextUtils.isEmpty(tileServiceClassName)) { + extras.putString(AccessibilitySettings.EXTRA_TILE_SERVICE_COMPONENT_NAME, + new ComponentName(packageName, tileServiceClassName).flattenToString()); + } + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index c1950be2506..7c52754765f 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -87,8 +87,6 @@ public class AccessibilitySettingsTest { private static final String PACKAGE_NAME = "com.android.test"; private static final String CLASS_NAME = PACKAGE_NAME + ".test_a11y_service"; private static final ComponentName COMPONENT_NAME = new ComponentName(PACKAGE_NAME, CLASS_NAME); - private static final int ON = 1; - private static final int OFF = 0; private static final String EMPTY_STRING = ""; private static final String DEFAULT_SUMMARY = "default summary"; private static final String DEFAULT_DESCRIPTION = "default description"; @@ -246,37 +244,6 @@ public class AccessibilitySettingsTest { assertThat(description).isEqualTo(DEFAULT_DESCRIPTION); } - @Test - public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() { - final String key = COMPONENT_NAME.flattenToString(); - final AccessibilitySettings.RestrictedPreferenceHelper helper = - new AccessibilitySettings.RestrictedPreferenceHelper(mContext); - final List infoList = new ArrayList<>( - singletonList(mServiceInfo)); - - final List preferenceList = - helper.createAccessibilityServicePreferenceList(infoList); - RestrictedPreference preference = preferenceList.get(0); - - assertThat(preference.getKey()).isEqualTo(key); - } - - @Test - public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() { - final String key = COMPONENT_NAME.flattenToString(); - final AccessibilitySettings.RestrictedPreferenceHelper helper = - new AccessibilitySettings.RestrictedPreferenceHelper(mContext); - setMockAccessibilityShortcutInfo(mShortcutInfo); - final List infoList = new ArrayList<>( - singletonList(mShortcutInfo)); - - final List preferenceList = - helper.createAccessibilityActivityPreferenceList(infoList); - RestrictedPreference preference = preferenceList.get(0); - - assertThat(preference.getKey()).isEqualTo(key); - } - @Test @Config(shadows = {ShadowFragment.class, ShadowUserManager.class}) public void onCreate_haveRegisterToSpecificUrisAndActions() { diff --git a/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java b/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java new file mode 100644 index 00000000000..99a78cfc408 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import static java.util.Collections.singletonList; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityShortcutInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settingslib.RestrictedPreference; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** Test for {@link RestrictedPreferenceHelper}. */ +@RunWith(RobolectricTestRunner.class) +public class RestrictedPreferenceHelperTest { + + private static final String PACKAGE_NAME = "com.android.test"; + private static final String CLASS_NAME = PACKAGE_NAME + ".test_a11y_service"; + private static final ComponentName COMPONENT_NAME = new ComponentName(PACKAGE_NAME, CLASS_NAME); + private static final String DEFAULT_SUMMARY = "default summary"; + private static final String DEFAULT_DESCRIPTION = "default description"; + private static final String DEFAULT_LABEL = "default label"; + + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Spy + private final AccessibilityServiceInfo mServiceInfo = getMockAccessibilityServiceInfo( + PACKAGE_NAME, CLASS_NAME); + @Mock + private AccessibilityShortcutInfo mShortcutInfo; + private final RestrictedPreferenceHelper mHelper = new RestrictedPreferenceHelper(mContext); + + @Test + public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() { + final String key = COMPONENT_NAME.flattenToString(); + final List infoList = new ArrayList<>( + singletonList(mServiceInfo)); + + final List preferenceList = + mHelper.createAccessibilityServicePreferenceList(infoList); + final RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.getKey()).isEqualTo(key); + } + + @Test + public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() { + final String key = COMPONENT_NAME.flattenToString(); + setMockAccessibilityShortcutInfo(mShortcutInfo); + final List infoList = new ArrayList<>( + singletonList(mShortcutInfo)); + + final List preferenceList = + mHelper.createAccessibilityActivityPreferenceList(infoList); + final RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.getKey()).isEqualTo(key); + } + + private AccessibilityServiceInfo getMockAccessibilityServiceInfo(String packageName, + String className) { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + final ServiceInfo serviceInfo = new ServiceInfo(); + applicationInfo.packageName = packageName; + serviceInfo.packageName = packageName; + serviceInfo.name = className; + serviceInfo.applicationInfo = applicationInfo; + + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = serviceInfo; + try { + final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo, + mContext); + info.setComponentName(new ComponentName(packageName, className)); + return info; + } catch (XmlPullParserException | IOException e) { + // Do nothing + } + return null; + } + + private void setMockAccessibilityShortcutInfo(AccessibilityShortcutInfo mockInfo) { + final ActivityInfo activityInfo = Mockito.mock(ActivityInfo.class); + activityInfo.applicationInfo = new ApplicationInfo(); + when(mockInfo.getActivityInfo()).thenReturn(activityInfo); + when(activityInfo.loadLabel(any())).thenReturn(DEFAULT_LABEL); + when(mockInfo.loadSummary(any())).thenReturn(DEFAULT_SUMMARY); + when(mockInfo.loadDescription(any())).thenReturn(DEFAULT_DESCRIPTION); + when(mockInfo.getComponentName()).thenReturn(COMPONENT_NAME); + } +}