diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 9f0af97fa5c..4c515ad441d 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1596,4 +1596,15 @@ 2 4 + + + + Always + Except when another payment app is open + + + + 0 + 1 + diff --git a/res/values/config.xml b/res/values/config.xml index 7a2f64189e9..ec239a22b47 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -573,4 +573,14 @@ false + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index ec72321ced8..fab3500cbf9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7596,7 +7596,25 @@ Install available apps + Contactless payments + + Default payment app + + To make a payment using a payment app, hold the back of your device to a payment terminal + + Learn more + + + + Set work app as default payment app? + + To make a payment using a work app: + + work profile must be turned on. + + you\u2019ll need to enter your work profile lock if you have one. + How it works @@ -7608,7 +7626,9 @@ %1$s - %2$s - Use default + Use default payment app + + Use default payment app Always diff --git a/res/xml/nfc_default_payment_settings.xml b/res/xml/nfc_default_payment_settings.xml new file mode 100644 index 00000000000..d9978cdf5c2 --- /dev/null +++ b/res/xml/nfc_default_payment_settings.xml @@ -0,0 +1,19 @@ + + + + diff --git a/res/xml/nfc_payment_settings.xml b/res/xml/nfc_payment_settings.xml index 8e55a39a3ca..d748a1c20b3 100644 --- a/res/xml/nfc_payment_settings.xml +++ b/res/xml/nfc_payment_settings.xml @@ -19,16 +19,17 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/nfc_payment_settings_title"> - + - + android:dialogTitle="@string/nfc_payment_use_default_dialog" + settings:controller="com.android.settings.nfc.NfcForegroundPreferenceController" + android:entries="@array/nfc_payment_favor" + android:entryValues="@array/nfc_payment_favor_values" /> diff --git a/src/com/android/settings/applications/AppLocaleUtil.java b/src/com/android/settings/applications/AppLocaleUtil.java new file mode 100644 index 00000000000..e795b015792 --- /dev/null +++ b/src/com/android/settings/applications/AppLocaleUtil.java @@ -0,0 +1,68 @@ +/* + * 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.applications; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.util.Log; + +import com.android.settings.R; +import com.android.settingslib.applications.ApplicationsState.AppEntry; + +/** This class provides methods that help dealing with per app locale. */ +public class AppLocaleUtil { + private static final String TAG = AppLocaleUtil.class.getSimpleName(); + + /** + * Decides the UI display of per app locale. + */ + public static boolean canDisplayLocaleUi(Context context, AppEntry app) { + return !isDisallowedPackage(context, app.info.packageName) + && !isSignedWithPlatformKey(context, app.info.packageName) + && app.hasLauncherEntry; + } + + private static boolean isDisallowedPackage(Context context, String packageName) { + final String[] disallowedPackages = context.getResources().getStringArray( + R.array.config_disallowed_app_localeChange_packages); + for (String disallowedPackage : disallowedPackages) { + if (packageName.equals(disallowedPackage)) { + return true; + } + } + return false; + } + + private static boolean isSignedWithPlatformKey(Context context, String packageName) { + PackageInfo packageInfo = null; + PackageManager packageManager = context.getPackageManager(); + ActivityManager activityManager = context.getSystemService(ActivityManager.class); + try { + packageInfo = packageManager.getPackageInfoAsUser( + packageName, /* flags= */ 0, + activityManager.getCurrentUser()); + } catch (PackageManager.NameNotFoundException ex) { + Log.e(TAG, "package not found: " + packageName); + } + if (packageInfo == null) { + return false; + } + return packageInfo.applicationInfo.isSignedWithPlatformKey(); + } +} diff --git a/src/com/android/settings/applications/AppStateLocaleBridge.java b/src/com/android/settings/applications/AppStateLocaleBridge.java new file mode 100644 index 00000000000..ebaf4abdea7 --- /dev/null +++ b/src/com/android/settings/applications/AppStateLocaleBridge.java @@ -0,0 +1,77 @@ +/* + * 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.applications; + +import android.content.Context; +import android.util.Log; + +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.applications.ApplicationsState.AppEntry; +import com.android.settingslib.applications.ApplicationsState.AppFilter; + +import java.util.List; + +/** + * Creates a application filter to restrict UI display of applications. + * This is to avoid users from changing the per apps locale + * Also provides app filters that can use the info. + */ +public class AppStateLocaleBridge extends AppStateBaseBridge { + private static final String TAG = AppStateLocaleBridge.class.getSimpleName(); + + private final Context mContext; + + public AppStateLocaleBridge(Context context, ApplicationsState appState, + Callback callback) { + super(appState, callback); + mContext = context; + } + + @Override + protected void updateExtraInfo(AppEntry app, String packageName, int uid) { + app.extraInfo = AppLocaleUtil.canDisplayLocaleUi(mContext, app) + ? Boolean.TRUE : Boolean.FALSE; + } + + @Override + protected void loadAllExtraInfo() { + final List allApps = mAppSession.getAllApps(); + for (int i = 0; i < allApps.size(); i++) { + AppEntry app = allApps.get(i); + app.extraInfo = AppLocaleUtil.canDisplayLocaleUi(mContext, app) + ? Boolean.TRUE : Boolean.FALSE; + } + } + + /** For the Settings which shows category of per app's locale. */ + public static final AppFilter FILTER_APPS_LOCALE = + new AppFilter() { + @Override + public void init() { + } + + @Override + public boolean filterApp(AppEntry entry) { + if (entry.extraInfo == null) { + Log.d(TAG, "No extra info."); + return false; + } + return (Boolean) entry.extraInfo; + } + }; + + +} diff --git a/src/com/android/settings/applications/appinfo/AppLocaleDetails.java b/src/com/android/settings/applications/appinfo/AppLocaleDetails.java index 7fd43c22e49..aee360e22f6 100644 --- a/src/com/android/settings/applications/appinfo/AppLocaleDetails.java +++ b/src/com/android/settings/applications/appinfo/AppLocaleDetails.java @@ -37,6 +37,8 @@ import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; +import com.android.internal.app.LocalePicker; +import com.android.internal.app.LocalePicker.LocaleInfo; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; @@ -45,8 +47,11 @@ import com.android.settingslib.applications.AppUtils; import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.RadioButtonPreference; +import com.google.common.collect.Iterables; + import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Locale; /** @@ -200,8 +205,8 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen private TelephonyManager mTelephonyManager; private LocaleManager mLocaleManager; - private Collection mSuggestedLocales = new ArrayList<>();; - private Collection mSupportedLocales = new ArrayList<>();; + private Collection mSuggestedLocales = new ArrayList<>(); + private Collection mSupportedLocales = new ArrayList<>(); AppLocaleDetailsHelper(Context context, String packageName) { mContext = context; @@ -230,25 +235,41 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen @VisibleForTesting void handleSuggestedLocales() { LocaleList currentSystemLocales = getCurrentSystemLocales(); - Locale simLocale = mTelephonyManager.getSimLocale(); Locale appLocale = getAppDefaultLocale(mContext, mPackageName); + String simCountry = mTelephonyManager.getSimCountryIso().toUpperCase(Locale.US); + String networkCountry = mTelephonyManager.getNetworkCountryIso().toUpperCase(Locale.US); // 1st locale in suggested languages group. if (appLocale != null) { mSuggestedLocales.add(appLocale); } // 2nd locale in suggested languages group. - if (simLocale != null && !compareLocale(simLocale, appLocale)) { - mSuggestedLocales.add(simLocale); + final List localeInfos = LocalePicker.getAllAssetLocales(mContext, false); + for (LocaleInfo localeInfo : localeInfos) { + Locale locale = localeInfo.getLocale(); + String localeCountry = locale.getCountry().toUpperCase(Locale.US); + if (!compareLocale(locale, appLocale) + && isCountrySuggestedLocale(localeCountry, simCountry, networkCountry)) { + mSuggestedLocales.add(locale); + } } // Other locales in suggested languages group. for (int i = 0; i < currentSystemLocales.size(); i++) { Locale locale = currentSystemLocales.get(i); - if (!compareLocale(locale, appLocale) && !compareLocale(locale, simLocale)) { + boolean isInSuggestedLocales = false; + for (int j = 0; j < mSuggestedLocales.size(); j++) { + Locale suggestedLocale = Iterables.get(mSuggestedLocales, j); + if (compareLocale(locale, suggestedLocale)) { + isInSuggestedLocales = true; + break; + } + } + if (!isInSuggestedLocales) { mSuggestedLocales.add(locale); } } } + @VisibleForTesting static boolean compareLocale(Locale source, Locale target) { if (source == null && target == null) { return true; @@ -259,6 +280,13 @@ public class AppLocaleDetails extends AppInfoBase implements RadioButtonPreferen } } + private static boolean isCountrySuggestedLocale(String localeCountry, + String simCountry, + String networkCountry) { + return ((!simCountry.isEmpty() && simCountry.equals(localeCountry)) + || (!networkCountry.isEmpty() && networkCountry.equals(localeCountry))); + } + @VisibleForTesting void handleSupportedLocales() { //TODO Waiting for PackageManager api diff --git a/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java b/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java index f1e43ad1ee9..810d2304cbe 100644 --- a/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java @@ -20,20 +20,23 @@ import android.content.Context; import android.util.FeatureFlagUtils; import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.AppLocaleUtil; /** * A controller to update current locale information of application. */ public class AppLocalePreferenceController extends AppInfoPreferenceControllerBase { + private static final String TAG = AppLocalePreferenceController.class.getSimpleName(); + public AppLocalePreferenceController(Context context, String key) { super(context, key); } @Override public int getAvailabilityStatus() { - return FeatureFlagUtils - .isEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION) - ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + boolean isFeatureOn = FeatureFlagUtils + .isEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION); + return isFeatureOn && canDisplayLocaleUi() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } @Override @@ -45,4 +48,8 @@ public class AppLocalePreferenceController extends AppInfoPreferenceControllerBa public CharSequence getSummary() { return AppLocaleDetails.getSummary(mContext, mParent.getAppEntry().info.packageName); } + + boolean canDisplayLocaleUi() { + return AppLocaleUtil.canDisplayLocaleUi(mContext, mParent.getAppEntry()); + } } diff --git a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java index d1d4f622cc8..6e67815d7e7 100644 --- a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java +++ b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java @@ -21,6 +21,7 @@ import androidx.annotation.IntDef; import com.android.settings.R; import com.android.settings.applications.AppStateAlarmsAndRemindersBridge; import com.android.settings.applications.AppStateInstallAppsBridge; +import com.android.settings.applications.AppStateLocaleBridge; import com.android.settings.applications.AppStateManageExternalStorageBridge; import com.android.settings.applications.AppStateMediaManagementAppsBridge; import com.android.settings.applications.AppStateNotificationBridge; @@ -54,6 +55,7 @@ public class AppFilterRegistry { FILTER_APPS_BLOCKED, FILTER_ALARMS_AND_REMINDERS, FILTER_APPS_MEDIA_MANAGEMENT, + FILTER_APPS_LOCALE, }) @interface FilterType { } @@ -79,14 +81,15 @@ public class AppFilterRegistry { public static final int FILTER_MANAGE_EXTERNAL_STORAGE = 17; public static final int FILTER_ALARMS_AND_REMINDERS = 18; public static final int FILTER_APPS_MEDIA_MANAGEMENT = 19; - // Next id: 20. If you add an entry here, length of mFilters should be updated + public static final int FILTER_APPS_LOCALE = 20; + // Next id: 21. If you add an entry here, length of mFilters should be updated private static AppFilterRegistry sRegistry; private final AppFilterItem[] mFilters; private AppFilterRegistry() { - mFilters = new AppFilterItem[20]; + mFilters = new AppFilterItem[21]; // High power allowlist, on mFilters[FILTER_APPS_POWER_ALLOWLIST] = new AppFilterItem( @@ -203,8 +206,16 @@ public class AppFilterRegistry { AppStateMediaManagementAppsBridge.FILTER_MEDIA_MANAGEMENT_APPS, FILTER_APPS_MEDIA_MANAGEMENT, R.string.media_management_apps_title); + + // Apps that can configurate appication's locale. + mFilters[FILTER_APPS_LOCALE] = new AppFilterItem( + AppStateLocaleBridge.FILTER_APPS_LOCALE, + FILTER_APPS_LOCALE, + R.string.app_locale_picker_title); } + + public static AppFilterRegistry getInstance() { if (sRegistry == null) { sRegistry = new AppFilterRegistry(); @@ -235,6 +246,8 @@ public class AppFilterRegistry { return FILTER_ALARMS_AND_REMINDERS; case ManageApplications.LIST_TYPE_MEDIA_MANAGEMENT_APPS: return FILTER_APPS_MEDIA_MANAGEMENT; + case ManageApplications.LIST_TYPE_APPS_LOCALE: + return FILTER_APPS_LOCALE; default: return FILTER_APPS_ALL; } diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index d98548280fb..01bc2f19bb9 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -95,6 +95,7 @@ import com.android.settings.applications.AppStateAlarmsAndRemindersBridge; import com.android.settings.applications.AppStateAppOpsBridge.PermissionState; import com.android.settings.applications.AppStateBaseBridge; import com.android.settings.applications.AppStateInstallAppsBridge; +import com.android.settings.applications.AppStateLocaleBridge; import com.android.settings.applications.AppStateManageExternalStorageBridge; import com.android.settings.applications.AppStateMediaManagementAppsBridge; import com.android.settings.applications.AppStateNotificationBridge; @@ -232,7 +233,7 @@ public class ManageApplications extends InstrumentedFragment public static final int LIST_MANAGE_EXTERNAL_STORAGE = 11; public static final int LIST_TYPE_ALARMS_AND_REMINDERS = 12; public static final int LIST_TYPE_MEDIA_MANAGEMENT_APPS = 13; - public static final int LIST_TYPE_APPS_LOCAL = 14; + public static final int LIST_TYPE_APPS_LOCALE = 14; // List types that should show instant apps. public static final Set LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList( @@ -321,7 +322,7 @@ public class ManageApplications extends InstrumentedFragment mNotificationBackend = new NotificationBackend(); mSortOrder = R.id.sort_order_recent_notification; } else if (className.equals(AppLocaleDetails.class.getName())) { - mListType = LIST_TYPE_APPS_LOCAL; + mListType = LIST_TYPE_APPS_LOCALE; } else { mListType = LIST_TYPE_MAIN; } @@ -504,7 +505,7 @@ public class ManageApplications extends InstrumentedFragment return SettingsEnums.ALARMS_AND_REMINDERS; case LIST_TYPE_MEDIA_MANAGEMENT_APPS: return SettingsEnums.MEDIA_MANAGEMENT_APPS; - case LIST_TYPE_APPS_LOCAL: + case LIST_TYPE_APPS_LOCALE: return SettingsEnums.APPS_LOCALE_LIST; default: return SettingsEnums.PAGE_UNKNOWN; @@ -629,7 +630,7 @@ public class ManageApplications extends InstrumentedFragment startAppInfoFragment(MediaManagementAppsDetails.class, R.string.media_management_apps_title); break; - case LIST_TYPE_APPS_LOCAL: + case LIST_TYPE_APPS_LOCALE: startAppInfoFragment(AppLocaleDetails.class, R.string.app_locale_picker_title); break; @@ -743,9 +744,9 @@ public class ManageApplications extends InstrumentedFragment && mSortOrder != R.id.sort_order_size); mOptionsMenu.findItem(R.id.show_system).setVisible(!mShowSystem - && mListType != LIST_TYPE_HIGH_POWER); + && mListType != LIST_TYPE_HIGH_POWER && mListType != LIST_TYPE_APPS_LOCALE); mOptionsMenu.findItem(R.id.hide_system).setVisible(mShowSystem - && mListType != LIST_TYPE_HIGH_POWER); + && mListType != LIST_TYPE_HIGH_POWER && mListType != LIST_TYPE_APPS_LOCALE); mOptionsMenu.findItem(R.id.reset_app_preferences).setVisible(mListType == LIST_TYPE_MAIN); @@ -1100,6 +1101,8 @@ public class ManageApplications extends InstrumentedFragment mExtraInfoBridge = new AppStateAlarmsAndRemindersBridge(mContext, mState, this); } else if (mManageApplications.mListType == LIST_TYPE_MEDIA_MANAGEMENT_APPS) { mExtraInfoBridge = new AppStateMediaManagementAppsBridge(mContext, mState, this); + } else if (mManageApplications.mListType == LIST_TYPE_APPS_LOCALE) { + mExtraInfoBridge = new AppStateLocaleBridge(mContext, mState, this); } else { mExtraInfoBridge = null; } @@ -1533,7 +1536,7 @@ public class ManageApplications extends InstrumentedFragment case LIST_TYPE_MEDIA_MANAGEMENT_APPS: holder.setSummary(MediaManagementAppsDetails.getSummary(mContext, entry)); break; - case LIST_TYPE_APPS_LOCAL: + case LIST_TYPE_APPS_LOCALE: holder.setSummary(AppLocaleDetails .getSummary(mContext, entry.info.packageName)); break; diff --git a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java index a8a30b46c93..ba660ee3c6d 100644 --- a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.icu.text.RelativeDateTimeFormatter; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; @@ -85,11 +86,17 @@ public class RecentLocationAccessPreferenceController extends LocationBasePrefer public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mCategoryRecentLocationRequests = screen.findPreference(getPreferenceKey()); + } + + @Override + public void updateState(Preference preference) { + mCategoryRecentLocationRequests.removeAll(); final Context prefContext = mCategoryRecentLocationRequests.getContext(); final List recentLocationAccesses = new ArrayList<>(); final UserManager userManager = UserManager.get(mContext); - for (RecentAppOpsAccess.Access access : mRecentLocationApps.getAppListSorted( - /* showSystemApps= */ false)) { + final boolean showSystem = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1; + for (RecentAppOpsAccess.Access access : mRecentLocationApps.getAppListSorted(showSystem)) { if (isRequestMatchesProfileType(userManager, access, mType)) { recentLocationAccesses.add(access); if (recentLocationAccesses.size() == MAX_APPS) { diff --git a/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java b/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java index e27b28c8238..f7bf31a896a 100644 --- a/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java +++ b/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java @@ -17,6 +17,7 @@ package com.android.settings.location; import android.content.Context; import android.os.Bundle; +import android.provider.Settings; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -36,7 +37,6 @@ public class RecentLocationAccessSeeAllFragment extends DashboardFragment { private static final int MENU_SHOW_SYSTEM = Menu.FIRST + 1; private static final int MENU_HIDE_SYSTEM = Menu.FIRST + 2; - private static final String EXTRA_SHOW_SYSTEM = "show_system"; private boolean mShowSystem = false; private MenuItem mShowSystemMenu; @@ -58,18 +58,8 @@ public class RecentLocationAccessSeeAllFragment extends DashboardFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - mShowSystem = savedInstanceState.getBoolean(EXTRA_SHOW_SYSTEM, mShowSystem); - } - if (mController != null) { - mController.setShowSystem(mShowSystem); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem); + mShowSystem = Settings.Secure.getInt(getContentResolver(), + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1; } @Override @@ -88,6 +78,8 @@ public class RecentLocationAccessSeeAllFragment extends DashboardFragment { case MENU_SHOW_SYSTEM: case MENU_HIDE_SYSTEM: mShowSystem = menuItem.getItemId() == MENU_SHOW_SYSTEM; + Settings.Secure.putInt(getContentResolver(), + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, mShowSystem ? 1 : 0); updateMenu(); if (mController != null) { mController.setShowSystem(mShowSystem); diff --git a/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java b/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java index bca4486f01c..e3379c7da7f 100644 --- a/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java @@ -20,6 +20,7 @@ import static com.android.settings.location.RecentLocationAccessPreferenceContro import android.content.Context; import android.os.UserManager; +import android.provider.Settings; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -43,6 +44,8 @@ public class RecentLocationAccessSeeAllPreferenceController public RecentLocationAccessSeeAllPreferenceController(Context context, String key) { super(context, key); + mShowSystem = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1; mRecentLocationAccesses = RecentAppOpsAccess.createForLocation(context); } diff --git a/src/com/android/settings/location/RecentLocationRequestPreferenceController.java b/src/com/android/settings/location/RecentLocationRequestPreferenceController.java index 812a4404fb1..a14e047cb4b 100644 --- a/src/com/android/settings/location/RecentLocationRequestPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationRequestPreferenceController.java @@ -17,6 +17,7 @@ import android.content.Context; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; @@ -83,8 +84,11 @@ public class RecentLocationRequestPreferenceController extends LocationBasePrefe final Context prefContext = mCategoryRecentLocationRequests.getContext(); final List recentLocationRequests = new ArrayList<>(); final UserManager userManager = UserManager.get(mContext); + final boolean showSystem = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1; + for (RecentLocationApps.Request request : mRecentLocationApps.getAppListSorted( - false /* systemApps */)) { + showSystem)) { if (isRequestMatchesProfileType(userManager, request, mType)) { recentLocationRequests.add(request); if (recentLocationRequests.size() == MAX_APPS) { diff --git a/src/com/android/settings/nfc/DefaultPaymentSettings.java b/src/com/android/settings/nfc/DefaultPaymentSettings.java new file mode 100644 index 00000000000..4dceefc3451 --- /dev/null +++ b/src/com/android/settings/nfc/DefaultPaymentSettings.java @@ -0,0 +1,278 @@ +/* + * 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.nfc; + +import android.app.settings.SettingsEnums; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.Layout; +import android.text.SpannableString; +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; +import com.android.settings.applications.defaultapps.DefaultAppPickerFragment; +import com.android.settings.nfc.PaymentBackend.PaymentAppInfo; +import com.android.settingslib.widget.CandidateInfo; +import com.android.settingslib.widget.FooterPreference; +import com.android.settingslib.widget.SelectorWithWidgetPreference; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * DefaultPaymentSettings handles the NFC default payment app selection. + */ +public class DefaultPaymentSettings extends DefaultAppPickerFragment { + public static final String TAG = "DefaultPaymentSettings"; + + private PaymentBackend mPaymentBackend; + private List mAppInfos; + private Preference mFooterPreference; + + @Override + public int getMetricsCategory() { + return SettingsEnums.NFC_PAYMENT; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.nfc_default_payment_settings; + } + + @Override + protected String getDefaultKey() { + PaymentAppInfo defaultAppInfo = mPaymentBackend.getDefaultApp(); + if (defaultAppInfo != null) { + return defaultAppInfo.componentName.flattenToString() + " " + + defaultAppInfo.userHandle.getIdentifier(); + } + return null; + } + + @Override + protected boolean setDefaultKey(String key) { + String[] keys = key.split(" "); + if (keys.length >= 2) { + mPaymentBackend.setDefaultPaymentApp(ComponentName.unflattenFromString(keys[0]), + Integer.parseInt(keys[1])); + } + return true; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mPaymentBackend = new PaymentBackend(getActivity()); + mAppInfos = mPaymentBackend.getPaymentAppInfos(); + } + + @Override + protected void addStaticPreferences(PreferenceScreen screen) { + if (mFooterPreference == null) { + setupFooterPreference(); + } + screen.addPreference(mFooterPreference); + } + + @Override + public void onResume() { + super.onResume(); + mPaymentBackend.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mPaymentBackend.onPause(); + } + + /** + * Comparator for NfcPaymentCandidateInfo. + */ + public class NfcPaymentCandidateInfoComparator implements Comparator { + /** + * Compare the NfcPaymentCandidateInfo by the label string. + */ + public int compare(NfcPaymentCandidateInfo obj1, NfcPaymentCandidateInfo obj2) { + if (obj1.loadLabel() == obj2.loadLabel()) { + return 0; + } + if (obj1.loadLabel() == null) { + return -1; + } + if (obj2.loadLabel() == null) { + return 1; + } + return obj1.loadLabel().toString().compareTo(obj2.loadLabel().toString()); + } + } + + @Override + public void bindPreferenceExtra(SelectorWithWidgetPreference pref, String key, + CandidateInfo info, String defaultKey, String systemDefaultKey) { + final NfcPaymentCandidateInfo candidateInfo = (NfcPaymentCandidateInfo) info; + if (candidateInfo.isManagedProfile()) { + pref.setSummary("Work"); + } + } + + @Override + protected List getCandidates() { + final List candidates = new ArrayList<>(); + for (PaymentAppInfo appInfo: mAppInfos) { + UserManager um = getContext().createContextAsUser( + appInfo.userHandle, /*flags=*/0).getSystemService(UserManager.class); + boolean isManagedProfile = um.isManagedProfile(appInfo.userHandle.getIdentifier()); + + CharSequence label; + label = appInfo.label; + candidates.add(new NfcPaymentCandidateInfo( + appInfo.componentName.flattenToString(), + label, + appInfo.icon, + appInfo.userHandle.getIdentifier(), + isManagedProfile)); + } + Collections.sort(candidates, new NfcPaymentCandidateInfoComparator()); + return candidates; + } + + @VisibleForTesting + class NfcPaymentCandidateInfo extends CandidateInfo { + private final String mKey; + private final CharSequence mLabel; + private final Drawable mDrawable; + private final int mUserId; + private final boolean mIsManagedProfile; + + NfcPaymentCandidateInfo(String key, CharSequence label, Drawable drawable, int userId, + boolean isManagedProfile) { + super(true /* enabled */); + mKey = key; + mLabel = label; + mDrawable = drawable; + mUserId = userId; + mIsManagedProfile = isManagedProfile; + } + + @Override + public CharSequence loadLabel() { + return mLabel; + } + + @Override + public Drawable loadIcon() { + return mDrawable; + } + + @Override + public String getKey() { + return mKey + " " + mUserId; + } + + public boolean isManagedProfile() { + return mIsManagedProfile; + } + } + + @Override + protected CharSequence getConfirmationMessage(CandidateInfo appInfo) { + if (appInfo == null) { + return null; + } + NfcPaymentCandidateInfo paymentInfo = (NfcPaymentCandidateInfo) appInfo; + UserManager um = getContext().createContextAsUser(UserHandle.of(paymentInfo.mUserId), + /*flags=*/0).getSystemService(UserManager.class); + boolean isManagedProfile = um.isManagedProfile(paymentInfo.mUserId); + if (!isManagedProfile) { + return null; + } + + final String title = getContext().getString( + R.string.nfc_default_payment_workapp_confirmation_title); + final String messageTitle = getContext().getString( + R.string.nfc_default_payment_workapp_confirmation_message_title); + final String messageOne = getContext().getString( + R.string.nfc_default_payment_workapp_confirmation_message_1); + final String messageTwo = getContext().getString( + R.string.nfc_default_payment_workapp_confirmation_message_2); + final SpannableString titleString = new SpannableString(title); + final SpannableString messageString = new SpannableString(messageTitle); + final SpannableString oneString = new SpannableString(messageOne); + final SpannableString twoString = new SpannableString(messageTwo); + + titleString.setSpan(new RelativeSizeSpan(1.5f), 0, title.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + titleString.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), 0, + title.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + messageString.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), 0, + messageTitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + oneString.setSpan(new BulletSpan(20), 0, messageOne.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + twoString.setSpan(new BulletSpan(20), 0, messageTwo.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + return TextUtils.concat(titleString, "\n\n", messageString, "\n\n", oneString, "\n", + twoString); + } + + 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.setIcon(R.drawable.ic_info_outline_24dp); + } +} diff --git a/src/com/android/settings/nfc/NfcDefaultPaymentPreferenceController.java b/src/com/android/settings/nfc/NfcDefaultPaymentPreferenceController.java new file mode 100644 index 00000000000..6bcacb1ad63 --- /dev/null +++ b/src/com/android/settings/nfc/NfcDefaultPaymentPreferenceController.java @@ -0,0 +1,146 @@ +/* + * 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.nfc; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.nfc.NfcAdapter; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.applications.defaultapps.DefaultAppPreferenceController; +import com.android.settings.nfc.PaymentBackend.PaymentAppInfo; +import com.android.settingslib.applications.DefaultAppInfo; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +import java.util.List; + +/** + * NfcDefaultPaymentPreferenceController shows an app icon and text summary for current selected + * default payment, and links to the nfc default payment selection page. + */ +public class NfcDefaultPaymentPreferenceController extends DefaultAppPreferenceController implements + PaymentBackend.Callback, LifecycleObserver, OnResume, OnPause { + + private static final String TAG = "NfcDefaultPaymentController"; + private static final String KEY = "nfc_payment_app"; + + private PaymentBackend mPaymentBackend; + private Preference mPreference; + private Context mContext; + + public NfcDefaultPaymentPreferenceController(Context context, Lifecycle lifecycle) { + super(context); + mContext = context; + mPaymentBackend = new PaymentBackend(context); + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public boolean isAvailable() { + final PackageManager pm = mContext.getPackageManager(); + if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC)) { + return false; + } + if (NfcAdapter.getDefaultAdapter(mContext) == null) { + return false; + } + if (mPaymentBackend == null) { + mPaymentBackend = new PaymentBackend(mContext); + } + final List appInfos = mPaymentBackend.getPaymentAppInfos(); + return (appInfos != null && !appInfos.isEmpty()) + ? true + : false; + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public void onResume() { + if (mPaymentBackend != null) { + mPaymentBackend.registerCallback(this); + mPaymentBackend.onResume(); + } + } + + @Override + public void onPause() { + if (mPaymentBackend != null) { + mPaymentBackend.unregisterCallback(this); + mPaymentBackend.onPause(); + } + } + + @Override + public void displayPreference(PreferenceScreen screen) { + mPreference = screen.findPreference(getPreferenceKey()); + super.displayPreference(screen); + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + preference.setIconSpaceReserved(true); + } + + @Override + public void onPaymentAppsChanged() { + updateState(mPreference); + } + + /** + * PaymentDefaultAppInfo is used to store the default payment app info. + */ + public static class PaymentDefaultAppInfo extends DefaultAppInfo { + public PaymentAppInfo mInfo; + + public PaymentDefaultAppInfo(Context context, PackageManager pm, int userId, + PaymentAppInfo info) { + super(context, pm, userId, info.componentName); + mInfo = info; + } + + @Override + public Drawable loadIcon() { + return mInfo.icon; + } + } + + @Override + protected DefaultAppInfo getDefaultAppInfo() { + if (mPaymentBackend == null) { + return null; + } + final PaymentAppInfo defaultApp = mPaymentBackend.getDefaultApp(); + if (defaultApp != null) { + return new PaymentDefaultAppInfo(mContext, mPackageManager, + defaultApp.userHandle.getIdentifier(), defaultApp); + } + return null; + } +} diff --git a/src/com/android/settings/nfc/NfcForegroundPreferenceController.java b/src/com/android/settings/nfc/NfcForegroundPreferenceController.java index b02608ef879..246bdb9716e 100644 --- a/src/com/android/settings/nfc/NfcForegroundPreferenceController.java +++ b/src/com/android/settings/nfc/NfcForegroundPreferenceController.java @@ -16,11 +16,9 @@ package com.android.settings.nfc; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.pm.PackageManager; -import android.text.TextUtils; -import androidx.preference.DropDownPreference; +import androidx.preference.ListPreference; import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; @@ -36,13 +34,18 @@ public class NfcForegroundPreferenceController extends BasePreferenceController PaymentBackend.Callback, Preference.OnPreferenceChangeListener, LifecycleObserver, OnStart, OnStop { - private DropDownPreference mPreference; + private ListPreference mPreference; private PaymentBackend mPaymentBackend; private MetricsFeatureProvider mMetricsFeatureProvider; + private final String[] mListValues; + private final String[] mListEntries; + public NfcForegroundPreferenceController(Context context, String key) { super(context, key); mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + mListValues = context.getResources().getStringArray(R.array.nfc_payment_favor_values); + mListEntries = context.getResources().getStringArray(R.array.nfc_payment_favor); } public void setPaymentBackend(PaymentBackend backend) { @@ -78,21 +81,6 @@ public class NfcForegroundPreferenceController extends BasePreferenceController : UNSUPPORTED_ON_DEVICE; } - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mPreference = screen.findPreference(getPreferenceKey()); - if (mPreference == null) { - return; - } - - mPreference.setEntries(new CharSequence[]{ - mContext.getText(R.string.nfc_payment_favor_open), - mContext.getText(R.string.nfc_payment_favor_default) - }); - mPreference.setEntryValues(new CharSequence[]{"1", "0"}); - } - @Override public void onPaymentAppsChanged() { updateState(mPreference); @@ -100,26 +88,29 @@ public class NfcForegroundPreferenceController extends BasePreferenceController @Override public void updateState(Preference preference) { - if (preference instanceof DropDownPreference) { - ((DropDownPreference) preference).setValue( - mPaymentBackend.isForegroundMode() ? "1" : "0"); - } super.updateState(preference); + if (!(preference instanceof ListPreference)) { + return; + } + final ListPreference listPreference = (ListPreference) preference; + listPreference.setIconSpaceReserved(true); + listPreference.setValue(mListValues[mPaymentBackend.isForegroundMode() ? 1 : 0]); } @Override public CharSequence getSummary() { - return mPreference.getEntry(); + return mListEntries[mPaymentBackend.isForegroundMode() ? 1 : 0]; } @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - if (!(preference instanceof DropDownPreference)) { + if (!(preference instanceof ListPreference)) { return false; } - final DropDownPreference pref = (DropDownPreference) preference; + + final ListPreference listPreference = (ListPreference) preference; final String newValueString = (String) newValue; - pref.setSummary(pref.getEntries()[pref.findIndexOfValue(newValueString)]); + listPreference.setSummary(mListEntries[listPreference.findIndexOfValue(newValueString)]); final boolean foregroundMode = Integer.parseInt(newValueString) != 0; mPaymentBackend.setForegroundMode(foregroundMode); mMetricsFeatureProvider.action(mContext, @@ -127,12 +118,4 @@ public class NfcForegroundPreferenceController extends BasePreferenceController : SettingsEnums.ACTION_NFC_PAYMENT_ALWAYS_SETTING); return true; } - - @Override - public void updateNonIndexableKeys(List keys) { - final String key = getPreferenceKey(); - if (!TextUtils.isEmpty(key)) { - keys.add(key); - } - } -} \ No newline at end of file +} diff --git a/src/com/android/settings/nfc/PaymentBackend.java b/src/com/android/settings/nfc/PaymentBackend.java index 542c95bb6d6..0cfed201e9c 100644 --- a/src/com/android/settings/nfc/PaymentBackend.java +++ b/src/com/android/settings/nfc/PaymentBackend.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; import android.nfc.NfcAdapter; import android.nfc.cardemulation.ApduServiceInfo; import android.nfc.cardemulation.CardEmulation; @@ -50,6 +51,7 @@ public class PaymentBackend { public ComponentName componentName; public ComponentName settingsComponent; public UserHandle userHandle; + public Drawable icon; } /** @@ -131,6 +133,7 @@ public class PaymentBackend { appInfo.settingsComponent = null; } appInfo.description = service.getDescription(); + appInfo.icon = pm.getUserBadgedIcon(service.loadIcon(pm), appInfo.userHandle); appInfos.add(appInfo); } diff --git a/src/com/android/settings/nfc/PaymentSettings.java b/src/com/android/settings/nfc/PaymentSettings.java index bbe5f29d9ae..a1f75bc63d2 100644 --- a/src/com/android/settings/nfc/PaymentSettings.java +++ b/src/com/android/settings/nfc/PaymentSettings.java @@ -18,15 +18,11 @@ package com.android.settings.nfc; import android.app.settings.SettingsEnums; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -37,8 +33,12 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.search.SearchIndexable; +import java.util.ArrayList; +import java.util.List; @SearchIndexable public class PaymentSettings extends DashboardFragment { @@ -61,13 +61,24 @@ public class PaymentSettings extends DashboardFragment { return R.xml.nfc_payment_settings; } + @Override + protected List createPreferenceControllers(Context context) { + return buildPreferenceControllers(context, getSettingsLifecycle()); + } + + private static List buildPreferenceControllers(Context context, + Lifecycle lifecycle) { + final List controllers = new ArrayList<>(); + controllers.add(new NfcDefaultPaymentPreferenceController(context, lifecycle)); + + return controllers; + } + @Override public void onAttach(Context context) { super.onAttach(context); mPaymentBackend = new PaymentBackend(getActivity()); - setHasOptionsMenu(true); - use(NfcPaymentPreferenceController.class).setPaymentBackend(mPaymentBackend); use(NfcForegroundPreferenceController.class).setPaymentBackend(mPaymentBackend); } @@ -93,15 +104,6 @@ public class PaymentSettings extends DashboardFragment { mPaymentBackend.onPause(); } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - MenuItem menuItem = menu.add(R.string.nfc_payment_how_it_works); - Intent howItWorksIntent = new Intent(getActivity(), HowItWorks.class); - menuItem.setIntent(howItWorksIntent); - menuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_NEVER); - } - @VisibleForTesting boolean isShowEmptyImage(PreferenceScreen screen) { for (int i = 0; i < screen.getPreferenceCount(); i++) { @@ -127,4 +129,4 @@ public class PaymentSettings extends DashboardFragment { return pm.hasSystemFeature(PackageManager.FEATURE_NFC); } }; -} \ No newline at end of file +} diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java index 8f89a5f9846..c3657be1343 100644 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -74,6 +74,9 @@ public class UserDetailsSettings extends SettingsPreferenceFragment private static final int DIALOG_SETUP_USER = 4; private static final int DIALOG_CONFIRM_RESET_GUEST = 5; + /** Whether to enable the app_copying fragment. */ + private static final boolean SHOW_APP_COPYING_PREF = false; + private UserManager mUserManager; private UserCapabilities mUserCaps; private boolean mGuestUserAutoCreated; @@ -298,6 +301,9 @@ public class UserDetailsSettings extends SettingsPreferenceFragment if (mGuestUserAutoCreated) { mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0); } + if (!SHOW_APP_COPYING_PREF) { + removePreference(KEY_APP_COPYING); + } } else { mPhonePref.setChecked(!mUserManager.hasUserRestriction( UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId))); @@ -410,6 +416,9 @@ public class UserDetailsSettings extends SettingsPreferenceFragment } private void openAppCopyingScreen() { + if (!SHOW_APP_COPYING_PREF) { + return; + } final Bundle extras = new Bundle(); extras.putInt(AppRestrictionsFragment.EXTRA_USER_ID, mUserInfo.id); new SubSettingLauncher(getContext()) diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index dc4bca35b1c..9398d20e7ad 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -503,7 +503,8 @@ public class UserSettings extends SettingsPreferenceFragment private void onAddSupervisedUserClicked() { final Intent intent = new Intent() .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) - .setPackage(mConfigSupervisedUserCreationPackage); + .setPackage(mConfigSupervisedUserCreationPackage) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // TODO(b/209659998): [to-be-removed] fallback activity for supervised user creation. if (getActivity().getPackageManager().resolveActivity(intent, 0) == null) { diff --git a/tests/robotests/src/com/android/settings/ResetNetworkConfirmTest.java b/tests/robotests/src/com/android/settings/ResetNetworkConfirmTest.java index e7a0090a53a..e4495f42b90 100644 --- a/tests/robotests/src/com/android/settings/ResetNetworkConfirmTest.java +++ b/tests/robotests/src/com/android/settings/ResetNetworkConfirmTest.java @@ -18,6 +18,15 @@ package com.android.settings; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.wifi.p2p.WifiP2pManager; +import android.os.Looper; import android.view.LayoutInflater; import android.widget.TextView; @@ -25,7 +34,6 @@ import androidx.fragment.app.FragmentActivity; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowRecoverySystem; -import com.android.settings.testutils.shadow.ShadowWifiP2pManager; import org.junit.After; import org.junit.Before; @@ -39,27 +47,31 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowRecoverySystem.class, - ShadowWifiP2pManager.class, ShadowBluetoothAdapter.class -}) +@Config(shadows = {ShadowRecoverySystem.class, ShadowBluetoothAdapter.class}) public class ResetNetworkConfirmTest { private FragmentActivity mActivity; + + @Mock + private WifiP2pManager mWifiP2pManager; @Mock private ResetNetworkConfirm mResetNetworkConfirm; @Before public void setUp() { MockitoAnnotations.initMocks(this); + when(mWifiP2pManager.initialize(any(Context.class), any(Looper.class), any())) + .thenReturn(mock(WifiP2pManager.Channel.class)); + mResetNetworkConfirm = new ResetNetworkConfirm(); - mActivity = Robolectric.setupActivity(FragmentActivity.class); + mActivity = spy(Robolectric.setupActivity(FragmentActivity.class)); + when(mActivity.getSystemService(Context.WIFI_P2P_SERVICE)).thenReturn(mWifiP2pManager); mResetNetworkConfirm.mActivity = mActivity; } @After public void tearDown() { ShadowRecoverySystem.reset(); - ShadowWifiP2pManager.reset(); } @Test @@ -88,11 +100,10 @@ public class ResetNetworkConfirmTest { * Test for WifiP2pManager factoryReset method. */ @Test - @Ignore public void testResetNetworkData_resetP2p() { mResetNetworkConfirm.p2pFactoryReset(mActivity); - assertThat(ShadowWifiP2pManager.getFactoryResetCount()).isEqualTo(1); + verify(mWifiP2pManager).factoryReset(any(WifiP2pManager.Channel.class), any()); } @Test diff --git a/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java index c7e571fafd2..b5d1cc78eba 100644 --- a/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java @@ -18,6 +18,7 @@ package com.android.settings.accounts; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -32,7 +33,6 @@ import androidx.preference.SwitchPreference; import com.android.settings.R; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -106,10 +106,9 @@ public class WorkModePreferenceControllerTest { } @Test - @Ignore public void onStart_shouldRegisterReceiver() { mController.onStart(); - verify(mContext).registerReceiver(eq(mController.mReceiver), any()); + verify(mContext).registerReceiver(eq(mController.mReceiver), any(), anyInt()); } @Test diff --git a/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java b/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java index 1048970cd46..c6c243665fb 100644 --- a/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java +++ b/tests/robotests/src/com/android/settings/dream/DreamPickerControllerTest.java @@ -48,7 +48,6 @@ import java.util.Collections; @RunWith(RobolectricTestRunner.class) public class DreamPickerControllerTest { - private DreamPickerController mController; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private DreamBackend mBackend; private Context mContext; @@ -63,31 +62,36 @@ public class DreamPickerControllerTest { mPreference = new LayoutPreference(mContext, R.layout.dream_picker_layout); when(mScreen.findPreference(anyString())).thenReturn(mPreference); + } - mController = new DreamPickerController( + private DreamPickerController buildController() { + final DreamPickerController controller = new DreamPickerController( mContext, /* preferenceKey= */ "test", mBackend); - - mController.displayPreference(mScreen); + controller.displayPreference(mScreen); + return controller; } @Test public void isDisabledIfNoDreamsAvailable() { when(mBackend.getDreamInfos()).thenReturn(new ArrayList<>(0)); - assertThat(mController.isAvailable()).isFalse(); + final DreamPickerController controller = buildController(); + assertThat(controller.isAvailable()).isFalse(); } @Test public void isEnabledIfDreamsAvailable() { when(mBackend.getDreamInfos()).thenReturn(Collections.singletonList(new DreamInfo())); - assertThat(mController.isAvailable()).isTrue(); + final DreamPickerController controller = buildController(); + assertThat(controller.isAvailable()).isTrue(); } @Test public void testDreamDisplayedInList() { when(mBackend.getDreamInfos()).thenReturn(Collections.singletonList(new DreamInfo())); - mController.updateState(mPreference); + final DreamPickerController controller = buildController(); + controller.updateState(mPreference); RecyclerView view = mPreference.findViewById(R.id.dream_list); assertThat(view.getAdapter().getItemCount()).isEqualTo(1); @@ -100,7 +104,8 @@ public class DreamPickerControllerTest { mockDreamInfo.isActive = true; when(mBackend.getDreamInfos()).thenReturn(Collections.singletonList(mockDreamInfo)); - mController.updateState(mPreference); + final DreamPickerController controller = buildController(); + controller.updateState(mPreference); Button view = mPreference.findViewById(R.id.preview_button); view.performClick(); diff --git a/tests/robotests/src/com/android/settings/enterprise/BugReportsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/BugReportsPreferenceControllerTest.java index 5a05eccf640..34853c68e14 100644 --- a/tests/robotests/src/com/android/settings/enterprise/BugReportsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/enterprise/BugReportsPreferenceControllerTest.java @@ -22,13 +22,13 @@ import static org.mockito.Mockito.when; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; import java.util.Date; -import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class BugReportsPreferenceControllerTest - extends AdminActionPreferenceControllerTestBase { + extends AdminActionPreferenceControllerTestBase { @Override public void setUp() { @@ -39,7 +39,7 @@ public class BugReportsPreferenceControllerTest @Override public void setDate(Date date) { when(mFeatureFactory.enterprisePrivacyFeatureProvider.getLastBugReportRequestTime()) - .thenReturn(date); + .thenReturn(date); } @Test diff --git a/tests/robotests/src/com/android/settings/location/LocationInjectedServicesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationInjectedServicesPreferenceControllerTest.java index ad928da6218..ae62724cea5 100644 --- a/tests/robotests/src/com/android/settings/location/LocationInjectedServicesPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationInjectedServicesPreferenceControllerTest.java @@ -43,7 +43,6 @@ import com.android.settings.widget.RestrictedAppPreference; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -98,16 +97,15 @@ public class LocationInjectedServicesPreferenceControllerTest { } @Test - @Ignore public void onResume_shouldRegisterListener() { mController.onResume(); verify(mContext).registerReceiver(eq(mController.mInjectedSettingsReceiver), - eq(mController.INTENT_FILTER_INJECTED_SETTING_CHANGED)); + eq(mController.INTENT_FILTER_INJECTED_SETTING_CHANGED), + anyInt()); } @Test - @Ignore public void onPause_shouldUnregisterListener() { mController.onResume(); mController.onPause(); diff --git a/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java index 52068c4d7c3..225f91bde17 100644 --- a/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java @@ -19,9 +19,12 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.os.UserHandle; +import android.provider.Settings; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; @@ -34,12 +37,15 @@ import com.android.settings.dashboard.DashboardFragment; import com.android.settings.testutils.shadow.ShadowDeviceConfig; import com.android.settingslib.applications.RecentAppOpsAccess; +import com.google.common.collect.ImmutableList; + import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @@ -107,4 +113,20 @@ public class RecentLocationAccessPreferenceControllerTest { mContext.getText(R.string.location_recent_location_access_view_details)); assertThat(details.hasOnClickListeners()).isTrue(); } + + /** Verifies the title text, details text are correct, and the click listener is set. */ + @Test + public void updateState_showSystemAccess() { + doReturn(ImmutableList.of( + new RecentAppOpsAccess.Access("app", UserHandle.CURRENT, null, "app", "", 0))) + .when(mRecentLocationApps).getAppListSorted(false); + doReturn(new ArrayList<>()).when(mRecentLocationApps).getAppListSorted(true); + mController.displayPreference(mScreen); + mController.updateState(mLayoutPreference); + verify(mLayoutPreference).addPreference(Mockito.any()); + + Settings.Secure.putInt( + mContext.getContentResolver(), Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 1); + verify(mLayoutPreference, Mockito.times(1)).addPreference(Mockito.any()); + } } diff --git a/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java index 545a3584e6e..be778cbf29f 100644 --- a/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.UserHandle; +import android.provider.Settings; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; @@ -81,6 +82,25 @@ public class RecentLocationRequestPreferenceControllerTest { verify(mCategory, times(3)).addPreference(any()); } + @Test + public void updateState_whenAppListMoreThanThree_showSystem() { + when(mController.mRecentLocationApps.getAppListSorted(false)) + .thenReturn(createMockRequest(2)); + when(mController.mRecentLocationApps.getAppListSorted(true)) + .thenReturn(createMockRequest(3)); + + mController.displayPreference(mScreen); + verify(mCategory, times(2)).addPreference(any()); + + Settings.Secure.putInt( + mContext.getContentResolver(), + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, + 1); + + mController.displayPreference(mScreen); + verify(mCategory, times(5)).addPreference(any()); + } + @Test public void updateState_workProfile_shouldShowOnlyWorkProfileApps() { final List requests = createMockRequest(6); diff --git a/tests/robotests/src/com/android/settings/nfc/NfcForegroundPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/nfc/NfcForegroundPreferenceControllerTest.java index 4c2195c50e9..63fa320fbe2 100644 --- a/tests/robotests/src/com/android/settings/nfc/NfcForegroundPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/nfc/NfcForegroundPreferenceControllerTest.java @@ -26,7 +26,7 @@ import android.app.settings.SettingsEnums; import android.content.Context; import android.content.pm.PackageManager; -import androidx.preference.DropDownPreference; +import androidx.preference.ListPreference; import androidx.preference.PreferenceScreen; import com.android.settings.R; @@ -39,7 +39,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; @@ -56,7 +55,7 @@ public class NfcForegroundPreferenceControllerTest { private PackageManager mManager; private Context mContext; - private DropDownPreference mPreference; + private ListPreference mPreference; private NfcForegroundPreferenceController mController; private FakeFeatureFactory mFakeFeatureFactory; @@ -67,7 +66,9 @@ public class NfcForegroundPreferenceControllerTest { when(mContext.getPackageManager()).thenReturn(mManager); mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); mController = new NfcForegroundPreferenceController(mContext, PREF_KEY); - mPreference = new DropDownPreference(mContext); + mPreference = new ListPreference(mContext); + mPreference.setEntries(R.array.nfc_payment_favor); + mPreference.setEntryValues(R.array.nfc_payment_favor_values); when(mScreen.findPreference(PREF_KEY)).thenReturn(mPreference); } @@ -146,17 +147,14 @@ public class NfcForegroundPreferenceControllerTest { final CharSequence favorDefault = mContext.getText(R.string.nfc_payment_favor_default); final CharSequence favorOpen = mContext.getText(R.string.nfc_payment_favor_open); - assertThat(mPreference.getEntry()).isEqualTo(favorDefault); - assertThat(mPreference.getSummary()).isEqualTo(favorDefault); - - mPreference.setValueIndex(0); - mPreference.callChangeListener(mPreference.getEntryValues()[0]); + mPreference.setValueIndex(1); + mPreference.callChangeListener(mPreference.getEntryValues()[1]); verify(mPaymentBackend).setForegroundMode(true); assertThat(mPreference.getEntry()).isEqualTo(favorOpen); assertThat(mPreference.getSummary()).isEqualTo(favorOpen); - mPreference.setValueIndex(1); - mPreference.callChangeListener(mPreference.getEntryValues()[1]); + mPreference.setValueIndex(0); + mPreference.callChangeListener(mPreference.getEntryValues()[0]); verify(mPaymentBackend).setForegroundMode(false); assertThat(mPreference.getEntry()).isEqualTo(favorDefault); assertThat(mPreference.getSummary()).isEqualTo(favorDefault); @@ -168,16 +166,16 @@ public class NfcForegroundPreferenceControllerTest { mController.displayPreference(mScreen); mController.onPaymentAppsChanged(); - mPreference.setValueIndex(0); - mPreference.callChangeListener(mPreference.getEntryValues()[0]); + mPreference.setValueIndex(1); + mPreference.callChangeListener(mPreference.getEntryValues()[1]); verify(mPaymentBackend).setForegroundMode(true); verify(mFakeFeatureFactory.metricsFeatureProvider).action(mContext, SettingsEnums.ACTION_NFC_PAYMENT_FOREGROUND_SETTING); - mPreference.setValueIndex(1); - mPreference.callChangeListener(mPreference.getEntryValues()[1]); + mPreference.setValueIndex(0); + mPreference.callChangeListener(mPreference.getEntryValues()[0]); verify(mPaymentBackend).setForegroundMode(false); verify(mFakeFeatureFactory.metricsFeatureProvider).action(mContext, SettingsEnums.ACTION_NFC_PAYMENT_ALWAYS_SETTING); } -} \ No newline at end of file +} diff --git a/tests/robotests/src/com/android/settings/nfc/PaymentSettingsTest.java b/tests/robotests/src/com/android/settings/nfc/PaymentSettingsTest.java index 32eedd87b3a..458bc93d38c 100644 --- a/tests/robotests/src/com/android/settings/nfc/PaymentSettingsTest.java +++ b/tests/robotests/src/com/android/settings/nfc/PaymentSettingsTest.java @@ -53,7 +53,7 @@ import java.util.List; @Config(shadows = {PaymentSettingsTest.ShadowPaymentBackend.class, ShadowNfcAdapter.class}) public class PaymentSettingsTest { - static final String PAYMENT_KEY = "nfc_payment"; + static final String PAYMENT_KEY = "nfc_payment_app"; static final String FOREGROUND_KEY = "nfc_foreground"; private Context mContext; @@ -160,4 +160,4 @@ public class PaymentSettingsTest { return mAppInfos; } } -} \ No newline at end of file +} diff --git a/tests/robotests/src/com/android/settings/users/UserSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserSettingsTest.java index 5c13e2f8de6..2c694ace881 100644 --- a/tests/robotests/src/com/android/settings/users/UserSettingsTest.java +++ b/tests/robotests/src/com/android/settings/users/UserSettingsTest.java @@ -664,6 +664,7 @@ public class UserSettingsTest { public void onPreferenceClick_addSupervisedUserClicked_startIntentWithAction() { final String intentPackage = "testPackage"; final String intentAction = UserManager.ACTION_CREATE_SUPERVISED_USER; + final int intentFlags = Intent.FLAG_ACTIVITY_NEW_TASK; final int metricsAction = SettingsEnums.ACTION_USER_SUPERVISED_ADD; try { setConfigSupervisedUserCreationPackage(intentPackage); @@ -676,6 +677,7 @@ public class UserSettingsTest { verify(mFragment).startActivity(captor.capture()); assertThat(captor.getValue().getPackage()).isEqualTo(intentPackage); assertThat(captor.getValue().getAction()).isEqualTo(intentAction); + assertThat(captor.getValue().getFlags() & intentFlags).isGreaterThan(0); verify(mMetricsFeatureProvider).action(any(), eq(metricsAction)); } finally { diff --git a/tests/unit/src/com/android/settings/applications/AppLocaleUtilTest.java b/tests/unit/src/com/android/settings/applications/AppLocaleUtilTest.java new file mode 100644 index 00000000000..22a055f34da --- /dev/null +++ b/tests/unit/src/com/android/settings/applications/AppLocaleUtilTest.java @@ -0,0 +1,135 @@ +/* + * 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.applications; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settingslib.applications.ApplicationsState.AppEntry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class AppLocaleUtilTest { + @Mock + private PackageManager mPackageManager; + @Mock + private ActivityManager mActivityManager; + @Mock + private AppEntry mEntry; + @Mock + private ApplicationInfo mApplicationInfo; + @Mock + private Resources mResources; + + private Context mContext; + private String mDisallowedPackage = "com.disallowed.package"; + private String mAallowedPackage = "com.allowed.package"; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager); + } + + @Test + public void isDisplayLocaleUi_showUI() throws PackageManager.NameNotFoundException { + setTestAppEntry(mAallowedPackage); + setDisallowedPackageName(mDisallowedPackage); + setApplicationInfo(/*no platform key*/false); + mEntry.hasLauncherEntry = true; + + assertTrue(AppLocaleUtil.canDisplayLocaleUi(mContext, mEntry)); + } + + @Test + public void isDisplayLocaleUi_notShowUI_hasPlatformKey() + throws PackageManager.NameNotFoundException { + setTestAppEntry(mAallowedPackage); + setDisallowedPackageName(mDisallowedPackage); + setApplicationInfo(/*has platform key*/true); + mEntry.hasLauncherEntry = true; + + assertFalse(AppLocaleUtil.canDisplayLocaleUi(mContext, mEntry)); + } + + @Test + public void isDisplayLocaleUi_notShowUI_noLauncherEntry() + throws PackageManager.NameNotFoundException { + setTestAppEntry(mAallowedPackage); + setDisallowedPackageName(mDisallowedPackage); + setApplicationInfo(/*no platform key*/false); + mEntry.hasLauncherEntry = false; + + assertFalse(AppLocaleUtil.canDisplayLocaleUi(mContext, mEntry)); + } + + @Test + public void isDisplayLocaleUi_notShowUI_matchDisallowedPackageList() + throws PackageManager.NameNotFoundException { + setTestAppEntry(mDisallowedPackage); + setDisallowedPackageName(mDisallowedPackage); + setApplicationInfo(/*no platform key*/false); + mEntry.hasLauncherEntry = false; + + assertFalse(AppLocaleUtil.canDisplayLocaleUi(mContext, mEntry)); + } + + private void setTestAppEntry(String packageName) { + mEntry.info = mApplicationInfo; + mApplicationInfo.packageName = packageName; + } + + private void setDisallowedPackageName(String packageName) { + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getStringArray(anyInt())).thenReturn(new String[]{packageName}); + } + + private void setApplicationInfo(boolean signedWithPlatformKey) + throws PackageManager.NameNotFoundException { + ApplicationInfo applicationInfo = new ApplicationInfo(); + if (signedWithPlatformKey) { + applicationInfo.privateFlags = applicationInfo.privateFlags + | ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY; + } + + PackageInfo packageInfo = new PackageInfo(); + packageInfo.applicationInfo = applicationInfo; + when(mPackageManager.getPackageInfoAsUser(anyString(), anyInt(), anyInt())).thenReturn( + packageInfo); + } +} diff --git a/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java b/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java index e185354e5ed..1042a6a20a7 100644 --- a/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java +++ b/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java @@ -53,7 +53,6 @@ public class AppLocaleDetailsTest { private Context mContext; private LocaleList mSystemLocales; - private Locale mSimLocale; private LocaleList mAppLocale; private String[] mAssetLocales; @@ -69,7 +68,8 @@ public class AppLocaleDetailsTest { when(mContext.getSystemService(LocaleManager.class)).thenReturn(mLocaleManager); setupInitialLocales("en", - "uk", + "tw", + "jp", "en, uk, jp, ne", new String[]{"en", "ne", "ms", "pa"}); } @@ -88,6 +88,8 @@ public class AppLocaleDetailsTest { @Test @UiThreadTest public void handleAllLocalesData_1stLocaleOfSuggestedLocaleListIsAppLocale() { + Locale simCountryLocale = new Locale("zh", "TW"); + Locale networkCountryLocale = new Locale("ja", "JP"); DummyAppLocaleDetailsHelper helper = new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); @@ -95,25 +97,17 @@ public class AppLocaleDetailsTest { Locale locale = Iterables.get(helper.getSuggestedLocales(), 0); assertTrue(locale.equals(mAppLocale.get(0))); + assertTrue(helper.getSuggestedLocales().contains(simCountryLocale)); + assertTrue(helper.getSuggestedLocales().contains(networkCountryLocale)); } @Test @UiThreadTest - public void handleAllLocalesData_2ndLocaleOfSuggestedLocaleListIsSimLocale() { - DummyAppLocaleDetailsHelper helper = - new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); - - helper.handleAllLocalesData(); - - Locale locale = Iterables.get(helper.getSuggestedLocales(), 1); - assertTrue(locale.equals(mSimLocale)); - } - - @Test - @UiThreadTest - public void handleAllLocalesData_withoutAppLocale_1stLocaleOfSuggestedLocaleListIsSimLocal() { + public void handleAllLocalesData_withoutAppLocale_1stSuggestedLocaleIsSimCountryLocale() { + Locale simCountryLocale = new Locale("zh", "TW"); setupInitialLocales("", - "uk", + "tw", + "", "en, uk, jp, ne", new String[]{"en", "ne", "ms", "pa"}); DummyAppLocaleDetailsHelper helper = @@ -122,13 +116,34 @@ public class AppLocaleDetailsTest { helper.handleAllLocalesData(); Locale locale = Iterables.get(helper.getSuggestedLocales(), 0); - assertTrue(locale.equals(mSimLocale)); + assertTrue(locale.equals(simCountryLocale)); + assertFalse(helper.getSuggestedLocales().contains(mAppLocale.get(0))); } @Test @UiThreadTest - public void handleAllLocalesData_noAppAndSimLocale_1stLocaleIsFirstOneInSystemLocales() { + public void handleAllLocalesData_withoutAppLocale_1stSuggestedLocaleIsNetworkCountryLocale() { + Locale networkCountryLocale = new Locale("en", "GB"); setupInitialLocales("", + "", + "gb", + "en, uk, jp, ne", + new String[]{"en", "ne", "ms", "pa"}); + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + + Locale locale = Iterables.get(helper.getSuggestedLocales(), 0); + assertTrue(locale.equals(networkCountryLocale)); + assertFalse(helper.getSuggestedLocales().contains(mAppLocale.get(0))); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_noAppAndSimNetworkLocale_1stLocaleIsFirstOneInSystemLocales() { + setupInitialLocales("", + "", "", "en, uk, jp, ne", new String[]{"en", "ne", "ms", "pa"}); @@ -175,25 +190,34 @@ public class AppLocaleDetailsTest { /** * Sets the initial Locale data * - * @param appLocale Application locale, it shall be a language tag. - * example: "en" - * @param simLocale SIM carrier locale, it shall be a language tag. - * example: "en" - * @param systemLocales System 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"} + * @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 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 simLocale, + String simCountry, + String networkCountry, String systemLocales, String[] assetLocales) { mAppLocale = LocaleList.forLanguageTags(appLocale); - mSimLocale = Locale.forLanguageTag(simLocale); mSystemLocales = LocaleList.forLanguageTags(systemLocales); mAssetLocales = assetLocales; - when(mTelephonyManager.getSimLocale()).thenReturn(simLocale.isEmpty() ? null : mSimLocale); + when(mTelephonyManager.getSimCountryIso()).thenReturn(simCountry); + when(mTelephonyManager.getNetworkCountryIso()).thenReturn(networkCountry); when(mLocaleManager.getApplicationLocales(anyString())).thenReturn(mAppLocale); } diff --git a/tests/unit/src/com/android/settings/applications/appinfo/AppLocalePreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/appinfo/AppLocalePreferenceControllerTest.java index d7e3f923297..526b6cc9e3e 100644 --- a/tests/unit/src/com/android/settings/applications/appinfo/AppLocalePreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/appinfo/AppLocalePreferenceControllerTest.java @@ -18,8 +18,6 @@ package com.android.settings.applications.appinfo; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.spy; - import android.content.Context; import android.util.FeatureFlagUtils; @@ -37,20 +35,27 @@ import org.mockito.MockitoAnnotations; public class AppLocalePreferenceControllerTest { private Context mContext; + private boolean mCanDisplayLocaleUi; private AppLocalePreferenceController mController; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = spy(ApplicationProvider.getApplicationContext()); + mContext = ApplicationProvider.getApplicationContext(); - mController = spy(new AppLocalePreferenceController(mContext, "test_key")); + mController = new AppLocalePreferenceController(mContext, "test_key") { + @Override + boolean canDisplayLocaleUi() { + return mCanDisplayLocaleUi; + } + }; FeatureFlagUtils .setEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION, true); } @Test - public void getAvailabilityStatus_featureFlagOff_shouldReturnUnavailable() { + public void getAvailabilityStatus_canShowUiButFeatureFlagOff_shouldReturnUnavailable() { + mCanDisplayLocaleUi = true; FeatureFlagUtils .setEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION, false); @@ -59,8 +64,28 @@ public class AppLocalePreferenceControllerTest { } @Test - public void getAvailabilityStatus_featureFlagOn_shouldReturnAvailable() { + public void getAvailabilityStatus_canShowUiAndFeatureFlagOn_shouldReturnAvailable() { + mCanDisplayLocaleUi = true; + assertThat(mController.getAvailabilityStatus()) .isEqualTo(BasePreferenceController.AVAILABLE); } + + @Test + public void getAvailabilityStatus_featureFlagOnButCanNotShowUi_shouldReturnUnavailable() { + mCanDisplayLocaleUi = false; + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void getAvailabilityStatus_featureFlagOffAndCanNotShowUi_shouldReturnUnavailable() { + mCanDisplayLocaleUi = false; + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION, false); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } }