diff --git a/res/layout/locale_dialog.xml b/res/layout/locale_dialog.xml new file mode 100644 index 00000000000..cbdb37eb23e --- /dev/null +++ b/res/layout/locale_dialog.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index b7b98f26038..69d25122649 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -228,6 +228,11 @@ Enables Bluetooth LE audio feature if the device supports LE audio hardware capabilities. + + Enable Bluetooth LE audio Allow List + + Enable Bluetooth LE audio allow list feature. + Media devices @@ -372,6 +377,21 @@ https://support.google.com/android?p=per_language_app_settings + + Change system language to %s ? + + + Your device settings and regional preferences will change. + + + Change + + + %s not available + + + This language can’t be used as a system language, but you’ve let apps and websites know you prefer this language. + Regional preferences diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 7e720c908e0..b93ff41c034 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -345,6 +345,11 @@ android:title="@string/bluetooth_enable_leaudio" android:summary="@string/bluetooth_enable_leaudio_summary" /> + + diff --git a/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java b/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java index c630267e10c..f19795fa8de 100644 --- a/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java +++ b/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java @@ -18,7 +18,6 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.ON; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; import android.content.Context; import android.provider.Settings; @@ -45,13 +44,13 @@ public class CameraFlashNotificationPreferenceController extends TogglePreferenc @Override public boolean isChecked() { return Settings.System.getInt(mContext.getContentResolver(), - SETTING_KEY_CAMERA_FLASH_NOTIFICATION, OFF) != OFF; + Settings.System.CAMERA_FLASH_NOTIFICATION, OFF) != OFF; } @Override public boolean setChecked(boolean isChecked) { return Settings.System.putInt(mContext.getContentResolver(), - SETTING_KEY_CAMERA_FLASH_NOTIFICATION, (isChecked ? ON : OFF)); + Settings.System.CAMERA_FLASH_NOTIFICATION, (isChecked ? ON : OFF)); } @Override diff --git a/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java b/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java index 8774043c8d3..5a16a30bc9e 100644 --- a/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java +++ b/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java @@ -19,8 +19,6 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.FlashNotificationsUtil.ACTION_FLASH_NOTIFICATION_START_PREVIEW; import static com.android.settings.accessibility.FlashNotificationsUtil.EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_SHORT_PREVIEW; import android.content.ContentResolver; @@ -95,10 +93,10 @@ public class FlashNotificationsPreviewPreferenceController extends @NonNull Lifecycle.Event event) { if (event == Lifecycle.Event.ON_RESUME) { mContentResolver.registerContentObserver( - Settings.System.getUriFor(SETTING_KEY_CAMERA_FLASH_NOTIFICATION), + Settings.System.getUriFor(Settings.System.CAMERA_FLASH_NOTIFICATION), /* notifyForDescendants= */ false, mContentObserver); mContentResolver.registerContentObserver( - Settings.System.getUriFor(SETTING_KEY_SCREEN_FLASH_NOTIFICATION), + Settings.System.getUriFor(Settings.System.SCREEN_FLASH_NOTIFICATION), /* notifyForDescendants= */ false, mContentObserver); } else if (event == Lifecycle.Event.ON_PAUSE) { mContentResolver.unregisterContentObserver(mContentObserver); diff --git a/src/com/android/settings/accessibility/FlashNotificationsUtil.java b/src/com/android/settings/accessibility/FlashNotificationsUtil.java index 429936e41ba..544f835d8b5 100644 --- a/src/com/android/settings/accessibility/FlashNotificationsUtil.java +++ b/src/com/android/settings/accessibility/FlashNotificationsUtil.java @@ -16,8 +16,6 @@ package com.android.settings.accessibility; -import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; - import android.content.Context; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; @@ -43,19 +41,10 @@ class FlashNotificationsUtil { static final String EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE = "com.android.internal.intent.extra.FLASH_NOTIFICATION_PREVIEW_TYPE"; - static final String SETTING_KEY_CAMERA_FLASH_NOTIFICATION = - "camera_flash_notification"; - static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION = - "screen_flash_notification"; - static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR = - "screen_flash_notification_color_global"; - static final int TYPE_SHORT_PREVIEW = 0; static final int TYPE_LONG_PREVIEW = 1; - static final int DEFAULT_SCREEN_FLASH_COLOR = - ScreenFlashNotificationColor.YELLOW.mColorInt; - + static final int DEFAULT_SCREEN_FLASH_COLOR = ScreenFlashNotificationColor.YELLOW.mColorInt; @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -128,9 +117,9 @@ class FlashNotificationsUtil { final boolean isTorchAvailable = FlashNotificationsUtil.isTorchAvailable(context); final boolean isCameraFlashEnabled = Settings.System.getInt(context.getContentResolver(), - SETTING_KEY_CAMERA_FLASH_NOTIFICATION, State.OFF) != State.OFF; + Settings.System.CAMERA_FLASH_NOTIFICATION, State.OFF) != State.OFF; final boolean isScreenFlashEnabled = Settings.System.getInt(context.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION, State.OFF) != State.OFF; + Settings.System.SCREEN_FLASH_NOTIFICATION, State.OFF) != State.OFF; return ((isTorchAvailable && isCameraFlashEnabled) ? State.CAMERA : State.OFF) | (isScreenFlashEnabled ? State.SCREEN : State.OFF); diff --git a/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java b/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java index 546ae19eb88..2b96dcffb86 100644 --- a/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java +++ b/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java @@ -19,8 +19,6 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import static com.android.settings.accessibility.FlashNotificationsUtil.DEFAULT_SCREEN_FLASH_COLOR; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR; import android.content.Context; import android.graphics.Color; @@ -59,7 +57,7 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc @Override public boolean isChecked() { return Settings.System.getInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION, OFF) != OFF; + Settings.System.SCREEN_FLASH_NOTIFICATION, OFF) != OFF; } @Override @@ -67,7 +65,7 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc if (isChecked) checkAndSetInitialColor(); return Settings.System.putInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION, (isChecked ? ON : OFF)); + Settings.System.SCREEN_FLASH_NOTIFICATION, (isChecked ? ON : OFF)); } @Override @@ -79,7 +77,8 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc public CharSequence getSummary() { return FlashNotificationsUtil.getColorDescriptionText(mContext, Settings.System.getInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, DEFAULT_SCREEN_FLASH_COLOR)); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, + DEFAULT_SCREEN_FLASH_COLOR)); } @Override @@ -94,12 +93,12 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc if (getPreferenceKey().equals(preference.getKey()) && mParentFragment != null) { final int initialColor = Settings.System.getInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, DEFAULT_SCREEN_FLASH_COLOR); final Consumer consumer = color -> { Settings.System.putInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, color); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, color); refreshColorSummary(); }; @@ -115,10 +114,10 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc private void checkAndSetInitialColor() { if (Settings.System.getInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, Color.TRANSPARENT) + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, Color.TRANSPARENT) == Color.TRANSPARENT) { Settings.System.putInt(mContext.getContentResolver(), - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, DEFAULT_SCREEN_FLASH_COLOR); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, DEFAULT_SCREEN_FLASH_COLOR); } } diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java index 0bccfe2da86..f8c1f643a0c 100644 --- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java +++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java @@ -25,14 +25,11 @@ import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; import android.credentials.CredentialManager; -import android.credentials.ListEnabledProvidersException; -import android.credentials.ListEnabledProvidersResponse; +import android.credentials.CredentialProviderInfo; import android.credentials.SetEnabledProvidersException; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.os.CancellationSignal; import android.os.OutcomeReceiver; import android.os.UserHandle; import android.util.IconDrawableFactory; @@ -71,10 +68,9 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl private final PackageManager mPm; private final IconDrawableFactory mIconFactory; - private final List mServices; + private final List mServices; private final Set mEnabledPackageNames; private final @Nullable CredentialManager mCredentialManager; - private final CancellationSignal mCancellationSignal = new CancellationSignal(); private final Executor mExecutor; private final Map mPrefs = new HashMap<>(); // key is package name @@ -132,42 +128,20 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl lifecycleOwner, mCredentialManager.getCredentialProviderServices( getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY)); - - mCredentialManager.listEnabledProviders( - mCancellationSignal, - mExecutor, - new OutcomeReceiver() { - @Override - public void onResult(ListEnabledProvidersResponse result) { - Set enabledPackages = new HashSet<>(); - for (String flattenedComponentName : result.getProviderComponentNames()) { - ComponentName cn = - ComponentName.unflattenFromString(flattenedComponentName); - if (cn != null) { - enabledPackages.add(cn.getPackageName()); - } - } - - setEnabledPackageNames(enabledPackages); - } - - @Override - public void onError(ListEnabledProvidersException e) { - Log.e(TAG, "listEnabledProviders error: " + e.toString()); - } - }); } @VisibleForTesting - void setAvailableServices(LifecycleOwner lifecycleOwner, List availableServices) { + void setAvailableServices( + LifecycleOwner lifecycleOwner, List availableServices) { mServices.clear(); mServices.addAll(availableServices); - } - @VisibleForTesting - void setEnabledPackageNames(Set enabledPackages) { mEnabledPackageNames.clear(); - mEnabledPackageNames.addAll(enabledPackages); + for (CredentialProviderInfo cpi : availableServices) { + if (cpi.isEnabled()) { + mEnabledPackageNames.add(cpi.getServiceInfo().packageName); + } + } for (String packageName : mPrefs.keySet()) { mPrefs.get(packageName).setChecked(mEnabledPackageNames.contains(packageName)); @@ -189,22 +163,22 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl PreferenceGroup group = screen.findPreference(getPreferenceKey()); Context context = screen.getContext(); - for (ServiceInfo serviceInfo : mServices) { - CharSequence title = ""; - if (serviceInfo.nonLocalizedLabel != null) { - title = serviceInfo.loadLabel(mPm); - } - - group.addPreference( - addProviderPreference( - context, - title, - mIconFactory.getBadgedIcon( - serviceInfo, serviceInfo.applicationInfo, getUser()), - serviceInfo.packageName)); + for (CredentialProviderInfo service : mServices) { + group.addPreference(createPreference(context, service)); } } + /** Creates a preference object based on the provider info. */ + @VisibleForTesting + public SwitchPreference createPreference(Context context, CredentialProviderInfo service) { + CharSequence label = service.getLabel(context); + return addProviderPreference( + context, + label == null ? "" : label, + service.getServiceIcon(mContext), + service.getServiceInfo().packageName); + } + /** * Enables the package name as an enabled credential manager provider. * @@ -246,9 +220,10 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl public List getEnabledSettings() { // Get all the component names that match the enabled package names. List enabledServices = new ArrayList<>(); - for (ServiceInfo service : mServices) { - if (mEnabledPackageNames.contains(service.packageName)) { - enabledServices.add(service.getComponentName().flattenToString()); + for (CredentialProviderInfo service : mServices) { + ComponentName cn = service.getServiceInfo().getComponentName(); + if (mEnabledPackageNames.contains(service.getServiceInfo().packageName)) { + enabledServices.add(cn.flattenToString()); } } diff --git a/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceController.java new file mode 100644 index 00000000000..23506b3ccc3 --- /dev/null +++ b/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceController.java @@ -0,0 +1,129 @@ +/* + * Copyright 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.development; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothStatusCodes; +import android.content.Context; +import android.os.SystemProperties; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.SwitchPreference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.development.DeveloperOptionsPreferenceController; + +/** + * Preference controller to control Bluetooth LE audio feature + */ +public class BluetoothLeAudioAllowListPreferenceController + extends DeveloperOptionsPreferenceController + implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin { + + private static final String PREFERENCE_KEY = "bluetooth_enable_leaudio_allow_list"; + + private static final String LE_AUDIO_ALLOW_LIST_SWITCH_SUPPORT_PROPERTY = + "ro.bluetooth.leaudio_allow_list.supported"; + @VisibleForTesting + static final String LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY = + "persist.bluetooth.leaudio.enable_allow_list"; + + private static final String LE_AUDIO_DYNAMIC_SWITCH_PROPERTY = + "ro.bluetooth.leaudio_switcher.supported"; + @VisibleForTesting + static final String LE_AUDIO_DYNAMIC_ENABLED_PROPERTY = + "persist.bluetooth.leaudio_switcher.enabled"; + + @VisibleForTesting + BluetoothAdapter mBluetoothAdapter; + + private final DevelopmentSettingsDashboardFragment mFragment; + + @VisibleForTesting + boolean mChanged = false; + + public BluetoothLeAudioAllowListPreferenceController(Context context, + DevelopmentSettingsDashboardFragment fragment) { + super(context); + mFragment = fragment; + mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter(); + } + + @Override + public String getPreferenceKey() { + return PREFERENCE_KEY; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + BluetoothRebootDialog.show(mFragment); + mChanged = true; + return false; + } + + @Override + public void updateState(Preference preference) { + if (mBluetoothAdapter == null) { + return; + } + + int leAudioSupportedState = mBluetoothAdapter.isLeAudioSupported(); + boolean leAudioEnabled = false; + + if ((leAudioSupportedState == BluetoothStatusCodes.FEATURE_SUPPORTED) + || (leAudioSupportedState == BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED + && SystemProperties.getBoolean(LE_AUDIO_DYNAMIC_SWITCH_PROPERTY, false) + && SystemProperties.getBoolean(LE_AUDIO_DYNAMIC_ENABLED_PROPERTY, false))) { + leAudioEnabled = true; + } + + final boolean leAudioAllowListSupport = + SystemProperties.getBoolean(LE_AUDIO_ALLOW_LIST_SWITCH_SUPPORT_PROPERTY, false); + + if (leAudioEnabled && leAudioAllowListSupport) { + final boolean leAudioAllowListEnabled = + SystemProperties.getBoolean(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, false); + ((SwitchPreference) mPreference).setChecked(leAudioAllowListEnabled); + } else { + mPreference.setEnabled(false); + ((SwitchPreference) mPreference).setChecked(false); + } + } + + /** + * Called when the RebootDialog confirm is clicked. + */ + public void onRebootDialogConfirmed() { + if (!mChanged) { + return; + } + + final boolean leAudioAllowListEnabled = + SystemProperties.getBoolean(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, false); + SystemProperties.set(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, + Boolean.toString(!leAudioAllowListEnabled)); + } + + /** + * Called when the RebootDialog cancel is clicked. + */ + public void onRebootDialogCanceled() { + mChanged = false; + } +} diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index c732c0a5549..0d4ad4a97b9 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -389,6 +389,11 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra getDevelopmentOptionsController( BluetoothLeAudioPreferenceController.class); leAudioFeatureController.onRebootDialogConfirmed(); + + final BluetoothLeAudioAllowListPreferenceController leAudioAllowListController = + getDevelopmentOptionsController( + BluetoothLeAudioAllowListPreferenceController.class); + leAudioAllowListController.onRebootDialogConfirmed(); } @Override @@ -406,6 +411,11 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra getDevelopmentOptionsController( BluetoothLeAudioPreferenceController.class); leAudioFeatureController.onRebootDialogCanceled(); + + final BluetoothLeAudioAllowListPreferenceController leAudioAllowListController = + getDevelopmentOptionsController( + BluetoothLeAudioAllowListPreferenceController.class); + leAudioAllowListController.onRebootDialogCanceled(); } @Override @@ -602,6 +612,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new BluetoothAvrcpVersionPreferenceController(context)); controllers.add(new BluetoothMapVersionPreferenceController(context)); controllers.add(new BluetoothLeAudioPreferenceController(context, fragment)); + controllers.add(new BluetoothLeAudioAllowListPreferenceController(context, fragment)); controllers.add(new BluetoothA2dpHwOffloadPreferenceController(context, fragment)); controllers.add(new BluetoothLeAudioHwOffloadPreferenceController(context, fragment)); controllers.add(new BluetoothMaxConnectedAudioDevicesPreferenceController(context)); diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java index ee3f54f70e0..274817e3819 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java @@ -118,7 +118,7 @@ public class BatterySaverButtonPreferenceController extends @Override public boolean setChecked(boolean stateOn) { return BatterySaverUtils.setPowerSaveMode(mContext, stateOn, - true /* needFirstTimeWarning */); + false /* needFirstTimeWarning */); } @Override diff --git a/src/com/android/settings/localepicker/LocaleDialogFragment.java b/src/com/android/settings/localepicker/LocaleDialogFragment.java new file mode 100644 index 00000000000..63fc1792a46 --- /dev/null +++ b/src/com/android/settings/localepicker/LocaleDialogFragment.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.localepicker; + +import android.app.Activity; +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentManager; + +import com.android.internal.app.LocaleStore; +import com.android.settings.R; +import com.android.settings.RestrictedSettingsFragment; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; + +/** + * Create a dialog for system locale events. + */ +public class LocaleDialogFragment extends InstrumentedDialogFragment { + private static final String TAG = LocaleDialogFragment.class.getSimpleName(); + + static final int DIALOG_CONFIRM_SYSTEM_DEFAULT = 0; + static final int DIALOG_NOT_AVAILABLE_LOCALE = 1; + + static final String ARG_DIALOG_TYPE = "arg_dialog_type"; + static final String ARG_TARGET_LOCALE = "arg_target_locale"; + static final String ARG_RESULT_RECEIVER = "arg_result_receiver"; + + /** + * Show dialog + */ + public static void show( + @NonNull RestrictedSettingsFragment fragment, + int dialogType, + LocaleStore.LocaleInfo localeInfo) { + show(fragment, dialogType, localeInfo, null); + } + + /** + * Show dialog + */ + public static void show( + @NonNull RestrictedSettingsFragment fragment, + int dialogType, + LocaleStore.LocaleInfo localeInfo, + ResultReceiver resultReceiver) { + FragmentManager manager = fragment.getChildFragmentManager(); + Bundle args = new Bundle(); + args.putInt(ARG_DIALOG_TYPE, dialogType); + args.putSerializable(ARG_TARGET_LOCALE, localeInfo); + args.putParcelable(ARG_RESULT_RECEIVER, resultReceiver); + + LocaleDialogFragment localeDialogFragment = new LocaleDialogFragment(); + localeDialogFragment.setArguments(args); + localeDialogFragment.show(manager, TAG); + } + + @Override + public int getMetricsCategory() { + int dialogType = getArguments().getInt(ARG_DIALOG_TYPE); + switch (dialogType) { + case DIALOG_CONFIRM_SYSTEM_DEFAULT: + return SettingsEnums.DIALOG_SYSTEM_LOCALE_CHANGE; + case DIALOG_NOT_AVAILABLE_LOCALE: + return SettingsEnums.DIALOG_SYSTEM_LOCALE_UNAVAILABLE; + default: + return SettingsEnums.DIALOG_SYSTEM_LOCALE_CHANGE; + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + LocaleDialogController controller = new LocaleDialogController(this); + LocaleDialogController.DialogContent dialogContent = controller.getDialogContent(); + ViewGroup viewGroup = (ViewGroup) LayoutInflater.from(getContext()).inflate( + R.layout.locale_dialog, null); + setDialogTitle(viewGroup, dialogContent.mTitle); + setDialogMessage(viewGroup, dialogContent.mMessage); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()) + .setView(viewGroup); + if (!dialogContent.mPositiveButton.isEmpty()) { + builder.setPositiveButton(dialogContent.mPositiveButton, controller); + } + if (!dialogContent.mNegativeButton.isEmpty()) { + builder.setNegativeButton(dialogContent.mNegativeButton, controller); + } + return builder.create(); + } + + private static void setDialogTitle(View root, String content) { + TextView titleView = root.findViewById(R.id.dialog_title); + if (titleView == null) { + return; + } + titleView.setText(content); + } + + private static void setDialogMessage(View root, String content) { + TextView textView = root.findViewById(R.id.dialog_msg); + if (textView == null) { + return; + } + textView.setText(content); + } + + static class LocaleDialogController implements DialogInterface.OnClickListener { + private final Context mContext; + private final int mDialogType; + private final LocaleStore.LocaleInfo mLocaleInfo; + private final ResultReceiver mResultReceiver; + + LocaleDialogController( + @NonNull Context context, @NonNull LocaleDialogFragment dialogFragment) { + mContext = context; + Bundle arguments = dialogFragment.getArguments(); + mDialogType = arguments.getInt(ARG_DIALOG_TYPE); + mLocaleInfo = (LocaleStore.LocaleInfo) arguments.getSerializable( + ARG_TARGET_LOCALE); + mResultReceiver = (ResultReceiver) arguments.getParcelable(ARG_RESULT_RECEIVER); + } + + LocaleDialogController(@NonNull LocaleDialogFragment dialogFragment) { + this(dialogFragment.getContext(), dialogFragment); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (mResultReceiver != null && mDialogType == DIALOG_CONFIRM_SYSTEM_DEFAULT) { + Bundle bundle = new Bundle(); + bundle.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT); + if (which == DialogInterface.BUTTON_POSITIVE) { + mResultReceiver.send(Activity.RESULT_OK, bundle); + } else if (which == DialogInterface.BUTTON_NEGATIVE) { + mResultReceiver.send(Activity.RESULT_CANCELED, bundle); + } + } + } + + @VisibleForTesting + DialogContent getDialogContent() { + DialogContent + dialogContent = new DialogContent(); + switch (mDialogType) { + case DIALOG_CONFIRM_SYSTEM_DEFAULT: + dialogContent.mTitle = String.format(mContext.getString( + R.string.title_change_system_locale), mLocaleInfo.getFullNameNative()); + dialogContent.mMessage = mContext.getString( + R.string.desc_notice_device_locale_settings_change); + dialogContent.mPositiveButton = mContext.getString( + R.string.button_label_confirmation_of_system_locale_change); + dialogContent.mNegativeButton = mContext.getString(R.string.cancel); + break; + case DIALOG_NOT_AVAILABLE_LOCALE: + dialogContent.mTitle = String.format(mContext.getString( + R.string.title_unavailable_locale), mLocaleInfo.getFullNameNative()); + dialogContent.mMessage = mContext.getString(R.string.desc_unavailable_locale); + dialogContent.mPositiveButton = mContext.getString(R.string.okay); + break; + default: + break; + } + return dialogContent; + } + + @VisibleForTesting + static class DialogContent { + String mTitle = ""; + String mMessage = ""; + String mPositiveButton = ""; + String mNegativeButton = ""; + } + } +} diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java index b3c2e3071af..bece4140153 100644 --- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java +++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java @@ -16,10 +16,14 @@ package com.android.settings.localepicker; +import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.os.Bundle; +import android.os.Handler; import android.os.LocaleList; +import android.os.Looper; +import android.os.ResultReceiver; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; @@ -49,9 +53,11 @@ class LocaleDragAndDropAdapter private static final String TAG = "LocaleDragAndDropAdapter"; private static final String CFGKEY_SELECTED_LOCALES = "selectedLocales"; private final Context mContext; - private final List mFeedItemList; + private List mFeedItemList; + private List mCacheItemList; private final ItemTouchHelper mItemTouchHelper; private RecyclerView mParentView = null; + private LocaleListEditor mParent; private boolean mRemoveMode = false; private boolean mDragEnabled = true; private NumberFormat mNumberFormatter = NumberFormat.getNumberInstance(); @@ -81,12 +87,15 @@ class LocaleDragAndDropAdapter } } - public LocaleDragAndDropAdapter(Context context, List feedItemList) { + LocaleDragAndDropAdapter(LocaleListEditor parent, + List feedItemList) { mFeedItemList = feedItemList; - mContext = context; + mParent = parent; + mCacheItemList = new ArrayList<>(feedItemList); + mContext = parent.getContext(); final float dragElevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, - context.getResources().getDisplayMetrics()); + mContext.getResources().getDisplayMetrics()); mItemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback( ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0 /* no swipe */) { @@ -168,13 +177,13 @@ class LocaleDragAndDropAdapter checkbox.setOnCheckedChangeListener(null); checkbox.setChecked(mRemoveMode ? feedItem.getChecked() : false); checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - LocaleStore.LocaleInfo feedItem = - (LocaleStore.LocaleInfo) dragCell.getTag(); - feedItem.setChecked(isChecked); - } - }); + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + LocaleStore.LocaleInfo feedItem = + (LocaleStore.LocaleInfo) dragCell.getTag(); + feedItem.setChecked(isChecked); + } + }); } @Override @@ -308,6 +317,42 @@ class LocaleDragAndDropAdapter }); } + public void doTheUpdateWithMovingLocaleItem() { + LocaleStore.LocaleInfo localeInfo = mFeedItemList.get(0); + if (!localeInfo.getLocale().equals(LocalePicker.getLocales().get(0))) { + LocaleDialogFragment.show(mParent, + LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, + localeInfo, + new ResultReceiver(new Handler(Looper.getMainLooper())) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + super.onReceiveResult(resultCode, resultData); + int type = resultData.getInt(LocaleDialogFragment.ARG_DIALOG_TYPE); + if (type == LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT) { + if (resultCode == Activity.RESULT_OK) { + doTheUpdate(); + if (!localeInfo.isTranslated()) { + LocaleDialogFragment.show(mParent, + LocaleDialogFragment + .DIALOG_NOT_AVAILABLE_LOCALE, + localeInfo); + } + } else { + if (!localeInfo.getLocale() + .equals(mCacheItemList.get(0).getLocale())) { + mFeedItemList = new ArrayList<>(mCacheItemList); + notifyDataSetChanged(); + } + } + mCacheItemList = new ArrayList<>(mFeedItemList); + } + } + }); + } else { + doTheUpdate(); + } + } + private void setDragEnabled(boolean enabled) { mDragEnabled = enabled; } @@ -315,6 +360,7 @@ class LocaleDragAndDropAdapter /** * Saves the list of checked locales to preserve status when the list is destroyed. * (for instance when the device is rotated) + * * @param outInstanceState Bundle in which to place the saved state */ public void saveState(Bundle outInstanceState) { @@ -332,6 +378,7 @@ class LocaleDragAndDropAdapter /** * Restores the list of checked locales to preserve status when the list is recreated. * (for instance when the device is rotated) + * * @param savedInstanceState Bundle with the data saved by {@link #saveState(Bundle)} */ public void restoreState(Bundle savedInstanceState) { diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java index 89efe53b4ff..bdb9295807e 100644 --- a/src/com/android/settings/localepicker/LocaleListEditor.java +++ b/src/com/android/settings/localepicker/LocaleListEditor.java @@ -105,7 +105,7 @@ public class LocaleListEditor extends RestrictedSettingsFragment { LocaleStore.fillCache(this.getContext()); final List feedsList = getUserLocaleList(); - mAdapter = new LocaleDragAndDropAdapter(this.getContext(), feedsList); + mAdapter = new LocaleDragAndDropAdapter(this, feedsList); } @Override diff --git a/src/com/android/settings/localepicker/LocaleRecyclerView.java b/src/com/android/settings/localepicker/LocaleRecyclerView.java index d32a735d48c..5d469bf7f10 100644 --- a/src/com/android/settings/localepicker/LocaleRecyclerView.java +++ b/src/com/android/settings/localepicker/LocaleRecyclerView.java @@ -40,7 +40,7 @@ class LocaleRecyclerView extends RecyclerView { if (e.getAction() == MotionEvent.ACTION_UP || e.getAction() == MotionEvent.ACTION_CANCEL) { LocaleDragAndDropAdapter adapter = (LocaleDragAndDropAdapter) this.getAdapter(); if (adapter != null) { - adapter.doTheUpdate(); + adapter.doTheUpdateWithMovingLocaleItem(); } } return super.onTouchEvent(e); diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index a5629901966..13bb51928b6 100644 --- a/src/com/android/settings/network/SubscriptionUtil.java +++ b/src/com/android/settings/network/SubscriptionUtil.java @@ -50,6 +50,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -560,7 +561,8 @@ public class SubscriptionUtil { if (TextUtils.isEmpty(rawPhoneNumber)) { return null; } - String countryIso = MccTable.countryCodeForMcc(subscriptionInfo.getMccString()); + String countryIso = MccTable.countryCodeForMcc(subscriptionInfo.getMccString()) + .toUpperCase(Locale.ROOT); return PhoneNumberUtils.formatNumber(rawPhoneNumber, countryIso); } diff --git a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java index 92cd911843a..40bae5d7918 100644 --- a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java +++ b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; +import androidx.annotation.VisibleForTesting; import androidx.core.text.BidiFormatter; import androidx.lifecycle.LifecycleObserver; import androidx.preference.Preference; @@ -54,7 +55,7 @@ import java.util.List; public class AppChannelsBypassingDndPreferenceController extends NotificationPreferenceController implements PreferenceControllerMixin, LifecycleObserver { - private static final String KEY = "zen_mode_bypassing_app_channels_list"; + @VisibleForTesting static final String KEY = "zen_mode_bypassing_app_channels_list"; private static final String ARG_FROM_SETTINGS = "fromSettings"; private RestrictedSwitchPreference mAllNotificationsToggle; @@ -74,8 +75,8 @@ public class AppChannelsBypassingDndPreferenceController extends NotificationPre mAllNotificationsToggle = new RestrictedSwitchPreference(mPreferenceCategory.getContext()); mAllNotificationsToggle.setTitle(R.string.zen_mode_bypassing_app_channels_toggle_all); mAllNotificationsToggle.setDisabledByAdmin(mAdmin); - mAllNotificationsToggle.setEnabled( - (mAdmin == null || !mAllNotificationsToggle.isDisabledByAdmin())); + mAllNotificationsToggle.setEnabled(!mAppRow.banned + && (mAdmin == null || !mAllNotificationsToggle.isDisabledByAdmin())); mAllNotificationsToggle.setOnPreferenceClickListener( new Preference.OnPreferenceClickListener() { @Override @@ -206,6 +207,9 @@ public class AppChannelsBypassingDndPreferenceController extends NotificationPre } private boolean areAllChannelsBypassing() { + if (mAppRow.banned) { + return false; + } boolean allChannelsBypassing = true; for (NotificationChannel channel : mChannels) { if (showNotification(channel)) { @@ -226,7 +230,7 @@ public class AppChannelsBypassingDndPreferenceController extends NotificationPre * Whether notifications from this channel would show if DND weren't on. */ private boolean showNotification(NotificationChannel channel) { - return channel.getImportance() != IMPORTANCE_NONE; + return !mAppRow.banned && channel.getImportance() != IMPORTANCE_NONE; } /** diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt index 87a916a7e43..c2716b6e5ca 100644 --- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt +++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt @@ -32,6 +32,7 @@ import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider +import com.android.settings.spa.core.instrumentation.SpaLogProvider import com.android.settings.spa.development.UsageStatsPageProvider import com.android.settings.spa.home.HomePageProvider import com.android.settings.spa.network.NetworkAndInternetPageProvider @@ -87,4 +88,5 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { ), ) } + override val logger = SpaLogProvider } diff --git a/src/com/android/settings/spa/SpaActivity.kt b/src/com/android/settings/spa/SpaActivity.kt index 55883c12264..27f7241b257 100644 --- a/src/com/android/settings/spa/SpaActivity.kt +++ b/src/com/android/settings/spa/SpaActivity.kt @@ -16,18 +16,29 @@ package com.android.settings.spa +import android.app.ActivityManager import android.content.Context import android.content.Intent +import android.os.RemoteException import android.os.UserHandle +import android.util.Log import com.android.settingslib.spa.framework.BrowseActivity +import com.android.settingslib.spa.framework.util.SESSION_BROWSE +import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL import com.android.settingslib.spa.framework.util.appendSpaParams class SpaActivity : BrowseActivity() { companion object { + private const val TAG = "SpaActivity" @JvmStatic fun Context.startSpaActivity(destination: String) { val intent = Intent(this, SpaActivity::class.java) .appendSpaParams(destination = destination) + if (isLaunchedFromInternal()) { + intent.appendSpaParams(sessionName = SESSION_BROWSE) + } else { + intent.appendSpaParams(sessionName = SESSION_EXTERNAL) + } startActivity(intent) } @@ -37,5 +48,15 @@ class SpaActivity : BrowseActivity() { startSpaActivity("$destinationPrefix/$packageName/${UserHandle.myUserId()}") return true } + + fun Context.isLaunchedFromInternal(): Boolean { + var pkg: String? = null + try { + pkg = ActivityManager.getService().getLaunchedFromPackage(getActivityToken()) + } catch (e: RemoteException) { + Log.v(TAG, "Could not talk to activity manager.", e) + } + return applicationContext.packageName == pkg + } } } diff --git a/src/com/android/settings/spa/core/instrumentation/MetricsDataModel.kt b/src/com/android/settings/spa/core/instrumentation/MetricsDataModel.kt new file mode 100644 index 00000000000..62aa8df871a --- /dev/null +++ b/src/com/android/settings/spa/core/instrumentation/MetricsDataModel.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.spa.core.instrumentation + +import androidx.annotation.VisibleForTesting + +/** + * This class stores some metrics temporary data. Such as the timestamp of the page enter for + * calculating the duration time on page. + */ +class MetricsDataModel { + @VisibleForTesting + val pageTimeStampList = mutableListOf() + + fun addTimeStamp(dataItem: PageTimeStamp){ + pageTimeStampList.add(dataItem) + } + + fun getPageDuration(pageId: String, removed: Boolean = true): String { + val lastItem = pageTimeStampList.findLast { it.pageId == pageId } + if (removed && lastItem != null) { + pageTimeStampList.remove(lastItem) + } + return if (lastItem == null) "0" + else (System.currentTimeMillis() - lastItem.timeStamp).toString() + } +} diff --git a/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt b/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt new file mode 100644 index 00000000000..9b3e2d68ac2 --- /dev/null +++ b/src/com/android/settings/spa/core/instrumentation/SpaLogProvider.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.spa.core.instrumentation + +import android.app.settings.SettingsEnums +import android.os.Bundle +import androidx.annotation.VisibleForTesting +import com.android.settings.core.instrumentation.ElapsedTimeUtils +import com.android.settings.core.instrumentation.SettingsStatsLog +import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME +import com.android.settingslib.spa.framework.common.LogCategory +import com.android.settingslib.spa.framework.common.LogEvent +import com.android.settingslib.spa.framework.common.SpaLogger +import com.android.settingslib.spa.framework.util.SESSION_BROWSE +import com.android.settingslib.spa.framework.util.SESSION_EXTERNAL +import com.android.settingslib.spa.framework.util.SESSION_SEARCH +import com.android.settingslib.spa.framework.util.SESSION_SLICE +import com.android.settingslib.spa.framework.util.SESSION_UNKNOWN + +/** + * To receive the events from spa framework and logging the these events. + */ +object SpaLogProvider : SpaLogger { + private val dataModel = MetricsDataModel() + + override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) { + when(event) { + LogEvent.PAGE_ENTER, LogEvent.PAGE_LEAVE -> + write(SpaLogData(id, event, extraData, dataModel)) + else -> return //TODO(b/253979024): Will be implemented in subsequent CLs. + } + } + + private fun write(data: SpaLogData) { + with(data) { + SettingsStatsLog.write( + SettingsStatsLog.SETTINGS_SPA_REPORTED /* atomName */, + getSessionType(), + getPageId(), + getTarget(), + getAction(), + getKey(), + getValue(), + getPreValue(), + getElapsedTime() + ) + } + } +} + +@VisibleForTesting +class SpaLogData(val id: String, val event: LogEvent, + val extraData: Bundle, val dataModel: MetricsDataModel) { + + fun getSessionType(): Int { + if (!extraData.containsKey(LOG_DATA_SESSION_NAME)) { + return SettingsEnums.SESSION_UNKNOWN + } + val sessionSource = extraData.getString(LOG_DATA_SESSION_NAME) + return when(sessionSource) { + SESSION_BROWSE -> SettingsEnums.BROWSE + SESSION_SEARCH -> SettingsEnums.SEARCH + SESSION_SLICE -> SettingsEnums.SLICE_TYPE + SESSION_EXTERNAL -> SettingsEnums.EXTERNAL + else -> SettingsEnums.SESSION_UNKNOWN + } + } + + fun getPageId(): String { + return when(event) { + LogEvent.PAGE_ENTER, LogEvent.PAGE_LEAVE -> id + else -> getPageIdByEntryId(id) + } + } + + //TODO(b/253979024): Will be implemented in subsequent CLs. + fun getTarget(): String? { + return null + } + + fun getAction(): Int { + return event.action + } + + //TODO(b/253979024): Will be implemented in subsequent CLs. + fun getKey(): String? { + return null + } + + fun getValue(): String? { + when(event) { + LogEvent.PAGE_ENTER -> dataModel.addTimeStamp( + PageTimeStamp(id, System.currentTimeMillis())) + LogEvent.PAGE_LEAVE -> return dataModel.getPageDuration(id) + else -> {} //TODO(b/253979024): Will be implemented in subsequent CLs. + } + return null + } + + //TODO(b/253979024): Will be implemented in subsequent CLs. + fun getPreValue(): String? { + return null + } + + fun getElapsedTime(): Long { + return ElapsedTimeUtils.getElapsedTime(System.currentTimeMillis()) + } + + //TODO(b/253979024): Will be implemented in subsequent CLs. + private fun getPageIdByEntryId(id: String): String { + return "" + } +} + +/** + * The buffer is keeping the time stamp while spa page entering. + */ +data class PageTimeStamp(val pageId: String, val timeStamp: Long) diff --git a/tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java index db4e5210315..02ab32d6576 100644 --- a/tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java @@ -16,7 +16,8 @@ package com.android.settings.accessibility; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; @@ -81,28 +82,30 @@ public class CameraFlashNotificationPreferenceControllerTest { @Test public void isChecked_setOff_assertFalse() { - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, OFF); assertThat(mController.isChecked()).isFalse(); } @Test public void isChecked_setOn_assertTrue() { - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, ON); assertThat(mController.isChecked()).isTrue(); } @Test - public void setChecked_setTrue_assertNotZero() { + public void setChecked_setTrue_assertNotOff() { mController.setChecked(true); - assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, - 0)).isNotEqualTo(0); + assertThat( + Settings.System.getInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, + OFF)).isNotEqualTo(OFF); } @Test - public void setChecked_setFalse_assertNotOne() { + public void setChecked_setFalse_assertNotOn() { mController.setChecked(false); - assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, - 1)).isNotEqualTo(1); + assertThat( + Settings.System.getInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, + OFF)).isNotEqualTo(ON); } @Test diff --git a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java index 5f1dfbb1e63..98da926b3f2 100644 --- a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java @@ -18,8 +18,6 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.FlashNotificationsUtil.ACTION_FLASH_NOTIFICATION_START_PREVIEW; import static com.android.settings.accessibility.FlashNotificationsUtil.EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_LONG_PREVIEW; import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_SHORT_PREVIEW; import static com.android.settings.accessibility.ShadowFlashNotificationsUtils.setFlashNotificationsState; @@ -48,7 +46,6 @@ import androidx.test.core.app.ApplicationProvider; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -131,7 +128,6 @@ public class FlashNotificationsPreviewPreferenceControllerTest { verify(mPreference).setEnabled(eq(true)); } - @Ignore @Test public void testHandlePreferenceTreeClick_invalidPreference() { mController.handlePreferenceTreeClick(mock(Preference.class)); @@ -165,16 +161,16 @@ public class FlashNotificationsPreviewPreferenceControllerTest { public void onStateChanged_onResume_cameraUri_verifyRegister() { mController.onStateChanged(mock(LifecycleOwner.class), Lifecycle.Event.ON_RESUME); verify(mContentResolver).registerContentObserver( - eq(Settings.System.getUriFor(SETTING_KEY_CAMERA_FLASH_NOTIFICATION)), anyBoolean(), - eq(mController.mContentObserver)); + eq(Settings.System.getUriFor(Settings.System.CAMERA_FLASH_NOTIFICATION)), + anyBoolean(), eq(mController.mContentObserver)); } @Test public void onStateChanged_onResume_screenUri_verifyRegister() { mController.onStateChanged(mock(LifecycleOwner.class), Lifecycle.Event.ON_RESUME); verify(mContentResolver).registerContentObserver( - eq(Settings.System.getUriFor(SETTING_KEY_SCREEN_FLASH_NOTIFICATION)), anyBoolean(), - eq(mController.mContentObserver)); + eq(Settings.System.getUriFor(Settings.System.SCREEN_FLASH_NOTIFICATION)), + anyBoolean(), eq(mController.mContentObserver)); } @Test diff --git a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java index c5fe3a7c792..f943e3a3657 100644 --- a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java @@ -21,8 +21,8 @@ import static android.hardware.camera2.CameraCharacteristics.LENS_FACING; import static android.hardware.camera2.CameraCharacteristics.LENS_FACING_BACK; import static android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import static com.android.settings.accessibility.FlashNotificationsUtil.getColorDescriptionText; import static com.android.settings.accessibility.FlashNotificationsUtil.getFlashNotificationsState; import static com.android.settings.accessibility.FlashNotificationsUtil.getScreenColor; @@ -156,8 +156,8 @@ public class FlashNotificationsUtilTest { @Test public void getFlashNotificationsState_torchPresent_cameraOff_screenOff_assertOff() { setTorchPresent(); - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0); - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, OFF); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, OFF); assertThat(getFlashNotificationsState(mContext)) .isEqualTo(FlashNotificationsUtil.State.OFF); @@ -166,8 +166,8 @@ public class FlashNotificationsUtilTest { @Test public void getFlashNotificationsState_torchNotPresent_cameraOn_screenOff_assertOff() { setTorchNotPresent(); - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, ON); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, OFF); assertThat(getFlashNotificationsState(mContext)) .isEqualTo(FlashNotificationsUtil.State.OFF); @@ -176,8 +176,8 @@ public class FlashNotificationsUtilTest { @Test public void getFlashNotificationsState_torchPresent_cameraOn_screenOff_assertCamera() { setTorchPresent(); - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, ON); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, OFF); assertThat(getFlashNotificationsState(mContext)) .isEqualTo(FlashNotificationsUtil.State.CAMERA); @@ -186,8 +186,8 @@ public class FlashNotificationsUtilTest { @Test public void getFlashNotificationsState_torchPresent_cameraOff_screenOn_assertScreen() { setTorchPresent(); - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0); - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 1); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, OFF); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, ON); assertThat(getFlashNotificationsState(mContext)) .isEqualTo(FlashNotificationsUtil.State.SCREEN); @@ -196,8 +196,8 @@ public class FlashNotificationsUtilTest { @Test public void testGetFlashNotificationsState_torchPresent_cameraOn_screenOn_assertCameraScreen() { setTorchPresent(); - Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 1); + Settings.System.putInt(mContentResolver, Settings.System.CAMERA_FLASH_NOTIFICATION, ON); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, ON); assertThat(getFlashNotificationsState(mContext)) .isEqualTo(FlashNotificationsUtil.State.CAMERA_SCREEN); diff --git a/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java index 0662daa6fbb..5b8afe65ab5 100644 --- a/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java @@ -16,9 +16,9 @@ package com.android.settings.accessibility; +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import static com.android.settings.accessibility.FlashNotificationsUtil.DEFAULT_SCREEN_FLASH_COLOR; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; -import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR; import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.google.common.truth.Truth.assertThat; @@ -114,48 +114,50 @@ public class ScreenFlashNotificationPreferenceControllerTest { @Test public void isChecked_setOff_assertFalse() { - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, OFF); assertThat(mController.isChecked()).isFalse(); } @Test public void isChecked_setOn_assertTrue() { - Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 1); + Settings.System.putInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, ON); assertThat(mController.isChecked()).isTrue(); } @Test public void setChecked_whenTransparentColor_setTrue_assertNotTransparentColor() { Settings.System.putInt(mContentResolver, - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, Color.TRANSPARENT); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, Color.TRANSPARENT); mController.setChecked(true); assertThat(Settings.System.getInt(mContentResolver, - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, 0)) - .isEqualTo(DEFAULT_SCREEN_FLASH_COLOR); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, 0)).isEqualTo( + DEFAULT_SCREEN_FLASH_COLOR); } @Test public void setChecked_whenNotTransparent_setTrue_assertSameColor() { Settings.System.putInt(mContentResolver, - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, 0x4D0000FF); + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, 0x4D0000FF); mController.setChecked(true); assertThat(Settings.System.getInt(mContentResolver, - SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, 0)) + Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, 0)) .isEqualTo(0x4D0000FF); } @Test public void setChecked_setTrue_assertOn() { mController.setChecked(true); - assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, - 0)).isEqualTo(1); + assertThat( + Settings.System.getInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, + OFF)).isEqualTo(ON); } @Test public void setChecked_setFalse_assertOff() { mController.setChecked(false); - assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, - 1)).isEqualTo(0); + assertThat( + Settings.System.getInt(mContentResolver, Settings.System.SCREEN_FLASH_NOTIFICATION, + OFF)).isEqualTo(OFF); } @Test diff --git a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceControllerTest.java new file mode 100644 index 00000000000..f4e52bada6a --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioAllowListPreferenceControllerTest.java @@ -0,0 +1,106 @@ +/* + * 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.development; + +import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED; + +import static com.android.settings.development.BluetoothLeAudioAllowListPreferenceController + .LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.os.SystemProperties; + +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class BluetoothLeAudioAllowListPreferenceControllerTest { + + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private DevelopmentSettingsDashboardFragment mFragment; + + @Mock + private BluetoothAdapter mBluetoothAdapter; + + private Context mContext; + private SwitchPreference mPreference; + private BluetoothLeAudioPreferenceController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mPreference = new SwitchPreference(mContext); + mController = spy(new BluetoothLeAudioPreferenceController(mContext, mFragment)); + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())) + .thenReturn(mPreference); + mController.mBluetoothAdapter = mBluetoothAdapter; + mController.displayPreference(mPreferenceScreen); + when(mBluetoothAdapter.isLeAudioSupported()) + .thenReturn(FEATURE_SUPPORTED); + } + + @Test + public void onRebootDialogConfirmedAsLeAudioAllowListDisabled_shouldSwitchStatus() { + SystemProperties.set(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, Boolean.toString(false)); + mController.mChanged = true; + + mController.onRebootDialogConfirmed(); + final boolean mode = SystemProperties.getBoolean( + LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, false); + assertThat(mode).isFalse(); + } + + + @Test + public void onRebootDialogConfirmedAsLeAudioAllowListEnabled_shouldSwitchStatus() { + SystemProperties.set(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, Boolean.toString(true)); + mController.mChanged = true; + + mController.onRebootDialogConfirmed(); + final boolean status = SystemProperties.getBoolean( + LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, false); + assertThat(status).isTrue(); + } + + @Test + public void onRebootDialogCanceled_shouldNotSwitchStatus() { + SystemProperties.set(LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, Boolean.toString(false)); + mController.mChanged = true; + + mController.onRebootDialogCanceled(); + final boolean status = SystemProperties.getBoolean( + LE_AUDIO_ALLOW_LIST_ENABLED_PROPERTY, false); + assertThat(status).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java index 4f26754b212..9a7ef409f95 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java @@ -18,17 +18,13 @@ package com.android.settings.fuelgauge.batterysaver; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.Intent; import android.os.PowerManager; import android.provider.Settings; import android.provider.SettingsSlicesContract; @@ -85,6 +81,15 @@ public class BatterySaverButtonPreferenceControllerTest { assertThat(mPreference.isChecked()).isFalse(); } + @Test + public void onSwitchChanged_isCheckedAndAcked_setPowerSaveMode() { + setLowPowerWarningAcked(/* acked= */ 1); + + mController.onSwitchChanged(/* switchView= */ null, /* isChecked= */ true); + + verify(mPowerManager).setPowerSaveModeEnabled(true); + } + @Test public void updateState_lowPowerOn_preferenceIsChecked() { when(mPowerManager.isPowerSaveMode()).thenReturn(true); @@ -104,11 +109,10 @@ public class BatterySaverButtonPreferenceControllerTest { } @Test - public void setChecked_on_showWarningMessage() { + public void setChecked_on_setPowerSaveMode() { mController.setChecked(true); - verify(mContext).sendBroadcast(any(Intent.class)); - verify(mPowerManager, never()).setPowerSaveModeEnabled(anyBoolean()); + verify(mPowerManager).setPowerSaveModeEnabled(true); } @Test @@ -143,5 +147,9 @@ public class BatterySaverButtonPreferenceControllerTest { mContext.getContentResolver(), Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED, acked); + Settings.Secure.putInt( + mContext.getContentResolver(), + Settings.Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, + acked); } } diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java index 52d10833ef8..734f610b264 100644 --- a/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java @@ -93,13 +93,13 @@ public class KeyboardLayoutPickerControllerTest { } @Test - public void testLifecycle_onStart_NoInputDevice_shouldFinish() { + public void testLifecycle_onStart_NoInputDevice_shouldReturn() { final FragmentActivity activity = Robolectric.setupActivity(FragmentActivity.class); when(mInputManager.getInputDeviceByDescriptor(anyString())).thenReturn(null); when(mFragment.getActivity()).thenReturn(activity); mController.onStart(); - assertThat(activity.isFinishing()).isTrue(); + verify(mInputManager, never()).getEnabledKeyboardLayoutsForInputDevice(any()); } @Test diff --git a/tests/robotests/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceControllerTest.java new file mode 100644 index 00000000000..bcb641d9c23 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceControllerTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification.app; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.ParceledListSlice; + +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.PrimarySwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowApplication; + +import java.util.ArrayList; +import java.util.Collections; + +@RunWith(RobolectricTestRunner.class) +public class AppChannelsBypassingDndPreferenceControllerTest { + + @Mock + private NotificationBackend mBackend; + + private NotificationBackend.AppRow mAppRow; + private AppChannelsBypassingDndPreferenceController mController; + + private PreferenceScreen mPreferenceScreen; + private PreferenceCategory mCategory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Context context = ApplicationProvider.getApplicationContext(); + + mAppRow = new NotificationBackend.AppRow(); + mAppRow.uid = 42; + mAppRow.pkg = "com.example.exampling"; + + mController = new AppChannelsBypassingDndPreferenceController(context, mBackend); + mController.onResume(mAppRow, null, null, null, null, null, new ArrayList<>()); + + PreferenceManager preferenceManager = new PreferenceManager(context); + mPreferenceScreen = preferenceManager.createPreferenceScreen(context); + mCategory = new PreferenceCategory(context); + mCategory.setKey(AppChannelsBypassingDndPreferenceController.KEY); + mPreferenceScreen.addPreference(mCategory); + } + + @Test + public void displayPreference_showsAllAndChannels() { + when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn( + buildGroupList(true, true, false)); + + mController.displayPreference(mPreferenceScreen); + ShadowApplication.runBackgroundTasks(); + + assertThat(mCategory.getPreferenceCount()).isEqualTo(4); // "All" + 3 channels + assertThat(mCategory.getPreference(0).getTitle().toString()).isEqualTo( + "Allow all notifications"); + assertThat(mCategory.getPreference(1).getTitle().toString()).isEqualTo("Channel 1"); + assertThat(mCategory.getPreference(2).getTitle().toString()).isEqualTo("Channel 2"); + assertThat(mCategory.getPreference(3).getTitle().toString()).isEqualTo("Channel 3"); + } + + @Test + public void displayPreference_canToggleAllInterrupt() { + when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn( + buildGroupList(true, true, false)); + + mController.displayPreference(mPreferenceScreen); + ShadowApplication.runBackgroundTasks(); + + assertThat(mCategory.getPreference(0).isEnabled()).isTrue(); + } + + @Test + public void displayPreference_canToggleInterruptIfChannelEnabled() { + when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn( + buildGroupList(true, false, true)); + + mController.displayPreference(mPreferenceScreen); + ShadowApplication.runBackgroundTasks(); + + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 1)).isSwitchEnabled()).isTrue(); + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 2)).isSwitchEnabled()).isFalse(); + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 3)).isSwitchEnabled()).isTrue(); + } + + @Test + public void displayPreference_appBlocked_cannotToggleAllOrChannelInterrupts() { + mAppRow.banned = true; + when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn( + buildGroupList(true, false, true)); + + mController.displayPreference(mPreferenceScreen); + ShadowApplication.runBackgroundTasks(); + + assertThat(mCategory.getPreference(0).isEnabled()).isFalse(); + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 1)).isSwitchEnabled()).isFalse(); + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 2)).isSwitchEnabled()).isFalse(); + assertThat(((PrimarySwitchPreference) mCategory.getPreference( + 3)).isSwitchEnabled()).isFalse(); + } + + private static ParceledListSlice buildGroupList( + boolean... enabledByChannel) { + NotificationChannelGroup group = new NotificationChannelGroup("group", "The Group"); + for (int i = 0; i < enabledByChannel.length; i++) { + group.addChannel(new NotificationChannel("channel-" + (i + 1), "Channel " + (i + 1), + enabledByChannel[i] ? NotificationManager.IMPORTANCE_DEFAULT + : NotificationManager.IMPORTANCE_NONE)); + } + return new ParceledListSlice<>(Collections.singletonList(group)); + } +} diff --git a/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt b/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt index 8a894d5b4a8..46b956e6ccc 100644 --- a/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/SpaActivityTest.kt @@ -25,12 +25,15 @@ import com.android.settings.spa.SpaActivity.Companion.startSpaActivity import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp import com.android.settingslib.spa.framework.util.KEY_DESTINATION import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Answers import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule @@ -39,9 +42,14 @@ class SpaActivityTest { @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() - @Mock + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var context: Context + @Before + fun setUp() { + `when`(context.applicationContext.packageName).thenReturn("com.android.settings") + } + @Test fun startSpaActivity() { context.startSpaActivity(DESTINATION) diff --git a/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/MetricsDataModelTest.kt b/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/MetricsDataModelTest.kt new file mode 100644 index 00000000000..2ba03029b5d --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/MetricsDataModelTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.spa.core.instrumentation + +import android.os.SystemClock +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests for {@link MetricsDataModel}. */ +@RunWith(AndroidJUnit4::class) +class MetricsDataModelTest { + private val TEST_PID = "pseudo_page_id" + + private lateinit var metricsDataModel: MetricsDataModel + + @Before + fun setUp() { + metricsDataModel = MetricsDataModel() + } + + @Test + fun initMetricsDataModel() { + assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(0) + } + + @Test + fun addTimeStamp_addOnePageTimeStamp_sizeShouldBeOne() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + + assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(1) + } + + @Test + fun addTimeStamp_addTwoSamePageTimeStamp_sizeShouldBeTwo() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + + assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(2) + } + + @Test + fun getPageDuration_getExistPageId_mustFoundValue() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + SystemClock.sleep(5) + + assertThat(metricsDataModel.getPageDuration(TEST_PID).toInt()).isGreaterThan(0) + assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(0) + } + + @Test + fun getPageDuration_getNonExistPageId_valueShouldBeZero() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + + assertThat(metricsDataModel.getPageDuration("WRONG_ID").toLong()).isEqualTo(0L) + } + + @Test + fun getPageDuration_getExistPageIdAndDonotRemoved_sizeShouldBeOne() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, System.currentTimeMillis())) + SystemClock.sleep(5) + + assertThat(metricsDataModel.getPageDuration(TEST_PID, false).toLong()).isGreaterThan(0L) + assertThat(metricsDataModel.pageTimeStampList.size).isEqualTo(1) + } + + @Test + fun getPageDuration_getTwoExistPageId_theOrderIsLIFO() { + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, 10000L)) + metricsDataModel.addTimeStamp(PageTimeStamp(TEST_PID, 20000L)) + + // The formula is d1 = t1 - 20000, d2 = t2 - 10000 + // d2 - d1 = t2 - t1 + 10000, because t2 > t1 the result of d2 - d1 is greater 10000 + val duration1 = metricsDataModel.getPageDuration(TEST_PID).toLong() + SystemClock.sleep(5) + val duration2 = metricsDataModel.getPageDuration(TEST_PID).toLong() + + assertThat(duration2 - duration1).isGreaterThan(10000L) + } +} \ No newline at end of file diff --git a/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/SpaLogDataTest.kt b/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/SpaLogDataTest.kt new file mode 100644 index 00000000000..19be10ec892 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/core/instrumentation/SpaLogDataTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.spa.core.instrumentation + +import android.app.settings.SettingsEnums +import android.os.Bundle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME +import com.android.settingslib.spa.framework.common.LogEvent +import com.android.settingslib.spa.framework.util.SESSION_BROWSE +import com.android.settingslib.spa.framework.util.SESSION_SEARCH +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests for {@link SpaLogData}. */ +@RunWith(AndroidJUnit4::class) +class SpaLogDataTest { + private val TEST_PID = "pseudo_page_id" + + private lateinit var bundle: Bundle + private lateinit var dataModel: MetricsDataModel + + @Before + fun setUp() { + bundle = Bundle() + dataModel = MetricsDataModel() + } + + @Test + fun getSessionType_withoutSessionExtraData_returnSessionUnknow() { + val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel) + + assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SESSION_UNKNOWN) + } + + @Test + fun getSessionType_hasSessionBrowseExtraData_returnSessionBrowse() { + bundle.putString(LOG_DATA_SESSION_NAME, SESSION_BROWSE) + val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel) + + assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.BROWSE) + } + + @Test + fun getSessionType_hasSessionSearchExtraData_returnSessionSearch() { + bundle.putString(LOG_DATA_SESSION_NAME, SESSION_SEARCH) + val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel) + + assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SEARCH) + } + + @Test + fun getSessionType_hasSessionUnknownExtraData_returnSessionUnknow() { + bundle.putString(LOG_DATA_SESSION_NAME, "SESSION_OTHER") + val spaLogData = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel) + + assertThat(spaLogData.getSessionType()).isEqualTo(SettingsEnums.SESSION_UNKNOWN) + } + + @Test + fun getPageId_withPageEvent_returnInputId() { + val spaLogData1 = SpaLogData(TEST_PID, LogEvent.PAGE_ENTER, bundle, dataModel) + assertThat(spaLogData1.getPageId()).isEqualTo(TEST_PID) + + val spaLogData2 = SpaLogData(TEST_PID, LogEvent.PAGE_LEAVE, bundle, dataModel) + assertThat(spaLogData2.getPageId()).isEqualTo(TEST_PID) + } +} diff --git a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java index ff7e71a948f..2633ea7ad40 100644 --- a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java @@ -27,12 +27,14 @@ import static org.mockito.Mockito.spy; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.ServiceInfo; +import android.credentials.CredentialProviderInfo; import android.os.Looper; import androidx.lifecycle.Lifecycle; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -87,7 +89,7 @@ public class CredentialManagerPreferenceControllerTest { @Test public void getAvailabilityStatus_withServices_returnsAvailable() { CredentialManagerPreferenceController controller = - createControllerWithServices(Lists.newArrayList(createServiceInfo())); + createControllerWithServices(Lists.newArrayList(createCredentialProviderInfo())); assertThat(controller.isConnected()).isFalse(); assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE); } @@ -103,24 +105,59 @@ public class CredentialManagerPreferenceControllerTest { @Test public void displayPreference_withServices_preferencesAdded() { CredentialManagerPreferenceController controller = - createControllerWithServices(Lists.newArrayList(createServiceInfo())); + createControllerWithServices(Lists.newArrayList(createCredentialProviderInfo())); controller.displayPreference(mScreen); assertThat(controller.isConnected()).isFalse(); assertThat(mCredentialsPreferenceCategory.getPreferenceCount()).isEqualTo(1); } + @Test + public void buildSwitchPreference() { + CredentialProviderInfo providerInfo1 = + createCredentialProviderInfo( + "com.android.provider1", "ClassA", "Service Title", false); + CredentialProviderInfo providerInfo2 = + createCredentialProviderInfo( + "com.android.provider2", "ClassA", "Service Title", false); + CredentialManagerPreferenceController controller = + createControllerWithServices(Lists.newArrayList(providerInfo1, providerInfo2)); + assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE); + assertThat(controller.isConnected()).isFalse(); + + // Test the data is correct. + assertThat(providerInfo1.isEnabled()).isFalse(); + assertThat(providerInfo2.isEnabled()).isFalse(); + assertThat(controller.getEnabledProviders().size()).isEqualTo(0); + + // Toggle one provider and make sure it worked. + assertThat(controller.togglePackageNameEnabled("com.android.provider1")).isTrue(); + Set enabledProviders = controller.getEnabledProviders(); + assertThat(enabledProviders.size()).isEqualTo(1); + assertThat(enabledProviders.contains("com.android.provider1")).isTrue(); + + // Create the pref (checked). + SwitchPreference pref = controller.createPreference(mContext, providerInfo1); + assertThat(pref.getTitle().toString()).isEqualTo("Service Title"); + assertThat(pref.isChecked()).isTrue(); + + // Create the pref (not checked). + SwitchPreference pref2 = controller.createPreference(mContext, providerInfo2); + assertThat(pref2.getTitle().toString()).isEqualTo("Service Title"); + assertThat(pref2.isChecked()).isFalse(); + } + @Test public void getAvailabilityStatus_handlesToggleAndSave() { CredentialManagerPreferenceController controller = createControllerWithServices( Lists.newArrayList( - createServiceInfo("com.android.provider1", "ClassA"), - createServiceInfo("com.android.provider1", "ClassB"), - createServiceInfo("com.android.provider2", "ClassA"), - createServiceInfo("com.android.provider3", "ClassA"), - createServiceInfo("com.android.provider4", "ClassA"), - createServiceInfo("com.android.provider5", "ClassA"), - createServiceInfo("com.android.provider6", "ClassA"))); + createCredentialProviderInfo("com.android.provider1", "ClassA"), + createCredentialProviderInfo("com.android.provider1", "ClassB"), + createCredentialProviderInfo("com.android.provider2", "ClassA"), + createCredentialProviderInfo("com.android.provider3", "ClassA"), + createCredentialProviderInfo("com.android.provider4", "ClassA"), + createCredentialProviderInfo("com.android.provider5", "ClassA"), + createCredentialProviderInfo("com.android.provider6", "ClassA"))); assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE); assertThat(controller.isConnected()).isFalse(); @@ -177,8 +214,38 @@ public class CredentialManagerPreferenceControllerTest { assertThat(currentlyEnabledServices.contains("com.android.provider6/ClassA")).isFalse(); } + @Test + public void handlesCredentialProviderInfoEnabledDisabled() { + CredentialProviderInfo providerInfo1 = + createCredentialProviderInfo( + "com.android.provider1", "ClassA", "Service Title", false); + CredentialProviderInfo providerInfo2 = + createCredentialProviderInfo( + "com.android.provider2", "ClassA", "Service Title", true); + CredentialManagerPreferenceController controller = + createControllerWithServices(Lists.newArrayList(providerInfo1, providerInfo2)); + assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE); + assertThat(controller.isConnected()).isFalse(); + + // Test the data is correct. + assertThat(providerInfo1.isEnabled()).isFalse(); + assertThat(providerInfo2.isEnabled()).isTrue(); + + // Check that they are all actually registered. + Set enabledProviders = controller.getEnabledProviders(); + assertThat(enabledProviders.size()).isEqualTo(1); + assertThat(enabledProviders.contains("com.android.provider1")).isFalse(); + assertThat(enabledProviders.contains("com.android.provider2")).isTrue(); + + // Check that the settings string has the right component names. + List enabledServices = controller.getEnabledSettings(); + assertThat(enabledServices.size()).isEqualTo(1); + assertThat(enabledServices.contains("com.android.provider1/ClassA")).isFalse(); + assertThat(enabledServices.contains("com.android.provider2/ClassA")).isTrue(); + } + private CredentialManagerPreferenceController createControllerWithServices( - List availableServices) { + List availableServices) { CredentialManagerPreferenceController controller = new CredentialManagerPreferenceController( mContext, mCredentialsPreferenceCategory.getKey()); @@ -186,11 +253,17 @@ public class CredentialManagerPreferenceControllerTest { return controller; } - private ServiceInfo createServiceInfo() { - return createServiceInfo("com.android.provider", "CredManProvider"); + private CredentialProviderInfo createCredentialProviderInfo() { + return createCredentialProviderInfo("com.android.provider", "CredManProvider"); } - private ServiceInfo createServiceInfo(String packageName, String className) { + private CredentialProviderInfo createCredentialProviderInfo( + String packageName, String className) { + return createCredentialProviderInfo(packageName, className, null, false); + } + + private CredentialProviderInfo createCredentialProviderInfo( + String packageName, String className, CharSequence label, boolean isEnabled) { ServiceInfo si = new ServiceInfo(); si.packageName = packageName; si.name = className; @@ -200,6 +273,9 @@ public class CredentialManagerPreferenceControllerTest { si.applicationInfo.packageName = packageName; si.applicationInfo.nonLocalizedLabel = "test"; - return si; + return new CredentialProviderInfo.Builder(si) + .setOverrideLabel(label) + .setEnabled(isEnabled) + .build(); } } diff --git a/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java b/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java new file mode 100644 index 00000000000..5b10adf0ff6 --- /dev/null +++ b/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.localepicker; + +import static com.android.settings.localepicker.LocaleDialogFragment.ARG_DIALOG_TYPE; +import static com.android.settings.localepicker.LocaleDialogFragment.ARG_RESULT_RECEIVER; +import static com.android.settings.localepicker.LocaleDialogFragment.ARG_TARGET_LOCALE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.Activity; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.ResultReceiver; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ApplicationProvider; + +import com.android.internal.app.LocaleStore; +import com.android.settings.testutils.ResourcesUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.Locale; + +@UiThreadTest +public class LocaleDialogFragmentTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private Context mContext; + private LocaleDialogFragment mDialogFragment; + + @Before + public void setUp() throws Exception { + mContext = ApplicationProvider.getApplicationContext(); + mDialogFragment = new LocaleDialogFragment(); + } + + private void setArgument( + int type, ResultReceiver receiver) { + LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(Locale.ENGLISH); + Bundle args = new Bundle(); + args.putInt(ARG_DIALOG_TYPE, type); + args.putSerializable(ARG_TARGET_LOCALE, localeInfo); + args.putParcelable(ARG_RESULT_RECEIVER, receiver); + mDialogFragment.setArguments(args); + } + + @Test + public void getDialogContent_confirmSystemDefault_has2ButtonText() { + setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, null); + LocaleDialogFragment.LocaleDialogController controller = + new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); + + LocaleDialogFragment.LocaleDialogController.DialogContent dialogContent = + controller.getDialogContent(); + + assertEquals(ResourcesUtils.getResourcesString( + mContext, "button_label_confirmation_of_system_locale_change"), + dialogContent.mPositiveButton); + assertEquals(ResourcesUtils.getResourcesString(mContext, "cancel"), + dialogContent.mNegativeButton); + } + + @Test + public void getDialogContent_unavailableLocale_has1ButtonText() { + setArgument(LocaleDialogFragment.DIALOG_NOT_AVAILABLE_LOCALE, null); + LocaleDialogFragment.LocaleDialogController controller = + new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); + + LocaleDialogFragment.LocaleDialogController.DialogContent dialogContent = + controller.getDialogContent(); + + assertEquals(ResourcesUtils.getResourcesString(mContext, "okay"), + dialogContent.mPositiveButton); + assertTrue(dialogContent.mNegativeButton.isEmpty()); + } + + @Test + public void onClick_clickPositiveButton_sendOK() { + ResultReceiver resultReceiver = spy(new ResultReceiver(null)); + setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, resultReceiver); + LocaleDialogFragment.LocaleDialogController controller = + new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); + + controller.onClick(null, DialogInterface.BUTTON_POSITIVE); + + verify(resultReceiver).send(eq(Activity.RESULT_OK), any()); + } + + @Test + public void onClick_clickNegativeButton_sendCancel() { + ResultReceiver resultReceiver = spy(new ResultReceiver(null)); + setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, resultReceiver); + LocaleDialogFragment.LocaleDialogController controller = + new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); + + controller.onClick(null, DialogInterface.BUTTON_NEGATIVE); + + verify(resultReceiver).send(eq(Activity.RESULT_CANCELED), any()); + } + + @Test + public void getMetricsCategory_systemLocaleChange() { + setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, null); + + int result = mDialogFragment.getMetricsCategory(); + + assertEquals(SettingsEnums.DIALOG_SYSTEM_LOCALE_CHANGE, result); + } + + @Test + public void getMetricsCategory_unavailableLocale() { + setArgument(LocaleDialogFragment.DIALOG_NOT_AVAILABLE_LOCALE, null); + + int result = mDialogFragment.getMetricsCategory(); + + assertEquals(SettingsEnums.DIALOG_SYSTEM_LOCALE_UNAVAILABLE, result); + } +}