diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig index 3ed618bd6e0..3092b8fe72b 100644 --- a/aconfig/accessibility/accessibility_flags.aconfig +++ b/aconfig/accessibility/accessibility_flags.aconfig @@ -20,6 +20,16 @@ flag { } } +flag { + name: "check_prebundled_is_preinstalled" + namespace: "accessibility" + description: "Checks that all 'prebundled' components, used for grouping, are also preinstalled" + bug: "353888087" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "edit_shortcuts_in_full_screen" namespace: "accessibility" diff --git a/res/drawable/ic_zen_mode_trigger_with_activity.xml b/res/drawable/ic_zen_mode_trigger_with_activity.xml new file mode 100644 index 00000000000..567f01a87cc --- /dev/null +++ b/res/drawable/ic_zen_mode_trigger_with_activity.xml @@ -0,0 +1,26 @@ + + + + \ No newline at end of file diff --git a/res/drawable/ic_zen_mode_trigger_without_activity.xml b/res/drawable/ic_zen_mode_trigger_without_activity.xml new file mode 100644 index 00000000000..11a97f16953 --- /dev/null +++ b/res/drawable/ic_zen_mode_trigger_without_activity.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/res/layout-land/bluetooth_audio_streams_qr_code.xml b/res/layout-land/bluetooth_audio_streams_qr_code.xml new file mode 100644 index 00000000000..b35bc65f84f --- /dev/null +++ b/res/layout-land/bluetooth_audio_streams_qr_code.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + diff --git a/res/layout-land/qrcode_scanner_fragment.xml b/res/layout-land/qrcode_scanner_fragment.xml new file mode 100644 index 00000000000..0e563e32f6c --- /dev/null +++ b/res/layout-land/qrcode_scanner_fragment.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/advanced_bt_entity_sub.xml b/res/layout/advanced_bt_entity_sub.xml index 90ac4567176..dd8e43a0f73 100644 --- a/res/layout/advanced_bt_entity_sub.xml +++ b/res/layout/advanced_bt_entity_sub.xml @@ -45,7 +45,7 @@ android:layout_gravity="center" android:indeterminate="false" app:trackColor="@android:color/transparent" - app:indicatorColor="@color/bluetooth_battery_ring_indicator_color" + app:indicatorColor="@color/settingslib_materialColorPrimary" app:trackThickness="4dp" app:indicatorSize="76dp" app:indicatorInset="0dp" diff --git a/res/xml/bluetooth_audio_streams_qr_code.xml b/res/layout/bluetooth_audio_streams_qr_code.xml similarity index 91% rename from res/xml/bluetooth_audio_streams_qr_code.xml rename to res/layout/bluetooth_audio_streams_qr_code.xml index 5ec5505c4bb..fd521febc4a 100644 --- a/res/xml/bluetooth_audio_streams_qr_code.xml +++ b/res/layout/bluetooth_audio_streams_qr_code.xml @@ -47,7 +47,9 @@ + android:layout_height="@dimen/qrcode_size" + android:contentDescription="@string/audio_streams_qr_code_page_image_label" + android:focusable="true"/> + android:clipChildren="true" + android:contentDescription="@string/audio_streams_qr_code_scanner_label" + android:focusable="true"> #1F1F1F1F - - - #9ED582 diff --git a/res/values/strings.xml b/res/values/strings.xml index f994fbc911d..84eb74fe6b8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5477,6 +5477,8 @@ Grayscale Intensity + + Unavailable for grayscale mode or when color correction is disabled Green weak, deuteranomaly @@ -8124,9 +8126,9 @@ Allow visual signals - + Stay focused - + Additional actions @@ -9483,6 +9485,19 @@ Mode name + + Calendar events + + Sleep schedule + + While driving + + Linked to app + + Info and settings in %1$s + + Managed by %1$s + Warning @@ -12240,16 +12255,11 @@ Settings Panel - - Enable freeform windows - - Enable support for freeform windows. + + Enable freeform windowing experiences - - - Enable freeform windowing on second display - - Enable freeform windows only on secondary display. + + Enable desktop mode on secondary display Enable non-resizable in multi window @@ -13212,14 +13222,12 @@ Customize %1$s - - A reboot is required to enable legacy freeform windowing support. - - A reboot is required to change freeform windowing support. - - A reboot is required to force freeform windowing on secondary displays. + + A reboot is required to enable freeform window support. + + A reboot is required to update availability of freeform windowing experiences. + + A reboot is required to enable desktop mode on secondary displays. Reboot now @@ -13744,6 +13752,10 @@ Scan an audio stream QR code to listen with %1$s Can\u0027t edit password while sharing. To change the password, first turn off audio sharing. + + QR code + + QR code scanner diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 9420f59e881..88abadb5233 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -739,18 +739,15 @@ + android:title="@string/enable_desktop_mode" /> + android:title="@string/enable_freeform_support" /> + android:title="@string/enable_desktop_mode_on_secondary_display"/> + + + + + + + + @@ -49,14 +64,6 @@ android:title="@string/zen_category_exceptions" /> - - - - - diff --git a/src/com/android/settings/MainClear.java b/src/com/android/settings/MainClear.java index 9d219d783ba..9dadcb9600d 100644 --- a/src/com/android/settings/MainClear.java +++ b/src/com/android/settings/MainClear.java @@ -182,10 +182,13 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis } if (requestCode == KEYGUARD_REQUEST) { + final int userId = getActivity().getUserId(); if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(), false /* biometricsSuccessfullyAuthenticated */, - false /* biometricsAuthenticationRequested */)) { - Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRICS_REQUEST); + false /* biometricsAuthenticationRequested */, + userId)) { + Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRICS_REQUEST, + userId); return; } } diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index e94769ad61a..add5604a617 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -1487,23 +1487,25 @@ public final class Utils extends com.android.settingslib.Utils { /** * Request biometric authentication if all requirements for mandatory biometrics is satisfied. - * @param context of the corresponding activity/fragment + * + * @param context of the corresponding activity/fragment * @param biometricsSuccessfullyAuthenticated if the user has already authenticated using * biometrics - * @param biometricsAuthenticationRequested if the activity/fragment has already requested for - * biometric prompt + * @param biometricsAuthenticationRequested if the activity/fragment has already requested for + * biometric prompt + * @param userId user id for the authentication request * @return true if all requirements for mandatory biometrics is satisfied */ public static boolean requestBiometricAuthenticationForMandatoryBiometrics( @NonNull Context context, boolean biometricsSuccessfullyAuthenticated, - boolean biometricsAuthenticationRequested) { + boolean biometricsAuthenticationRequested, int userId) { final BiometricManager biometricManager = context.getSystemService(BiometricManager.class); if (biometricManager == null) { Log.e(TAG, "Biometric Manager is null."); return false; } - final int status = biometricManager.canAuthenticate( + final int status = biometricManager.canAuthenticate(userId, BiometricManager.Authenticators.MANDATORY_BIOMETRICS); return android.hardware.biometrics.Flags.mandatoryBiometrics() && status == BiometricManager.BIOMETRIC_SUCCESS @@ -1513,15 +1515,16 @@ public final class Utils extends com.android.settingslib.Utils { /** * Launch biometric prompt for mandatory biometrics. Call - * {@link #requestBiometricAuthenticationForMandatoryBiometrics(Context, boolean, boolean)} + * {@link #requestBiometricAuthenticationForMandatoryBiometrics(Context, boolean, boolean, int)} * to check if all requirements for mandatory biometrics is satisfied * before launching biometric prompt. * - * @param fragment corresponding fragment of the surface + * @param fragment corresponding fragment of the surface * @param requestCode for starting the new activity + * @param userId user id for the authentication request */ public static void launchBiometricPromptForMandatoryBiometrics(@NonNull Fragment fragment, - int requestCode) { + int requestCode, int userId) { final Intent intent = new Intent(); intent.putExtra(BIOMETRIC_PROMPT_AUTHENTICATORS, BiometricManager.Authenticators.MANDATORY_BIOMETRICS); @@ -1529,8 +1532,10 @@ public final class Utils extends com.android.settingslib.Utils { fragment.getString(R.string.cancel)); intent.putExtra(KeyguardManager.EXTRA_DESCRIPTION, fragment.getString(R.string.mandatory_biometrics_prompt_description)); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, true); + intent.putExtra(EXTRA_USER_ID, userId); intent.setClassName(SETTINGS_PACKAGE_NAME, - ConfirmDeviceCredentialActivity.class.getName()); + ConfirmDeviceCredentialActivity.InternalActivity.class.getName()); fragment.startActivityForResult(intent, requestCode); } diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index 23f8ec7fab1..d01806aa3a8 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -75,7 +75,8 @@ public class AccessibilitySettings extends DashboardFragment implements private static final String CATEGORY_AUDIO = "audio_category"; private static final String CATEGORY_SPEECH = "speech_category"; private static final String CATEGORY_DISPLAY = "display_category"; - private static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category"; + @VisibleForTesting + static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category"; private static final String CATEGORY_KEYBOARD_OPTIONS = "physical_keyboard_options_category"; @VisibleForTesting static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_category"; @@ -380,6 +381,7 @@ public class AccessibilitySettings extends DashboardFragment implements } protected void updateServicePreferences() { + final AccessibilityManager a11yManager = AccessibilityManager.getInstance(getPrefContext()); // Since services category is auto generated we have to do a pass // to generate it since services can come and go and then based on // the global accessibility state to decided whether it is enabled. @@ -410,8 +412,18 @@ public class AccessibilitySettings extends DashboardFragment implements AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM, mCategoryToPrefCategoryMap.get(CATEGORY_INTERACTION_CONTROL)); - final List preferenceList = getInstalledAccessibilityList( - getPrefContext()); + final List installedShortcutList = + a11yManager.getInstalledAccessibilityShortcutListAsUser(getPrefContext(), + UserHandle.myUserId()); + final List modifiableInstalledServiceList = + new ArrayList<>(a11yManager.getInstalledAccessibilityServiceList()); + final List preferenceList = getInstalledAccessibilityPreferences( + getPrefContext(), installedShortcutList, modifiableInstalledServiceList); + + if (Flags.checkPrebundledIsPreinstalled()) { + removeNonPreinstalledComponents(mPreBundledServiceComponentToCategoryMap, + installedShortcutList, modifiableInstalledServiceList); + } final PreferenceCategory downloadedServicesCategory = mCategoryToPrefCategoryMap.get(CATEGORY_DOWNLOADED_SERVICES); @@ -456,13 +468,21 @@ public class AccessibilitySettings extends DashboardFragment implements updatePreferenceCategoryVisibility(CATEGORY_KEYBOARD_OPTIONS); } - private List getInstalledAccessibilityList(Context context) { - final AccessibilityManager a11yManager = AccessibilityManager.getInstance(context); + /** + * Gets a list of {@link RestrictedPreference}s for the provided a11y shortcuts and services. + * + *

{@code modifiableInstalledServiceList} may be modified to remove any entries with + * matching package name and label as an entry in {@code installedShortcutList}. + * + * @param installedShortcutList A list of installed {@link AccessibilityShortcutInfo}s. + * @param modifiableInstalledServiceList A modifiable list of installed + * {@link AccessibilityServiceInfo}s. + */ + private List getInstalledAccessibilityPreferences(Context context, + List installedShortcutList, + List modifiableInstalledServiceList) { final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context); - final List installedShortcutList = - a11yManager.getInstalledAccessibilityShortcutListAsUser(context, - UserHandle.myUserId()); final List activityList = preferenceHelper.createAccessibilityActivityPreferenceList(installedShortcutList); final Set> packageLabelPairs = @@ -471,16 +491,14 @@ public class AccessibilitySettings extends DashboardFragment implements a11yActivityPref.getPackageName(), a11yActivityPref.getLabel()) ).collect(Collectors.toSet()); - // Remove duplicate item here, new a ArrayList to copy unmodifiable list result - // (getInstalledAccessibilityServiceList). - final List installedServiceList = new ArrayList<>( - a11yManager.getInstalledAccessibilityServiceList()); + // Remove duplicate A11yServices that are already shown as A11yActivities. if (!packageLabelPairs.isEmpty()) { - installedServiceList.removeIf( + modifiableInstalledServiceList.removeIf( target -> containsPackageAndLabelInList(packageLabelPairs, target)); } final List serviceList = - preferenceHelper.createAccessibilityServicePreferenceList(installedServiceList); + preferenceHelper.createAccessibilityServicePreferenceList( + modifiableInstalledServiceList); final List preferenceList = new ArrayList<>(); preferenceList.addAll(activityList); @@ -489,6 +507,22 @@ public class AccessibilitySettings extends DashboardFragment implements return preferenceList; } + private static void removeNonPreinstalledComponents( + Map componentToCategory, + List shortcutInfos, + List serviceInfos) { + for (AccessibilityShortcutInfo info : shortcutInfos) { + if (!info.getActivityInfo().applicationInfo.isSystemApp()) { + componentToCategory.remove(info.getComponentName()); + } + } + for (AccessibilityServiceInfo info : serviceInfos) { + if (!info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp()) { + componentToCategory.remove(info.getComponentName()); + } + } + } + private boolean containsPackageAndLabelInList( Set> packageLabelPairs, AccessibilityServiceInfo targetServiceInfo) { diff --git a/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java index 7dcd6612ac4..29971854e6b 100644 --- a/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java +++ b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java @@ -17,26 +17,50 @@ package com.android.settings.accessibility; import android.content.ContentResolver; import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; import android.provider.Settings; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.server.accessibility.Flags; +import com.android.settings.R; import com.android.settings.core.SliderPreferenceController; import com.android.settings.widget.SeekBarPreference; /** * The controller of the seekbar preference for the saturation level of color correction. */ -public class DaltonizerSaturationSeekbarPreferenceController extends SliderPreferenceController { +public class DaltonizerSaturationSeekbarPreferenceController + extends SliderPreferenceController + implements DefaultLifecycleObserver { private static final int DEFAULT_SATURATION_LEVEL = 7; private static final int SATURATION_MAX = 10; - private static final int SATURATION_MIN = 0; + private static final int SATURATION_MIN = 1; private int mSliderPosition; private final ContentResolver mContentResolver; + @Nullable + private SeekBarPreference mPreference; + + public final ContentObserver mContentObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + if (mPreference != null) { + updateState(mPreference); + } + } + }; + public DaltonizerSaturationSeekbarPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); @@ -49,10 +73,33 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe // TODO: Observer color correction on/off and enable/disable based on secure settings. } + @Override + public void onStart(@NonNull LifecycleOwner owner) { + if (!isAvailable()) return; + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER), + true, + mContentObserver + ); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED), + true, + mContentObserver + ); + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + if (!isAvailable()) return; + mContentResolver.unregisterContentObserver(mContentObserver); + mPreference = null; + } + @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); SeekBarPreference preference = screen.findPreference(getPreferenceKey()); + mPreference = preference; preference.setMax(getMax()); preference.setMin(getMin()); preference.setProgress(mSliderPosition); @@ -62,7 +109,7 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe @Override public int getAvailabilityStatus() { if (Flags.enableColorCorrectionSaturation()) { - return AVAILABLE; + return shouldSeekBarEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING; } return CONDITIONALLY_UNAVAILABLE; } @@ -85,6 +132,21 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe return true; } + @Override + public void updateState(Preference preference) { + if (preference == null) { + return; + } + + var shouldSeekbarEnabled = shouldSeekBarEnabled(); + // setSummary not working yet on SeekBarPreference. + String summary = shouldSeekbarEnabled + ? "" + : mContext.getString(R.string.daltonizer_saturation_unavailable_summary); + preference.setSummary(summary); + preference.setEnabled(shouldSeekbarEnabled); + } + @Override public int getMax() { return SATURATION_MAX; @@ -94,4 +156,16 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe public int getMin() { return SATURATION_MIN; } + + private boolean shouldSeekBarEnabled() { + int enabled = Settings.Secure.getInt( + mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0); + int mode = Settings.Secure.getInt( + mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, -1); + + // enabled == 0 is disabled and also default. + // mode == 0 is gray scale where saturation level isn't applicable. + // mode == -1 is disabled and also default. + return enabled != 0 && mode != -1 && mode != 0; + } } diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java index b9a0b939a6b..835f3a87d68 100644 --- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java +++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java @@ -144,9 +144,10 @@ public abstract class BiometricsSettingsBase extends DashboardFragment { launchChooseOrConfirmLock(); } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics( getActivity(), mBiometricsSuccessfullyAuthenticated, - mBiometricsAuthenticationRequested)) { + mBiometricsAuthenticationRequested, mUserId)) { mBiometricsAuthenticationRequested = true; - Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST); + Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST, + mUserId); } updateUnlockPhonePreferenceSummary(); @@ -161,10 +162,11 @@ public abstract class BiometricsSettingsBase extends DashboardFragment { public void onResume() { super.onResume(); if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(), - mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested) + mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested, mUserId) && mGkPwHandle != 0L) { mBiometricsAuthenticationRequested = true; - Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST); + Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST, + mUserId); } if (!mConfirmCredential) { mDoNotFinishActivity = false; diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java index 2a0dd83a491..305d6703cfe 100644 --- a/src/com/android/settings/biometrics/face/FaceSettings.java +++ b/src/com/android/settings/biometrics/face/FaceSettings.java @@ -289,9 +289,11 @@ public class FaceSettings extends DashboardFragment { finish(); } } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(), - mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested)) { + mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested, + mUserId)) { mBiometricsAuthenticationRequested = true; - Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST); + Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST, + mUserId); } else { mAttentionController.setToken(mToken); mEnrollController.setToken(mToken); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index c4bbcdeea07..815c08e0de1 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -485,9 +485,11 @@ public class FingerprintSettings extends SubSettings { mLaunchedConfirm = true; launchChooseOrConfirmLock(); } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(), - mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested)) { + mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested, + mUserId)) { mBiometricsAuthenticationRequested = true; - Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST); + Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST, + mUserId); } else if (!mHasFirstEnrolled) { mIsEnrolling = true; addFirstFingerprint(null); @@ -777,9 +779,11 @@ public class FingerprintSettings extends SubSettings { .getUdfpsEnrollCalibrator(getActivity().getApplicationContext(), null, null); if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(), - mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested)) { + mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested, + mUserId)) { mBiometricsAuthenticationRequested = true; - Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST); + Utils.launchBiometricPromptForMandatoryBiometrics(this, + BIOMETRIC_AUTH_REQUEST, mUserId); } } diff --git a/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt b/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt index 215692ab0e1..9bc920a96ba 100644 --- a/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt +++ b/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt @@ -16,7 +16,9 @@ package com.android.settings.biometrics.fingerprint2 +import android.content.pm.PackageManager import android.hardware.fingerprint.FingerprintManager +import android.os.ServiceManager.ServiceNotFoundException import android.view.MotionEvent import android.view.accessibility.AccessibilityManager import androidx.fragment.app.FragmentActivity @@ -74,8 +76,15 @@ class BiometricsEnvironment(context: SettingsApplication) : ViewModelStoreOwner private val backgroundDispatcher = executorService.asCoroutineDispatcher() private val applicationScope = MainScope() private val gateKeeperPasswordProvider = GatekeeperPasswordProvider(LockPatternUtils(context)) - private val fingerprintManager = - context.getSystemService(FragmentActivity.FINGERPRINT_SERVICE) as FingerprintManager? + private val fingerprintManager = try { + if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + context.getSystemService(FragmentActivity.FINGERPRINT_SERVICE) as FingerprintManager? + } else { + null + } + } catch (exception: ServiceNotFoundException){ + null + } private val fingerprintSensorRepository: FingerprintSensorRepository = FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, applicationScope) diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java index bfccdc4c672..0a90e7b3c4a 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java @@ -92,6 +92,8 @@ public class AudioSharingNamePreference extends ValidatedEditTextPreference { shareButton.setVisibility(View.VISIBLE); shareButton.setImageDrawable(getContext().getDrawable(R.drawable.ic_qrcode_24dp)); shareButton.setOnClickListener(unused -> launchAudioSharingQrCodeFragment()); + shareButton.setContentDescription( + getContext().getString(R.string.audio_sharing_qrcode_button_label)); } private void configureInvisibleStateForQrCodeIcon(ImageButton shareButton, View divider) { diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java index e4c07949b41..47f9c75514e 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java @@ -55,7 +55,7 @@ public class AudioStreamsQrCodeFragment extends InstrumentedFragment { @Override public final View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.xml.bluetooth_audio_streams_qr_code, container, false); + return inflater.inflate(R.layout.bluetooth_audio_streams_qr_code, container, false); } @Override diff --git a/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java b/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java index ff513c249f5..0d3d835dc5f 100644 --- a/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java +++ b/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceController.java @@ -69,7 +69,8 @@ public class DesktopModeSecondaryDisplayPreferenceController extends isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF); if (isEnabled && mFragment != null) { RebootConfirmationDialogFragment.show( - mFragment, R.string.reboot_dialog_force_desktop_mode, this); + mFragment, R.string.reboot_dialog_enable_desktop_mode_on_secondary_display, + this); } return true; } diff --git a/src/com/android/settings/development/FreeformWindowsPreferenceController.java b/src/com/android/settings/development/FreeformWindowsPreferenceController.java index 7cf7738f7b5..c02ffa7d00f 100644 --- a/src/com/android/settings/development/FreeformWindowsPreferenceController.java +++ b/src/com/android/settings/development/FreeformWindowsPreferenceController.java @@ -16,8 +16,9 @@ package com.android.settings.development; +import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; + import android.content.Context; -import android.os.Build; import android.provider.Settings; import androidx.annotation.Nullable; @@ -40,7 +41,8 @@ public class FreeformWindowsPreferenceController extends DeveloperOptionsPrefere @VisibleForTesting static final int SETTING_VALUE_ON = 1; - @Nullable private final DevelopmentSettingsDashboardFragment mFragment; + @Nullable + private final DevelopmentSettingsDashboardFragment mFragment; public FreeformWindowsPreferenceController( Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) { @@ -48,6 +50,13 @@ public class FreeformWindowsPreferenceController extends DeveloperOptionsPrefere mFragment = fragment; } + @Override + public boolean isAvailable() { + // When devices have the system feature FEATURE_FREEFORM_WINDOW_MANAGEMENT, freeform + // mode is enabled automatically, and this toggle is not needed. + return !mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT); + } + @Override public String getPreferenceKey() { return ENABLE_FREEFORM_SUPPORT_KEY; @@ -80,9 +89,4 @@ public class FreeformWindowsPreferenceController extends DeveloperOptionsPrefere Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, SETTING_VALUE_OFF); ((TwoStatePreference) mPreference).setChecked(false); } - - @VisibleForTesting - String getBuildType() { - return Build.TYPE; - } } diff --git a/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceController.java b/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceController.java index 23d4cc64efa..9f7512c45c9 100644 --- a/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceController.java +++ b/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceController.java @@ -30,85 +30,184 @@ import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.development.DeveloperOptionsPreferenceController; +/** + * This preference represents the default log level for the Bluetooth stack + * + * The default log level is captured and held in an Android Log Framework log tag, using "bluetooth" + * as the tag name. The Log framework does not provide methods to directly write a log tag value, + * but instead leverages special system properties to hold the value of a log tag. + * + * This preferences aims to keep the selection in sync with the currently set log tag value. It + * writes directly to the system properties that hold the level associated with the bluetooth log + * tag. It leverages the Log.isLoggable("bluetooth", level) function to discern the current value. + * The default level is INFO. + * + * This value is read once at start of the Bluetooth stack. To use a new value once setting it, be + * sure to turn Bluetooth off and back on again. + */ public class BluetoothStackLogPreferenceController extends DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin { + private static final String TAG = BluetoothStackLogPreferenceController.class.getSimpleName(); + + private static final String PREFERENCE_KEY = "bt_stack_log_level"; /* Ensure that the indexes match with bt_stack_log_values and bt_stack_log_entries ordering */ - private static final String PREFERENCE_KEY = "bt_stack_log_level"; - @VisibleForTesting static final int BTSTACK_LOG_MODE_VERBOSE_INDEX = 0; - @VisibleForTesting static final int BTSTACK_LOG_MODE_DEBUG_INDEX = 1; - @VisibleForTesting static final int BTSTACK_LOG_MODE_INFO_INDEX = 2; - @VisibleForTesting static final int BTSTACK_LOG_MODE_WARN_INDEX = 3; - @VisibleForTesting static final int BTSTACK_LOG_MODE_ERROR_INDEX = 4; + private static final int BT_LOG_LEVEL_VERBOSE_INDEX = 0; + private static final int BT_LOG_LEVEL_DEBUG_INDEX = 1; + private static final int BT_LOG_LEVEL_INFO_INDEX = 2; + private static final int BT_LOG_LEVEL_WARN_INDEX = 3; + private static final int BT_LOG_LEVEL_ERROR_INDEX = 4; + @VisibleForTesting static final int BT_LOG_LEVEL_DEFAULT_INDEX = BT_LOG_LEVEL_INFO_INDEX; - @VisibleForTesting - static final String BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST = "persist.log.tag.bluetooth"; - static final String BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY = "log.tag.bluetooth"; - static final String BLUETOOTH_STRING_NAME = "bluetooth"; - static final int DEFAULT_MODE = BTSTACK_LOG_MODE_INFO_INDEX; - - private final String[] mListValues; - private final String[] mListEntries; + private static final String BT_LOG_TAG = "bluetooth"; + @VisibleForTesting static final String BT_LOG_LEVEL_PROP_PERSIST = "persist.log.tag.bluetooth"; + @VisibleForTesting static final String BT_LOG_LEVEL_PROP = "log.tag.bluetooth"; + // Values represents the untranslatable log level strings that should be used for writing to + // system properties. Entries represents the translatable log level strings that should be used + // in the UI to communicate to the user their options for this preference. + private String[] mListValues; + private String[] mListEntries; + /** + * Create a BluetoothStackLogPreferenceController instance + */ public BluetoothStackLogPreferenceController(@NonNull Context context) { super(context); mListValues = context.getResources().getStringArray(R.array.bt_stack_log_level_values); mListEntries = context.getResources().getStringArray(R.array.bt_stack_log_level_entries); } - /** returns default log level index of INFO */ - public int getDefaultModeIndex() { - return DEFAULT_MODE; - } - + /** + * Returns the preference key associated with this preference + * + * Note that this key is _usually_ a system property in and of itself, which is expected to hold + * the value of the preference. In this case though, this key *does not* hold the preference. It + * is only really used to tie this controller to the list preference defined in the XML file. + * + * @return the preference key associated with this preference + */ @Override @Nullable public String getPreferenceKey() { return PREFERENCE_KEY; } + /** + * Update the state of the preference based on what the user has selected + * + * This function is invoked when the user has selected a new value for this preference. The new + * value is the entry value at the index of the list the user has selected. This value will be + * one of the values from the array returned in getEntryValues(). Specifically, this array is + * set using R.array.bt_stack_log_level_values + * + * @param preference - the preference object to set the value of + * @param newValue - the value the user has selected, as an Object + * @return True when updated successfully + */ @Override public boolean onPreferenceChange(@NonNull Preference preference, @NonNull Object newValue) { - SystemProperties.set(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST, newValue.toString()); - SystemProperties.set(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY, newValue.toString()); - updateState(mPreference); + Log.v(TAG, "onPreferenceChange(pref=" + preference + "value=" + newValue.toString() + ")"); + setBluetoothLogTag(newValue.toString()); + setBluetoothLogLevelIndex(getBluetoothLogLevelIndex()); return true; } + /** + * Refresh the state of this preference based on the state stored on the system + * + * Read the Bluetooth stack log level from the underlying system property/log tag, and map that + * level to the proper index in the values and entries array. Use those strings to set the value + * and summary of the preference. + * + * @param preference - the preference object to refresh the state of + */ @Override public void updateState(@NonNull Preference preference) { - final ListPreference listPreference = (ListPreference) preference; - int index = getBluetoothLogLevelIndex(); - listPreference.setValue(mListValues[index]); - listPreference.setSummary(mListEntries[index]); + Log.v(TAG, "updateState(pref=" + preference + "): refresh preference state"); + setBluetoothLogLevelIndex(getBluetoothLogLevelIndex()); } /** - * Returns the current log level from Log.isLoggable(). + * Notify this developer options preference of a change to developer options visibility + * + * We developer options are closed, we should clear out the value of this developer option + * preference and revert it back to the default state of INFO. */ - @VisibleForTesting - public int getBluetoothLogLevelIndex() { - if (Log.isLoggable(BLUETOOTH_STRING_NAME, Log.VERBOSE)) { - return BTSTACK_LOG_MODE_VERBOSE_INDEX; - } else if (Log.isLoggable(BLUETOOTH_STRING_NAME, Log.DEBUG)) { - return BTSTACK_LOG_MODE_DEBUG_INDEX; - } else if (Log.isLoggable(BLUETOOTH_STRING_NAME, Log.INFO)) { - return BTSTACK_LOG_MODE_INFO_INDEX; - } else if (Log.isLoggable(BLUETOOTH_STRING_NAME, Log.WARN)) { - return BTSTACK_LOG_MODE_WARN_INDEX; - } else if (Log.isLoggable(BLUETOOTH_STRING_NAME, Log.ERROR)) { - return BTSTACK_LOG_MODE_ERROR_INDEX; - } - return BTSTACK_LOG_MODE_INFO_INDEX; - } - @Override protected void onDeveloperOptionsSwitchDisabled() { super.onDeveloperOptionsSwitchDisabled(); - SystemProperties.set(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST, null); - SystemProperties.set(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY, null); - ((ListPreference) mPreference).setValue(mListValues[getDefaultModeIndex()]); - ((ListPreference) mPreference).setSummary(mListEntries[getDefaultModeIndex()]); + Log.v(TAG, "onDeveloperOptionsSwitchDisabled(): Revert stack log to default"); + setBluetoothLogTag(null); + setBluetoothLogLevelIndex(BT_LOG_LEVEL_DEFAULT_INDEX); + } + + /** + * Set the system property values used by the Log framework to read the "bluetooth" log tag + * + * @param logLevel - the log level to set the Bluetooth stack minimum log level to + */ + private void setBluetoothLogTag(@Nullable String logLevel) { + Log.i(TAG, "setBluetoothLogTag(logLevel=" + logLevel + "): Set properties for log tag"); + SystemProperties.set(BT_LOG_LEVEL_PROP_PERSIST, logLevel); + SystemProperties.set(BT_LOG_LEVEL_PROP, logLevel); + } + + /** + * Get the entry and value index corresponding to the current Bluetooth stack log level + * + * Since this preference uses an actual log tag and not a specific/private system property, we + * can read the value using the Log.isLoggable() function with our "bluetooth" log tag that + * represents the log level of the Bluetooth stack. This is safer than trying to replacate the + * logic used in the Log framework around the various persist, ro, and blank variants of the tag + * + * If no value is present, INFO is used. + * + * @return the entry/value index corresponding to the current log level of the tag "bluetooth" + */ + @VisibleForTesting + public int getBluetoothLogLevelIndex() { + int level = BT_LOG_LEVEL_DEFAULT_INDEX; + if (Log.isLoggable(BT_LOG_TAG, Log.VERBOSE)) { + level = BT_LOG_LEVEL_VERBOSE_INDEX; + } else if (Log.isLoggable(BT_LOG_TAG, Log.DEBUG)) { + level = BT_LOG_LEVEL_DEBUG_INDEX; + } else if (Log.isLoggable(BT_LOG_TAG, Log.INFO)) { + level = BT_LOG_LEVEL_INFO_INDEX; + } else if (Log.isLoggable(BT_LOG_TAG, Log.WARN)) { + level = BT_LOG_LEVEL_WARN_INDEX; + } else if (Log.isLoggable(BT_LOG_TAG, Log.ERROR)) { + level = BT_LOG_LEVEL_ERROR_INDEX; + } + Log.v(TAG, "getBluetoothLogLevelIndex() -> " + level); + return level; + } + + /** + * Set the current Bluetooth stack log level displayed in the list for this preference + * + * @param index - the index representing the log level choice of this preference + */ + private void setBluetoothLogLevelIndex(int index) { + if (index < BT_LOG_LEVEL_VERBOSE_INDEX || index > BT_LOG_LEVEL_ERROR_INDEX) { + Log.e(TAG, "setBluetoothLogLevelIndex(index=" + index + "): Log level invalid"); + return; + } + + String value = mListValues[index]; + String entryValue = mListEntries[index]; + + ListPreference preference = ((ListPreference) mPreference); + if (preference == null) { + Log.e(TAG, "setBluetoothLogLevelIndex(index=" + index + "): mPreference is null"); + return; + } + + preference.setValue(value); + preference.setSummary(entryValue); + + Log.i(TAG, "setBluetoothLogLevelIndex(index=" + index + + "): Updated Bluetooth stack log level to value='" + value + "', entryValue='" + + entryValue + "'"); } } diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java index 7d15858f8b5..f121d0c2e5b 100644 --- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java @@ -81,7 +81,10 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController return mContext.getString( com.android.settingslib.R.string.battery_info_status_not_charging); } - if (BatteryUtils.isBatteryDefenderOn(info)) { + if (BatteryUtils.isBatteryDefenderOn(info) + || FeatureFactory.getFeatureFactory() + .getPowerUsageFeatureProvider() + .isExtraDefend()) { return mContext.getString( com.android.settingslib.R.string.battery_info_status_charging_on_hold); } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java index bfa501c16aa..f710c71c969 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java @@ -63,8 +63,8 @@ public final class BatteryUsageBroadcastReceiver extends BroadcastReceiver { } final String action = intent.getAction(); Log.d(TAG, "onReceive:" + action); - if (com.android.settingslib.fuelgauge.BatteryUtils.isWorkProfile(context)) { - Log.w(TAG, "do nothing for work profile action=" + action); + if (com.android.settingslib.fuelgauge.BatteryUtils.isAdditionalProfile(context)) { + Log.w(TAG, "do nothing for an additional profile action=" + action); return; } DatabaseUtils.recordDateTime(context, action); diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java index 095a65a8e10..52010afc77b 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java @@ -110,8 +110,8 @@ public class BatteryUsageContentProvider extends ContentProvider { @Override public boolean onCreate() { - if (BatteryUtils.isWorkProfile(getContext())) { - Log.w(TAG, "do not create provider for work profile"); + if (BatteryUtils.isAdditionalProfile(getContext())) { + Log.w(TAG, "do not create provider for an additional profile"); return false; } mClock = Clock.systemUTC(); diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java index b758df4bd6b..45d724fb208 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java @@ -54,8 +54,8 @@ public final class BootBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { final String action = intent == null ? "" : intent.getAction(); - if (BatteryUtils.isWorkProfile(context)) { - Log.w(TAG, "do not start job for work profile action=" + action); + if (BatteryUtils.isAdditionalProfile(context)) { + Log.w(TAG, "do not start job for an additional profile action=" + action); return; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java index 76203232329..6feb815dc13 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java @@ -70,6 +70,7 @@ public final class DatabaseUtils { /** Clear memory threshold for device booting phase. */ private static final long CLEAR_MEMORY_THRESHOLD_MS = Duration.ofMinutes(5).toMillis(); + private static final long CLEAR_MEMORY_DELAYED_MS = Duration.ofSeconds(2).toMillis(); private static final long INVALID_TIMESTAMP = 0L; @@ -527,9 +528,11 @@ public final class DatabaseUtils { return startCalendar.getTimeInMillis(); } - /** Returns the context with profile parent identity when current user is work profile. */ + /** + * Returns the context with profile parent identity when current user is an additional profile. + */ public static Context getParentContext(Context context) { - if (com.android.settingslib.fuelgauge.BatteryUtils.isWorkProfile(context)) { + if (com.android.settingslib.fuelgauge.BatteryUtils.isAdditionalProfile(context)) { try { return context.createPackageContextAsUser( /* packageName= */ context.getPackageName(), diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java index 5c73adb69f9..982cf406101 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java @@ -50,10 +50,10 @@ public final class PeriodicJobReceiver extends BroadcastReceiver { Log.w(TAG, "receive unexpected action=" + action); return; } - if (BatteryUtils.isWorkProfile(context)) { + if (BatteryUtils.isAdditionalProfile(context)) { BatteryUsageLogUtils.writeLog( - context, Action.SCHEDULE_JOB, "do not refresh job for work profile"); - Log.w(TAG, "do not refresh job for work profile action=" + action); + context, Action.SCHEDULE_JOB, "do not refresh job for an additional profile"); + Log.w(TAG, "do not refresh job for an additional profile action=" + action); return; } BatteryUsageLogUtils.writeLog(context, Action.EXECUTE_JOB, ""); diff --git a/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java index 7e759ee3f26..e829a3cd98c 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java +++ b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java @@ -49,8 +49,8 @@ public final class BugReportContentProvider extends ContentProvider { Log.w(TAG, "failed to dump BatteryUsage state: null application context"); return; } - if (BatteryUtils.isWorkProfile(context)) { - Log.w(TAG, "ignore battery usage states dump in the work profile"); + if (BatteryUtils.isAdditionalProfile(context)) { + Log.w(TAG, "ignore battery usage states dump in the additional profile"); return; } writer.println("dump BatteryUsage and AppUsage states:"); diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java index 86135a96190..1f979022670 100644 --- a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java @@ -16,14 +16,26 @@ package com.android.settings.notification.modes; +import static android.app.AutomaticZenRule.TYPE_BEDTIME; +import static android.app.AutomaticZenRule.TYPE_DRIVING; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; +import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId; +import static com.google.common.base.Preconditions.checkNotNull; + +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.service.notification.SystemZenRules; +import android.service.notification.ZenModeConfig; import android.util.Log; +import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; @@ -35,6 +47,8 @@ import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenModesBackend; +import com.google.common.base.Strings; + /** * Preference controller for the link to an individual mode's configuration page. */ @@ -42,26 +56,29 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc private static final String TAG = "ZenModeSetTriggerLink"; @VisibleForTesting - protected static final String AUTOMATIC_TRIGGER_PREF_KEY = "zen_automatic_trigger_settings"; + static final String AUTOMATIC_TRIGGER_KEY = "zen_automatic_trigger_settings"; + static final String ADD_TRIGGER_KEY = "zen_add_automatic_trigger"; + private final DashboardFragment mFragment; + private final PackageManager mPackageManager; private final ConfigurationActivityHelper mConfigurationActivityHelper; private final ZenServiceListing mServiceListing; - private final DashboardFragment mFragment; ZenModeSetTriggerLinkPreferenceController(Context context, String key, DashboardFragment fragment, ZenModesBackend backend) { - this(context, key, fragment, backend, + this(context, key, fragment, backend, context.getPackageManager(), new ConfigurationActivityHelper(context.getPackageManager()), new ZenServiceListing(context)); } @VisibleForTesting ZenModeSetTriggerLinkPreferenceController(Context context, String key, - DashboardFragment fragment, ZenModesBackend backend, + DashboardFragment fragment, ZenModesBackend backend, PackageManager packageManager, ConfigurationActivityHelper configurationActivityHelper, ZenServiceListing serviceListing) { super(context, key, backend); mFragment = fragment; + mPackageManager = packageManager; mConfigurationActivityHelper = configurationActivityHelper; mServiceListing = serviceListing; } @@ -83,64 +100,137 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc // This controller is expected to govern a preference category so that it controls the // availability of the entire preference category if the mode doesn't have a way to // automatically trigger (such as manual DND). - PrimarySwitchPreference switchPref = ((PreferenceCategory) preference).findPreference( - AUTOMATIC_TRIGGER_PREF_KEY); - if (switchPref == null) { + if (zenMode.isManualDnd()) { return; } - switchPref.setChecked(zenMode.getRule().isEnabled()); - switchPref.setOnPreferenceChangeListener(mSwitchChangeListener); - switchPref.setSummary(zenMode.getRule().getTriggerDescription()); - switchPref.setIcon(null); - switchPref.setOnPreferenceClickListener(null); - switchPref.setIntent(null); + PrimarySwitchPreference triggerPref = checkNotNull( + ((PreferenceCategory) preference).findPreference(AUTOMATIC_TRIGGER_KEY)); + Preference addTriggerPref = checkNotNull( + ((PreferenceCategory) preference).findPreference(ADD_TRIGGER_KEY)); - if (zenMode.isSystemOwned()) { - if (zenMode.getType() == TYPE_SCHEDULE_TIME) { - switchPref.setTitle(R.string.zen_mode_set_schedule_link); - // TODO: b/332937635 - set correct metrics category - switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext, - ZenModeSetScheduleFragment.class, zenMode.getId(), 0).toIntent()); - } else if (zenMode.getType() == TYPE_SCHEDULE_CALENDAR) { - switchPref.setTitle(R.string.zen_mode_set_calendar_link); - switchPref.setIcon(null); - // TODO: b/332937635 - set correct metrics category - switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext, - ZenModeSetCalendarFragment.class, zenMode.getId(), 0).toIntent()); - } else { - switchPref.setTitle(R.string.zen_mode_select_schedule); - switchPref.setIcon(R.drawable.ic_add_24dp); - switchPref.setSummary(""); - // TODO: b/342156843 - Hide the switch (needs support in SettingsLib). - switchPref.setOnPreferenceClickListener(clickedPreference -> { - ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener); - return true; - }); - } + boolean isAddTrigger = zenMode.isSystemOwned() && zenMode.getType() != TYPE_SCHEDULE_TIME + && zenMode.getType() != TYPE_SCHEDULE_CALENDAR; + + if (isAddTrigger) { + triggerPref.setVisible(false); + addTriggerPref.setVisible(true); + addTriggerPref.setOnPreferenceClickListener(unused -> { + ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener); + return true; + }); } else { - Intent intent = mConfigurationActivityHelper.getConfigurationActivityIntentForMode( - zenMode, mServiceListing::findService); - if (intent != null) { - preference.setVisible(true); - switchPref.setTitle(R.string.zen_mode_configuration_link_title); - switchPref.setSummary(zenMode.getRule().getTriggerDescription()); - switchPref.setIntent(intent); + addTriggerPref.setVisible(false); + triggerPref.setVisible(true); + triggerPref.setChecked(zenMode.getRule().isEnabled()); + triggerPref.setOnPreferenceChangeListener(mSwitchChangeListener); + + if (zenMode.isSystemOwned()) { + setUpForSystemOwnedTrigger(triggerPref, zenMode); } else { - Log.i(TAG, "No intent found for " + zenMode.getRule().getName()); - preference.setVisible(false); + setUpForAppTrigger(triggerPref, zenMode); } } } + private void setUpForSystemOwnedTrigger(Preference preference, ZenMode mode) { + if (mode.getType() == TYPE_SCHEDULE_TIME) { + // TODO: b/332937635 - set correct metrics category + preference.setIntent(ZenSubSettingLauncher.forModeFragment(mContext, + ZenModeSetScheduleFragment.class, mode.getId(), 0).toIntent()); + + // [Clock Icon] 9:00 - 17:00 / Sun-Mon + preference.setIcon(com.android.internal.R.drawable.ic_zen_mode_type_schedule_time); + ZenModeConfig.ScheduleInfo schedule = + tryParseScheduleConditionId(mode.getRule().getConditionId()); + if (schedule != null) { + preference.setTitle(SystemZenRules.getTimeSummary(mContext, schedule)); + preference.setSummary(SystemZenRules.getShortDaysSummary(mContext, schedule)); + } else { + // Fallback, but shouldn't happen. + Log.wtf(TAG, "SCHEDULE_TIME mode without schedule: " + mode); + preference.setTitle(R.string.zen_mode_set_schedule_link); + preference.setSummary(null); + } + } else if (mode.getType() == TYPE_SCHEDULE_CALENDAR) { + // TODO: b/332937635 - set correct metrics category + preference.setIntent(ZenSubSettingLauncher.forModeFragment(mContext, + ZenModeSetCalendarFragment.class, mode.getId(), 0).toIntent()); + + // [Event Icon] Calendar Events / + preference.setIcon( + com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar); + preference.setTitle(R.string.zen_mode_trigger_title_schedule_calendar); + preference.setSummary(mode.getTriggerDescription()); + } else { + Log.wtf(TAG, "Unexpected type for system-owned mode: " + mode); + } + } + + @SuppressLint("SwitchIntDef") + private void setUpForAppTrigger(Preference preference, ZenMode mode) { + // App-owned mode may have triggerDescription, configurationActivity, or both/neither. + Intent configurationIntent = + mConfigurationActivityHelper.getConfigurationActivityIntentForMode( + mode, mServiceListing::findService); + + @StringRes int title = switch (mode.getType()) { + case TYPE_BEDTIME -> R.string.zen_mode_trigger_title_bedtime; + case TYPE_DRIVING -> R.string.zen_mode_trigger_title_driving; + default -> R.string.zen_mode_trigger_title_generic; + }; + + String summary; + if (!Strings.isNullOrEmpty(mode.getTriggerDescription())) { + summary = mode.getTriggerDescription(); + } else if (!Strings.isNullOrEmpty(mode.getRule().getPackageName())) { + String appName = null; + try { + ApplicationInfo appInfo = mPackageManager.getApplicationInfo( + mode.getRule().getPackageName(), 0); + appName = appInfo.loadLabel(mPackageManager).toString(); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Couldn't resolve owner for mode: " + mode); + } + + if (appName != null) { + summary = mContext.getString( + configurationIntent != null + ? R.string.zen_mode_trigger_summary_settings_in_app + : R.string.zen_mode_trigger_summary_managed_by_app, + appName); + } else { + summary = null; + } + } else { + Log.e(TAG, "Mode without package! " + mode); + summary = null; + } + + @DrawableRes int icon; + if (mode.getType() == TYPE_BEDTIME) { + icon = com.android.internal.R.drawable.ic_zen_mode_type_schedule_time; // Clock + } else if (mode.getType() == TYPE_DRIVING) { + icon = com.android.internal.R.drawable.ic_zen_mode_type_driving; // Car + } else { + icon = configurationIntent != null ? R.drawable.ic_zen_mode_trigger_with_activity + : R.drawable.ic_zen_mode_trigger_without_activity; + } + + preference.setTitle(title); + preference.setSummary(summary); + preference.setIcon(icon); + preference.setIntent(configurationIntent); + } + @VisibleForTesting final ZenModeScheduleChooserDialog.OnScheduleOptionListener mOnScheduleOptionListener = conditionId -> saveMode(mode -> { mode.setCustomModeConditionId(mContext, conditionId); return mode; + // TODO: b/342156843 - Maybe jump to the corresponding schedule editing screen? }); - @VisibleForTesting - protected Preference.OnPreferenceChangeListener mSwitchChangeListener = (p, newValue) -> { + private final Preference.OnPreferenceChangeListener mSwitchChangeListener = (p, newValue) -> { final boolean newEnabled = (Boolean) newValue; return saveMode((zenMode) -> { if (newEnabled != zenMode.getRule().isEnabled()) { @@ -148,6 +238,5 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc } return zenMode; }); - // TODO: b/342156843 - Do we want to jump to the corresponding schedule editing screen? }; } diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index b4f13e8d77b..d5d079ecf6d 100644 --- a/src/com/android/settings/password/ChooseLockGeneric.java +++ b/src/com/android/settings/password/ChooseLockGeneric.java @@ -492,9 +492,10 @@ public class ChooseLockGeneric extends SettingsActivity { : null; updatePreferencesOrFinish(false /* isRecreatingActivity */); if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getContext(), - mBiometricsAuthSuccessful, mWaitingForConfirmation)) { + mBiometricsAuthSuccessful, mWaitingForConfirmation, mUserId)) { mWaitingForConfirmation = true; - Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST); + Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST, + mUserId); } } else if (requestCode == BIOMETRIC_AUTH_REQUEST) { if (resultCode == Activity.RESULT_OK) { diff --git a/tests/robotests/src/com/android/settings/MainClearTest.java b/tests/robotests/src/com/android/settings/MainClearTest.java index 187fce113f9..26a430b161c 100644 --- a/tests/robotests/src/com/android/settings/MainClearTest.java +++ b/tests/robotests/src/com/android/settings/MainClearTest.java @@ -140,8 +140,8 @@ public class MainClearTest { when(mScrollView.getChildCount()).thenReturn(1); doReturn(mMockActivity).when(mMainClear).getActivity(); when(mMockActivity.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager); - when(mBiometricManager.canAuthenticate( - BiometricManager.Authenticators.MANDATORY_BIOMETRICS)) + when(mBiometricManager.canAuthenticate(anyInt(), + eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS))) .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE); } @@ -370,8 +370,8 @@ public class MainClearTest { when(mContext.getResources()).thenReturn(mResources); when(mMockActivity.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager); when(mResources.getString(anyInt())).thenReturn(TEST_ACCOUNT_NAME); - when(mBiometricManager.canAuthenticate( - BiometricManager.Authenticators.MANDATORY_BIOMETRICS)) + when(mBiometricManager.canAuthenticate(anyInt(), + eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS))) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); doReturn(true).when(mMainClear).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST)); doNothing().when(mMainClear).startActivityForResult(any(), anyInt()); diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java index fd97b78d2ec..b36e9d63e32 100644 --- a/tests/robotests/src/com/android/settings/UtilsTest.java +++ b/tests/robotests/src/com/android/settings/UtilsTest.java @@ -81,6 +81,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ConfirmDeviceCredentialActivity; import com.android.settings.testutils.shadow.ShadowLockPatternUtils; @@ -532,31 +533,45 @@ public class UtilsTest { when(mContext.getSystemService(BiometricManager.class)).thenReturn(null); assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext, false /* biometricsSuccessfullyAuthenticated */, - false /* biometricsAuthenticationRequested */)).isFalse(); + false /* biometricsAuthenticationRequested */, USER_ID)).isFalse(); } @Test @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS) public void testRequestBiometricAuthentication_biometricManagerReturnsSuccess_shouldReturnTrue() { - when(mBiometricManager.canAuthenticate( + when(mBiometricManager.canAuthenticate(USER_ID, BiometricManager.Authenticators.MANDATORY_BIOMETRICS)) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); - boolean requestBiometricAuthenticationForMandatoryBiometrics = + final boolean requestBiometricAuthenticationForMandatoryBiometrics = Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext, - true /* biometricsSuccessfullyAuthenticated */, - false /* biometricsAuthenticationRequested */); - assertThat(requestBiometricAuthenticationForMandatoryBiometrics).isFalse(); + false /* biometricsSuccessfullyAuthenticated */, + false /* biometricsAuthenticationRequested */, USER_ID); + assertThat(requestBiometricAuthenticationForMandatoryBiometrics).isTrue(); } @Test @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS) public void testRequestBiometricAuthentication_biometricManagerReturnsError_shouldReturnFalse() { - when(mBiometricManager.canAuthenticate( - BiometricManager.Authenticators.MANDATORY_BIOMETRICS)) + when(mBiometricManager.canAuthenticate(anyInt(), + eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS))) .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE); assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext, false /* biometricsSuccessfullyAuthenticated */, - false /* biometricsAuthenticationRequested */)).isFalse(); + false /* biometricsAuthenticationRequested */, USER_ID)).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS) + public void testRequestBiometricAuthentication_biometricManagerReturnsSuccessForDifferentUser_shouldReturnFalse() { + when(mBiometricManager.canAuthenticate(anyInt(), + eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS))) + .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE); + when(mBiometricManager.canAuthenticate(0 /* userId */, + BiometricManager.Authenticators.MANDATORY_BIOMETRICS)) + .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); + assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext, + false /* biometricsSuccessfullyAuthenticated */, + false /* biometricsAuthenticationRequested */, USER_ID)).isFalse(); } @Test @@ -566,7 +581,7 @@ public class UtilsTest { final int requestCode = 1; final ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); - Utils.launchBiometricPromptForMandatoryBiometrics(mFragment, requestCode); + Utils.launchBiometricPromptForMandatoryBiometrics(mFragment, requestCode, USER_ID); verify(mFragment).startActivityForResult(intentArgumentCaptor.capture(), eq(requestCode)); @@ -576,9 +591,12 @@ public class UtilsTest { BiometricManager.Authenticators.MANDATORY_BIOMETRICS); assertThat(intent.getExtra(BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT)).isNotNull(); assertThat(intent.getExtra(KeyguardManager.EXTRA_DESCRIPTION)).isNotNull(); + assertThat(intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, false)) + .isTrue(); + assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, 0)).isEqualTo(USER_ID); assertThat(intent.getComponent().getPackageName()).isEqualTo(SETTINGS_PACKAGE_NAME); assertThat(intent.getComponent().getClassName()).isEqualTo( - ConfirmDeviceCredentialActivity.class.getName()); + ConfirmDeviceCredentialActivity.InternalActivity.class.getName()); } private void setUpForConfirmCredentialString(boolean isEffectiveUserManagedProfile) { diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index 1463cd0b7f9..cb2429c558f 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -26,11 +26,9 @@ import static org.robolectric.Shadows.shadowOf; import static java.util.Collections.singletonList; import android.accessibilityservice.AccessibilityServiceInfo; -import android.accessibilityservice.AccessibilityShortcutInfo; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -110,9 +108,7 @@ public class AccessibilitySettingsTest { private final Context mContext = ApplicationProvider.getApplicationContext(); @Spy private final AccessibilityServiceInfo mServiceInfo = getMockAccessibilityServiceInfo( - PACKAGE_NAME, CLASS_NAME); - @Mock - private AccessibilityShortcutInfo mShortcutInfo; + new ComponentName(PACKAGE_NAME, CLASS_NAME)); private ShadowAccessibilityManager mShadowAccessibilityManager; @Mock private LocalBluetoothManager mLocalBluetoothManager; @@ -125,7 +121,6 @@ public class AccessibilitySettingsTest { mShadowAccessibilityManager.setInstalledAccessibilityServiceList(new ArrayList<>()); mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat); ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; - setMockAccessibilityShortcutInfo(mShortcutInfo); Intent intent = new Intent(); intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, @@ -398,14 +393,25 @@ public class AccessibilitySettingsTest { public void testAccessibilityMenuInSystem_IncludedInInteractionControl() { mShadowAccessibilityManager.setInstalledAccessibilityServiceList( List.of(getMockAccessibilityServiceInfo( - AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM))); + AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM, + /*isSystemApp=*/true))); setupFragment(); - final RestrictedPreference pref = mFragment.getPreferenceScreen().findPreference( - AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM.flattenToString()); - final String prefCategory = mFragment.mServicePreferenceToPreferenceCategoryMap.get( - pref).getKey(); - assertThat(prefCategory).isEqualTo(AccessibilitySettings.CATEGORY_INTERACTION_CONTROL); + assertThat(getPreferenceCategory(AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM)) + .isEqualTo(AccessibilitySettings.CATEGORY_INTERACTION_CONTROL); + } + + @Test + @EnableFlags(com.android.settings.accessibility.Flags.FLAG_CHECK_PREBUNDLED_IS_PREINSTALLED) + public void testNonPreinstalledApp_IncludedInDownloadedCategory() { + mShadowAccessibilityManager.setInstalledAccessibilityServiceList( + List.of(getMockAccessibilityServiceInfo( + AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM, + /*isSystemApp=*/false))); + setupFragment(); + + assertThat(getPreferenceCategory(AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM)) + .isEqualTo(AccessibilitySettings.CATEGORY_DOWNLOADED_SERVICES); } @Test @@ -418,13 +424,20 @@ public class AccessibilitySettingsTest { assertThat(pref).isNull(); } - private AccessibilityServiceInfo getMockAccessibilityServiceInfo(String packageName, - String className) { - return getMockAccessibilityServiceInfo(new ComponentName(packageName, className)); + private String getPreferenceCategory(ComponentName componentName) { + return mFragment.mServicePreferenceToPreferenceCategoryMap.get( + mFragment.getPreferenceScreen().findPreference( + componentName.flattenToString())).getKey(); } private AccessibilityServiceInfo getMockAccessibilityServiceInfo(ComponentName componentName) { - final ApplicationInfo applicationInfo = new ApplicationInfo(); + return getMockAccessibilityServiceInfo(componentName, true); + } + + private AccessibilityServiceInfo getMockAccessibilityServiceInfo(ComponentName componentName, + boolean isSystemApp) { + final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); + when(applicationInfo.isSystemApp()).thenReturn(isSystemApp); final ServiceInfo serviceInfo = new ServiceInfo(); applicationInfo.packageName = componentName.getPackageName(); serviceInfo.packageName = componentName.getPackageName(); @@ -445,16 +458,6 @@ public class AccessibilitySettingsTest { return null; } - private void setMockAccessibilityShortcutInfo(AccessibilityShortcutInfo mockInfo) { - final ActivityInfo activityInfo = Mockito.mock(ActivityInfo.class); - activityInfo.applicationInfo = new ApplicationInfo(); - when(mockInfo.getActivityInfo()).thenReturn(activityInfo); - when(activityInfo.loadLabel(any())).thenReturn(DEFAULT_LABEL); - when(mockInfo.loadSummary(any())).thenReturn(DEFAULT_SUMMARY); - when(mockInfo.loadDescription(any())).thenReturn(DEFAULT_DESCRIPTION); - when(mockInfo.getComponentName()).thenReturn(COMPONENT_NAME); - } - private void setInvisibleToggleFragmentType(AccessibilityServiceInfo info) { info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON; diff --git a/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java index 98ed4422dc8..5fd11f910fa 100644 --- a/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java @@ -16,38 +16,39 @@ package com.android.settings.accessibility; +import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; +import static androidx.lifecycle.Lifecycle.Event.ON_STOP; + import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -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 static org.robolectric.Shadows.shadowOf; import android.content.ContentResolver; import android.content.Context; +import android.os.Looper; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; +import androidx.lifecycle.LifecycleOwner; +import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; import com.android.server.accessibility.Flags; import com.android.settings.widget.SeekBarPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; /** Tests for {@link DaltonizerSaturationSeekbarPreferenceController}. */ @@ -60,8 +61,9 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { private int mOriginalSaturationLevel = -1; private PreferenceScreen mScreen; + private LifecycleOwner mLifecycleOwner; + private Lifecycle mLifecycle; - @Mock private SeekBarPreference mPreference; @Rule @@ -69,7 +71,6 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { @Before public void setup() { - MockitoAnnotations.initMocks(this); Context context = ApplicationProvider.getApplicationContext(); mContentResolver = context.getContentResolver(); mOriginalSaturationLevel = Settings.Secure.getInt( @@ -77,10 +78,13 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, 7); - mScreen = spy(new PreferenceScreen(context, /* attrs= */ null)); - when(mScreen.findPreference(ToggleDaltonizerPreferenceFragment.KEY_SATURATION)) - .thenReturn(mPreference); + mPreference = new SeekBarPreference(context); + mPreference.setKey(ToggleDaltonizerPreferenceFragment.KEY_SATURATION); + mScreen = new PreferenceManager(context).createPreferenceScreen(context); + mScreen.addPreference(mPreference); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); mController = new DaltonizerSaturationSeekbarPreferenceController( context, ToggleDaltonizerPreferenceFragment.KEY_SATURATION); @@ -94,6 +98,12 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { mOriginalSaturationLevel); } + @Test + public void constructor_defaultValuesMatch() { + assertThat(mController.getSliderPosition()).isEqualTo(7); + assertThat(mController.getMax()).isEqualTo(10); + assertThat(mController.getMin()).isEqualTo(1); + } @Test @DisableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) @@ -103,28 +113,72 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { @Test @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) - public void getAvailabilityStatus_flagEnabled_available() { + public void getAvailabilityStatus_flagEnabledProtanEnabled_available() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11); assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); } @Test - public void constructor_defaultValuesMatch() { - assertThat(mController.getSliderPosition()).isEqualTo(7); - assertThat(mController.getMax()).isEqualTo(10); - assertThat(mController.getMin()).isEqualTo(0); + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_flagEnabledDeutranEnabled_available() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 12); + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); } @Test @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) - public void displayPreference_enabled_visible() { + public void getAvailabilityStatus_flagEnabledTritanEnabled_available() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 13); + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_flagEnabledGrayScale_disabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 0); + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_flagEnabledColorCorrectionDisabled_disabled() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11); + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_flagEnabledColorCorrectionDisabledGrayScale_disabled() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 0); + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void displayPreference_flagEnabledColorCorrectionEnabled_enabledWithDefaultValues() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11); mController.displayPreference(mScreen); - verify(mPreference).setMax(eq(10)); - verify(mPreference).setMin(eq(0)); - verify(mPreference).setProgress(eq(7)); - verify(mPreference).setContinuousUpdates(eq(true)); - verify(mPreference).setOnPreferenceChangeListener(eq(mController)); - verify(mPreference).setVisible(eq(true)); + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.getMax()).isEqualTo(10); + assertThat(mPreference.getMin()).isEqualTo(1); + assertThat(mPreference.getProgress()).isEqualTo(7); + assertThat(mPreference.isVisible()).isTrue(); + assertThat(mPreference.getOnPreferenceChangeListener()).isEqualTo(mController); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void displayPreference_flagEnabledColorCorrectionDisabled_disabledWithDefaultValues() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11); + mController.displayPreference(mScreen); + + assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreference.getMax()).isEqualTo(10); + assertThat(mPreference.getMin()).isEqualTo(1); + assertThat(mPreference.getProgress()).isEqualTo(7); + assertThat(mPreference.isVisible()).isTrue(); + assertThat(mPreference.getOnPreferenceChangeListener()).isEqualTo(mController); } @Test @@ -132,12 +186,8 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { public void displayPreference_disabled_notVisible() { mController.displayPreference(mScreen); - verify(mPreference).setMax(eq(10)); - verify(mPreference).setMin(eq(0)); - verify(mPreference).setProgress(eq(7)); - verify(mPreference).setContinuousUpdates(eq(true)); - verify(mPreference, never()).setOnPreferenceChangeListener(any()); - verify(mPreference).setVisible(eq(false)); + assertThat(mPreference.isVisible()).isFalse(); + assertThat(mPreference.getOnPreferenceChangeListener()).isNull(); } @Test @@ -153,13 +203,13 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { @Test public void setSliderPosition_min_secureSettingsUpdated() { - var isSliderSet = mController.setSliderPosition(0); + var isSliderSet = mController.setSliderPosition(1); assertThat(isSliderSet).isTrue(); assertThat(Settings.Secure.getInt( mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, - 7)).isEqualTo(0); + 7)).isEqualTo(1); } @Test @@ -194,4 +244,140 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, 7)).isEqualTo(7); } + + @Test + public void updateState_enabledProtan_preferenceEnabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void updateState_enabledDeuteran_preferenceEnabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 12); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void updateState_enabledTritan_preferenceEnabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 13); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void updateState_disabledGrayScale_preferenceDisabled() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 0); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void updateState_nullPreference_noError() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 0); + + mController.updateState(null); + } + + @Test + public void updateState_enabledGrayScale_preferenceDisabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 0); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void onResume_daltonizerEnabledAfterResumed_preferenceEnabled() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11); + mController.displayPreference(mScreen); + assertThat(mPreference.isEnabled()).isFalse(); + + mLifecycle.addObserver(mController); + mLifecycle.handleLifecycleEvent(ON_RESUME); + + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + 1); + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void onResume_daltonizerDisabledAfterResumed_preferenceDisabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11); + mController.displayPreference(mScreen); + assertThat(mPreference.isEnabled()).isTrue(); + + mLifecycle.addObserver(mController); + mLifecycle.handleLifecycleEvent(ON_RESUME); + + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + 0); + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void onResume_daltonizerGrayScaledAfterResumed_preferenceDisabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11); + mController.displayPreference(mScreen); + assertThat(mPreference.isEnabled()).isTrue(); + + mLifecycle.addObserver(mController); + mLifecycle.handleLifecycleEvent(ON_RESUME); + + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, + 0); + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void onStop_daltonizerEnabledAfterOnStop_preferenceNotChanged() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11); + mController.displayPreference(mScreen); + assertThat(mPreference.isEnabled()).isFalse(); + + mLifecycle.addObserver(mController); + mLifecycle.handleLifecycleEvent(ON_STOP); + + // enabled. + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + 1); + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + private void setDaltonizerMode(int enabled, int mode) { + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + enabled); + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, + mode); + } } diff --git a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java index a775731df59..4f8860e8832 100644 --- a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java @@ -127,8 +127,8 @@ public class CombinedBiometricProfileSettingsTest { mFragment = spy(new TestCombinedBiometricProfileSettings(mContext)); doReturn(mActivity).when(mFragment).getActivity(); doReturn(mBiometricManager).when(mActivity).getSystemService(BiometricManager.class); - when(mBiometricManager.canAuthenticate( - BiometricManager.Authenticators.MANDATORY_BIOMETRICS)) + when(mBiometricManager.canAuthenticate(anyInt(), + eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS))) .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE); ReflectionHelpers.setField(mFragment, "mDashboardFeatureProvider", @@ -181,8 +181,8 @@ public class CombinedBiometricProfileSettingsTest { public void testLaunchBiometricPrompt_onCreateFragment() { ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); doNothing().when(mFragment).startActivityForResult(any(), anyInt()); - when(mBiometricManager.canAuthenticate( - BiometricManager.Authenticators.MANDATORY_BIOMETRICS)) + when(mBiometricManager.canAuthenticate(anyInt(), + eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS))) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); mFragment.onAttach(mContext); @@ -193,7 +193,7 @@ public class CombinedBiometricProfileSettingsTest { Intent intent = intentArgumentCaptor.getValue(); assertThat(intent.getComponent().getClassName()).isEqualTo( - ConfirmDeviceCredentialActivity.class.getName()); + ConfirmDeviceCredentialActivity.InternalActivity.class.getName()); } @Test diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java index a34b6de5335..29b29614133 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java @@ -176,7 +176,7 @@ public class FingerprintSettingsFragmentTest { Intent intent = intentArgumentCaptor.getValue(); assertThat(intent.getComponent().getClassName()).isEqualTo( - ConfirmDeviceCredentialActivity.class.getName()); + ConfirmDeviceCredentialActivity.InternalActivity.class.getName()); } // Test the case when FingerprintAuthenticateSidecar receives an error callback from the diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceTest.java index 13e2a9d4636..be62414253e 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceTest.java @@ -117,6 +117,7 @@ public class AudioSharingNamePreferenceTest { assertThat(shareButton.getVisibility()).isEqualTo(View.VISIBLE); assertThat(shareButton.getDrawable()).isNotNull(); assertThat(shareButton.hasOnClickListeners()).isTrue(); + assertThat(shareButton.getContentDescription()).isNotNull(); assertThat(divider).isNotNull(); assertThat(divider.getVisibility()).isEqualTo(View.VISIBLE); diff --git a/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java index bd005b3423c..978380ecfdd 100644 --- a/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java @@ -16,8 +16,9 @@ package com.android.settings.development; -import static com.android.settings.development.FreeformWindowsPreferenceController - .SETTING_VALUE_OFF; +import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; + +import static com.android.settings.development.FreeformWindowsPreferenceController.SETTING_VALUE_OFF; import static com.android.settings.development.FreeformWindowsPreferenceController.SETTING_VALUE_ON; import static com.google.common.truth.Truth.assertThat; @@ -29,6 +30,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.PackageManager; import android.provider.Settings; import androidx.fragment.app.FragmentActivity; @@ -43,7 +45,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @@ -52,9 +53,10 @@ import org.robolectric.annotation.Config; }) public class FreeformWindowsPreferenceControllerTest { - private static final String ENG_BUILD_TYPE = "eng"; - private static final String USER_BUILD_TYPE = "user"; - + @Mock + Context mContext; + @Mock + private PackageManager mPackageManager; @Mock private SwitchPreference mPreference; @Mock @@ -68,33 +70,33 @@ public class FreeformWindowsPreferenceControllerTest { @Mock private FragmentTransaction mTransaction; - private Context mContext; private FreeformWindowsPreferenceController mController; @Before public void setup() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; doReturn(mTransaction).when(mFragmentManager).beginTransaction(); doReturn(mFragmentManager).when(mActivity).getSupportFragmentManager(); doReturn(mActivity).when(mFragment).getActivity(); mController = new FreeformWindowsPreferenceController(mContext, mFragment); when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + when(mContext.getPackageManager()).thenReturn(mPackageManager); mController.displayPreference(mScreen); } @Test - public void isAvailable_engBuild_shouldBeTrue() { + public void isAvailable_deviceHasFreeformWindowSystemFeature_returnsFalse() { mController = spy(mController); - doReturn(ENG_BUILD_TYPE).when(mController).getBuildType(); + when(mPackageManager.hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)).thenReturn(true); - assertThat(mController.isAvailable()).isTrue(); + assertThat(mController.isAvailable()).isFalse(); } @Test - public void isAvailable_userBuild_shouldBeTrue() { + public void isAvailable_deviceDoesNotHaveFreeformWindowSystemFeature_returnsTrue() { mController = spy(mController); - doReturn(USER_BUILD_TYPE).when(mController).getBuildType(); + when(mPackageManager.hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)).thenReturn( + false); assertThat(mController.isAvailable()).isTrue(); } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java index b949a3eb302..fdb075d2e03 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java @@ -389,6 +389,28 @@ public class BatteryHeaderPreferenceControllerTest { verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString); } + @Test + public void updateBatteryStatus_dockDefend_chargingOnHold() { + var expected = "Charging on hold"; + mBatteryInfo.isBatteryDefender = false; + when(mFactory.powerUsageFeatureProvider.isExtraDefend()).thenReturn(true); + + mController.updateBatteryStatus(/* label= */ null, mBatteryInfo); + + verify(mBatteryUsageProgressBarPref).setBottomSummary(expected); + } + + @Test + public void updateBatteryStatus_batteryDefender_chargingOnHold() { + var expected = "Charging on hold"; + mBatteryInfo.isBatteryDefender = true; + when(mFactory.powerUsageFeatureProvider.isExtraDefend()).thenReturn(false); + + mController.updateBatteryStatus(/* label= */ null, mBatteryInfo); + + verify(mBatteryUsageProgressBarPref).setBottomSummary(expected); + } + private BatteryInfo arrangeUpdateBatteryStatusTestWithRemainingLabel( String remainingLabel, String statusLabel, diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java index af0cb91752c..63d44d09a87 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java @@ -28,9 +28,9 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.BatteryManager; import android.os.SystemClock; +import android.os.UserManager; import android.text.format.DateUtils; -import com.android.settings.testutils.BatteryTestUtils; import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; @@ -49,6 +49,7 @@ public final class BatteryUsageBroadcastReceiverTest { private FakeFeatureFactory mFakeFeatureFactory; @Mock private PackageManager mPackageManager; + @Mock private UserManager mUserManager; @Before public void setUp() { @@ -57,6 +58,7 @@ public final class BatteryUsageBroadcastReceiverTest { mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); mBatteryUsageBroadcastReceiver = new BatteryUsageBroadcastReceiver(); doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); DatabaseUtils.getSharedPreferences(mContext).edit().clear().apply(); } @@ -69,7 +71,17 @@ public final class BatteryUsageBroadcastReceiverTest { @Test public void onReceive_workProfile_doNothing() { - BatteryTestUtils.setWorkProfile(mContext); + doReturn(true).when(mUserManager).isManagedProfile(); + + mBatteryUsageBroadcastReceiver.onReceive( + mContext, new Intent(BatteryUsageBroadcastReceiver.ACTION_BATTERY_UNPLUGGING)); + + assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isFalse(); + } + + @Test + public void onReceive_privateProfile_doNothing() { + doReturn(true).when(mUserManager).isPrivateProfile(); mBatteryUsageBroadcastReceiver.onReceive( mContext, new Intent(BatteryUsageBroadcastReceiver.ACTION_BATTERY_UNPLUGGING)); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java index 950f8280215..ac711a482af 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java @@ -19,12 +19,16 @@ package com.android.settings.fuelgauge.batteryusage; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.os.UserManager; import androidx.test.core.app.ApplicationProvider; @@ -39,6 +43,8 @@ import com.android.settings.testutils.FakeClock; 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 java.time.Duration; @@ -62,9 +68,14 @@ public final class BatteryUsageContentProviderTest { private Context mContext; private BatteryUsageContentProvider mProvider; + @Mock + private UserManager mUserManager; + @Before public void setUp() { - mContext = ApplicationProvider.getApplicationContext(); + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); mProvider = new BatteryUsageContentProvider(); mProvider.attachInfo(mContext, /* info= */ null); BatteryTestUtils.setUpBatteryStateDatabase(mContext); @@ -77,7 +88,13 @@ public final class BatteryUsageContentProviderTest { @Test public void onCreate_withWorkProfileMode_returnsFalse() { - BatteryTestUtils.setWorkProfile(mContext); + doReturn(true).when(mUserManager).isManagedProfile(); + assertThat(mProvider.onCreate()).isFalse(); + } + + @Test + public void onCreate_withPrivateProfileMode_returnsFalse() { + doReturn(true).when(mUserManager).isPrivateProfile(); assertThat(mProvider.onCreate()).isFalse(); } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java index 704637f14c7..f318a2bcddf 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java @@ -18,6 +18,9 @@ package com.android.settings.fuelgauge.batteryusage; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import android.app.AlarmManager; @@ -26,6 +29,7 @@ import android.app.usage.UsageStatsManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.os.UserManager; import androidx.test.core.app.ApplicationProvider; @@ -37,6 +41,8 @@ import org.junit.After; 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; import org.robolectric.shadows.ShadowAlarmManager; @@ -55,10 +61,15 @@ public final class BootBroadcastReceiverTest { private ShadowAlarmManager mShadowAlarmManager; private PeriodicJobManager mPeriodicJobManager; + @Mock + private UserManager mUserManager; + @Before public void setUp() { + MockitoAnnotations.initMocks(this); TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - mContext = ApplicationProvider.getApplicationContext(); + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); mPeriodicJobManager = PeriodicJobManager.getInstance(mContext); mShadowAlarmManager = shadowOf(mContext.getSystemService(AlarmManager.class)); mReceiver = new BootBroadcastReceiver(); @@ -78,7 +89,15 @@ public final class BootBroadcastReceiverTest { @Test public void onReceive_withWorkProfile_notRefreshesJob() { - BatteryTestUtils.setWorkProfile(mContext); + doReturn(true).when(mUserManager).isManagedProfile(); + mReceiver.onReceive(mContext, new Intent(Intent.ACTION_BOOT_COMPLETED)); + + assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull(); + } + + @Test + public void onReceive_withPrivateProfile_notRefreshesJob() { + doReturn(true).when(mUserManager).isPrivateProfile(); mReceiver.onReceive(mContext, new Intent(Intent.ACTION_BOOT_COMPLETED)); assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull(); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java index d89e61b00ea..2fda2779106 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java @@ -47,7 +47,6 @@ import android.os.UserManager; import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity; import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity; -import com.android.settings.testutils.BatteryTestUtils; import org.junit.Before; import org.junit.Test; @@ -450,6 +449,26 @@ public final class DatabaseUtilsTest { assertThat(batteryHistMap).isEmpty(); } + @Test + public void getHistoryMap_withPrivateProfile_returnExpectedMap() + throws PackageManager.NameNotFoundException { + doReturn("com.fake.package").when(mContext).getPackageName(); + doReturn(mMockContext) + .when(mContext) + .createPackageContextAsUser("com.fake.package", /* flags= */ 0, UserHandle.OWNER); + doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); + doReturn(UserHandle.CURRENT).when(mContext).getUser(); + doReturn(true).when(mUserManager).isPrivateProfile(); + doReturn(UserHandle.SYSTEM).when(mUserManager).getProfileParent(UserHandle.CURRENT); + + DatabaseUtils.sFakeSupplier = () -> getMatrixCursor(); + + final Map> batteryHistMap = + DatabaseUtils.getHistoryMapSinceQueryTimestamp(mContext, 0); + + assertThat(batteryHistMap).isEmpty(); + } + @Test public void removeUsageSource_hasNoData() { DatabaseUtils.removeUsageSource(mContext); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java index d111de2160c..ea3c04c87c0 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java @@ -18,11 +18,15 @@ package com.android.settings.fuelgauge.batteryusage; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import android.app.AlarmManager; import android.content.Context; import android.content.Intent; +import android.os.UserManager; import androidx.test.core.app.ApplicationProvider; @@ -34,6 +38,8 @@ import org.junit.After; 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.ShadowAlarmManager; @@ -53,12 +59,17 @@ public final class PeriodicJobReceiverTest { private PeriodicJobManager mPeriodicJobManager; private ShadowAlarmManager mShadowAlarmManager; + @Mock + private UserManager mUserManager; + @Before public void setUp() { - mContext = ApplicationProvider.getApplicationContext(); + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); mPeriodicJobManager = PeriodicJobManager.getInstance(mContext); mShadowAlarmManager = shadowOf(mContext.getSystemService(AlarmManager.class)); mReceiver = new PeriodicJobReceiver(); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); // Inserts fake data into database for testing. final BatteryStateDatabase database = BatteryTestUtils.setUpBatteryStateDatabase(mContext); @@ -114,7 +125,14 @@ public final class PeriodicJobReceiverTest { @Test public void onReceive_inWorkProfileMode_notRefreshesJob() { - BatteryTestUtils.setWorkProfile(mContext); + doReturn(true).when(mUserManager).isManagedProfile(); + mReceiver.onReceive(mContext, JOB_UPDATE_INTENT); + assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull(); + } + + @Test + public void onReceive_inPrivateProfileMode_notRefreshesJob() { + doReturn(true).when(mUserManager).isPrivateProfile(); mReceiver.onReceive(mContext, JOB_UPDATE_INTENT); assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull(); } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java index d9981069a01..0dd18c54ae2 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java @@ -18,7 +18,11 @@ package com.android.settings.fuelgauge.batteryusage.bugreport; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + import android.content.Context; +import android.os.UserManager; import androidx.test.core.app.ApplicationProvider; @@ -27,6 +31,8 @@ import com.android.settings.testutils.BatteryTestUtils; 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 java.io.FileDescriptor; @@ -46,11 +52,17 @@ public final class BugReportContentProviderTest { private StringWriter mStringWriter; private BugReportContentProvider mBugReportContentProvider; + @Mock + private UserManager mUserManager; + @Before public void setUp() { + MockitoAnnotations.initMocks(this); mStringWriter = new StringWriter(); mPrintWriter = new PrintWriter(mStringWriter); - mContext = ApplicationProvider.getApplicationContext(); + mContext = spy(ApplicationProvider.getApplicationContext()); + doReturn(mContext).when(mContext).getApplicationContext(); + doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); mBugReportContentProvider = new BugReportContentProvider(); mBugReportContentProvider.attachInfo(mContext, /* info= */ null); // Inserts fake data into database for testing. @@ -77,7 +89,14 @@ public final class BugReportContentProviderTest { @Test public void dump_inWorkProfileMode_notDumpsBatteryUsageData() { - BatteryTestUtils.setWorkProfile(mContext); + doReturn(true).when(mUserManager).isManagedProfile(); + mBugReportContentProvider.dump(FileDescriptor.out, mPrintWriter, new String[] {}); + assertThat(mStringWriter.toString()).isEmpty(); + } + + @Test + public void dump_inPrivateProfileMode_notDumpsBatteryUsageData() { + doReturn(true).when(mUserManager).isPrivateProfile(); mBugReportContentProvider.dump(FileDescriptor.out, mPrintWriter, new String[] {}); assertThat(mStringWriter.toString()).isEmpty(); } diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java index fc3cef142c5..61ca4d84662 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java @@ -22,11 +22,15 @@ import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; -import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.AUTOMATIC_TRIGGER_PREF_KEY; +import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.ADD_TRIGGER_KEY; +import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.AUTOMATIC_TRIGGER_KEY; +import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceControllerTest.CharSequenceTruth.assertThat; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -35,6 +39,7 @@ import android.app.AutomaticZenRule; import android.app.Flags; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.platform.test.annotations.EnableFlags; @@ -42,7 +47,11 @@ import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.SystemZenRules; import android.service.notification.ZenModeConfig; +import androidx.annotation.Nullable; +import androidx.preference.Preference; import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; @@ -53,6 +62,9 @@ import com.android.settingslib.notification.modes.TestModeBuilder; import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenModesBackend; +import com.google.common.truth.StringSubject; +import com.google.common.truth.Truth; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -60,6 +72,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import java.util.Calendar; @@ -74,32 +87,47 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { private ZenModesBackend mBackend; private Context mContext; - private PrimarySwitchPreference mPreference; - @Mock private PackageManager mPm; @Mock private ConfigurationActivityHelper mConfigurationActivityHelper; - @Mock private PreferenceCategory mPrefCategory; + private PrimarySwitchPreference mConfigPreference; + private Preference mAddPreference; + @Mock private DashboardFragment mFragment; - private ZenModeSetTriggerLinkPreferenceController mPrefController; + private ZenModeSetTriggerLinkPreferenceController mController; @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContext = ApplicationProvider.getApplicationContext(); - mPrefController = new ZenModeSetTriggerLinkPreferenceController(mContext, - "zen_automatic_trigger_category", mFragment, mBackend, - mConfigurationActivityHelper, - mock(ZenServiceListing.class)); - mPreference = new PrimarySwitchPreference(mContext); + PreferenceManager preferenceManager = new PreferenceManager(mContext); + PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(mContext, + R.xml.modes_rule_settings, null); - when(mPrefCategory.findPreference(AUTOMATIC_TRIGGER_PREF_KEY)).thenReturn(mPreference); + mController = new ZenModeSetTriggerLinkPreferenceController(mContext, + "zen_automatic_trigger_category", mFragment, mBackend, mPm, + mConfigurationActivityHelper, mock(ZenServiceListing.class)); + + mPrefCategory = preferenceScreen.findPreference("zen_automatic_trigger_category"); + mConfigPreference = checkNotNull(mPrefCategory).findPreference(AUTOMATIC_TRIGGER_KEY); + mAddPreference = checkNotNull(mPrefCategory).findPreference(ADD_TRIGGER_KEY); + + when(mPm.getApplicationInfo(any(), anyInt())).then( + (Answer) invocationOnMock -> { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.packageName = invocationOnMock.getArgument(0); + appInfo.labelRes = 1; // Whatever, but != 0 so that loadLabel calls PM.getText() + return appInfo; + }); + when(mPm.getText(any(), anyInt(), any())).then( + (Answer) invocationOnMock -> + "App named " + invocationOnMock.getArgument(0)); } @Test @@ -110,37 +138,37 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) .build(), true); - mPrefController.updateZenMode(mPrefCategory, manualMode); - assertThat(mPrefController.isAvailable()).isFalse(); + mController.updateZenMode(mPrefCategory, manualMode); + assertThat(mController.isAvailable()).isFalse(); // should be available for other modes - mPrefController.updateZenMode(mPrefCategory, TestModeBuilder.EXAMPLE); - assertThat(mPrefController.isAvailable()).isTrue(); + mController.updateZenMode(mPrefCategory, TestModeBuilder.EXAMPLE); + assertThat(mController.isAvailable()).isTrue(); } @Test - public void testUpdateState() { + public void updateState_switchCheckedIfRuleEnabled() { ZenMode zenMode = new TestModeBuilder().setEnabled(false).build(); // Update preference controller with a zen mode that is not enabled - mPrefController.updateZenMode(mPrefCategory, zenMode); - assertThat(mPreference.getCheckedState()).isFalse(); + mController.updateZenMode(mPrefCategory, zenMode); + assertThat(mConfigPreference.getCheckedState()).isFalse(); // Now with the rule enabled zenMode.getRule().setEnabled(true); - mPrefController.updateZenMode(mPrefCategory, zenMode); - assertThat(mPreference.getCheckedState()).isTrue(); + mController.updateZenMode(mPrefCategory, zenMode); + assertThat(mConfigPreference.getCheckedState()).isTrue(); } @Test - public void testOnPreferenceChange() { + public void onPreferenceChange_updatesMode() { ZenMode zenMode = new TestModeBuilder().setEnabled(false).build(); // start with disabled rule - mPrefController.updateZenMode(mPrefCategory, zenMode); + mController.updateZenMode(mPrefCategory, zenMode); - // then update the preference to be checked - mPrefController.mSwitchChangeListener.onPreferenceChange(mPreference, true); + // then flip the switch + mConfigPreference.callChangeListener(true); // verify the backend got asked to update the mode to be enabled ArgumentCaptor captor = ArgumentCaptor.forClass(ZenMode.class); @@ -149,7 +177,7 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { } @Test - public void testRuleLink_calendar() { + public void updateState_scheduleCalendarRule() { ZenModeConfig.EventInfo eventInfo = new ZenModeConfig.EventInfo(); eventInfo.calendarId = 1L; eventInfo.calName = "My events"; @@ -159,23 +187,21 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { .setType(TYPE_SCHEDULE_CALENDAR) .setTriggerDescription("My events") .build(); - mPrefController.updateZenMode(mPrefCategory, mode); - assertThat(mPreference.getTitle()).isNotNull(); - assertThat(mPreference.getTitle().toString()).isEqualTo( - mContext.getString(R.string.zen_mode_set_calendar_link)); - assertThat(mPreference.getSummary()).isNotNull(); - assertThat(mPreference.getSummary().toString()).isEqualTo( - mode.getRule().getTriggerDescription()); - assertThat(mPreference.getIcon()).isNull(); + mController.updateState(mPrefCategory, mode); + assertThat(mAddPreference.isVisible()).isFalse(); + assertThat(mConfigPreference.isVisible()).isTrue(); + assertThat(mConfigPreference.getTitle()).isEqualTo("Calendar events"); + assertThat(mConfigPreference.getSummary()).isEqualTo("My events"); // Destination as written into the intent by SubSettingLauncher - assertThat(mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) + assertThat( + mConfigPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) .isEqualTo(ZenModeSetCalendarFragment.class.getName()); } @Test - public void testRuleLink_schedule() { + public void updateState_scheduleTimeRule() { ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo(); scheduleInfo.days = new int[]{Calendar.MONDAY, Calendar.TUESDAY, Calendar.THURSDAY}; scheduleInfo.startHour = 1; @@ -186,44 +212,41 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { .setType(TYPE_SCHEDULE_TIME) .setTriggerDescription("some schedule") .build(); - mPrefController.updateZenMode(mPrefCategory, mode); - assertThat(mPreference.getTitle()).isNotNull(); - assertThat(mPreference.getTitle().toString()).isEqualTo( - mContext.getString(R.string.zen_mode_set_schedule_link)); - assertThat(mPreference.getSummary()).isNotNull(); - assertThat(mPreference.getSummary().toString()).isEqualTo( - mode.getRule().getTriggerDescription()); - assertThat(mPreference.getIcon()).isNull(); + mController.updateState(mPrefCategory, mode); + assertThat(mAddPreference.isVisible()).isFalse(); + assertThat(mConfigPreference.isVisible()).isTrue(); + assertThat(mConfigPreference.getTitle()).isEqualTo("1:00 AM - 3:00 PM"); + assertThat(mConfigPreference.getSummary()).isEqualTo("Mon - Tue, Thu"); // Destination as written into the intent by SubSettingLauncher - assertThat(mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) + assertThat( + mConfigPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) .isEqualTo(ZenModeSetScheduleFragment.class.getName()); } @Test - public void testRuleLink_manual() { + public void updateState_customManualRule() { ZenMode mode = new TestModeBuilder() .setConditionId(ZenModeConfig.toCustomManualConditionId()) .setPackage(SystemZenRules.PACKAGE_ANDROID) .setType(TYPE_OTHER) .setTriggerDescription("Will not be shown") .build(); - mPrefController.updateZenMode(mPrefCategory, mode); - assertThat(mPreference.getTitle()).isNotNull(); - assertThat(mPreference.getTitle().toString()).isEqualTo( + mController.updateState(mPrefCategory, mode); + + assertThat(mConfigPreference.isVisible()).isFalse(); + assertThat(mAddPreference.isVisible()).isTrue(); + assertThat(mAddPreference.getTitle()).isEqualTo( mContext.getString(R.string.zen_mode_select_schedule)); - assertThat(mPreference.getIcon()).isNotNull(); - assertThat(mPreference.getSummary()).isNotNull(); - assertThat(mPreference.getSummary().toString()).isEqualTo(""); - - // Set up a click listener to open the dialog. - assertThat(mPreference.getOnPreferenceClickListener()).isNotNull(); + assertThat(mAddPreference.getSummary()).isNull(); + // Sets up a click listener to open the dialog. + assertThat(mAddPreference.getOnPreferenceClickListener()).isNotNull(); } @Test - public void testRuleLink_appWithConfigActivity_linksToConfigActivity() { + public void updateState_appWithConfigActivity_showsLinkToConfigActivity() { ZenMode mode = new TestModeBuilder() .setPackage("some.package") .setTriggerDescription("When The Music's Over") @@ -232,28 +255,62 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any())) .thenReturn(configurationIntent); - mPrefController.updateZenMode(mPrefCategory, mode); + mController.updateState(mPrefCategory, mode); - assertThat(mPreference.getTitle()).isNotNull(); - assertThat(mPreference.getTitle().toString()).isEqualTo( - mContext.getString(R.string.zen_mode_configuration_link_title)); - assertThat(mPreference.getSummary()).isNotNull(); - assertThat(mPreference.getSummary().toString()).isEqualTo("When The Music's Over"); - assertThat(mPreference.getIntent()).isEqualTo(configurationIntent); + assertThat(mConfigPreference.isVisible()).isTrue(); + assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app"); + assertThat(mConfigPreference.getSummary()).isEqualTo("When The Music's Over"); + assertThat(mConfigPreference.getIntent()).isEqualTo(configurationIntent); } @Test - public void testRuleLink_appWithoutConfigActivity_hidden() { + public void updateState_appWithoutConfigActivity_showsWithoutLinkToConfigActivity() { ZenMode mode = new TestModeBuilder() .setPackage("some.package") - .setTriggerDescription("Will not be shown :(") + .setTriggerDescription("When the saints go marching in") .build(); when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any())) .thenReturn(null); - mPrefController.updateZenMode(mPrefCategory, mode); + mController.updateState(mPrefCategory, mode); - assertThat(mPrefCategory.isVisible()).isFalse(); + assertThat(mConfigPreference.isVisible()).isTrue(); + assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app"); + assertThat(mConfigPreference.getSummary()).isEqualTo("When the saints go marching in"); + assertThat(mConfigPreference.getIntent()).isNull(); + } + + @Test + public void updateState_appWithoutTriggerDescriptionWithConfigActivity_showsAppNameInSummary() { + ZenMode mode = new TestModeBuilder() + .setPackage("some.package") + .build(); + Intent configurationIntent = new Intent("configure the mode"); + when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any())) + .thenReturn(configurationIntent); + when(mPm.getText(any(), anyInt(), any())).thenReturn("The App Name"); + + mController.updateState(mPrefCategory, mode); + + assertThat(mConfigPreference.isVisible()).isTrue(); + assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app"); + assertThat(mConfigPreference.getSummary()).isEqualTo("Info and settings in The App Name"); + } + + @Test + public void updateState_appWithoutTriggerDescriptionNorConfigActivity_showsAppNameInSummary() { + ZenMode mode = new TestModeBuilder() + .setPackage("some.package") + .build(); + when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any())) + .thenReturn(null); + when(mPm.getText(any(), anyInt(), any())).thenReturn("The App Name"); + + mController.updateState(mPrefCategory, mode); + + assertThat(mConfigPreference.isVisible()).isTrue(); + assertThat(mConfigPreference.getTitle()).isEqualTo("Linked to app"); + assertThat(mConfigPreference.getSummary()).isEqualTo("Managed by The App Name"); } @Test @@ -264,7 +321,7 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { .setType(TYPE_OTHER) .setTriggerDescription("") .build(); - mPrefController.updateZenMode(mPrefCategory, originalMode); + mController.updateZenMode(mPrefCategory, originalMode); ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo(); scheduleInfo.days = new int[] { Calendar.MONDAY }; @@ -272,7 +329,7 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { scheduleInfo.endHour = 15; Uri scheduleUri = ZenModeConfig.toScheduleConditionId(scheduleInfo); - mPrefController.mOnScheduleOptionListener.onScheduleSelected(scheduleUri); + mController.mOnScheduleOptionListener.onScheduleSelected(scheduleUri); // verify the backend got asked to update the mode to be schedule-based. ArgumentCaptor captor = ArgumentCaptor.forClass(ZenMode.class); @@ -284,4 +341,17 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { assertThat(updatedMode.getRule().getOwner()).isEqualTo( ZenModeConfig.getScheduleConditionProvider()); } + + static class CharSequenceTruth { + /** + * Shortcut version of {@link Truth#assertThat(String)} suitable for {@link CharSequence}. + * {@link CharSequence} doesn't necessarily provide a good {@code equals()} implementation; + * however we don't care about formatting here, so we want to assert on the resulting + * string (without needing to worry that {@code assertThat(x.getText().toString())} can + * throw if the text is null). + */ + static StringSubject assertThat(@Nullable CharSequence actual) { + return Truth.assertThat((String) (actual != null ? actual.toString() : null)); + } + } } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowCrossProfileApps.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowCrossProfileApps.java index 64a5f1169ef..c52fe2f2723 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowCrossProfileApps.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowCrossProfileApps.java @@ -19,9 +19,8 @@ package com.android.settings.testutils.shadow; import android.Manifest; import android.content.Context; import android.content.pm.CrossProfileApps; -import android.content.pm.ICrossProfileApps; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageInfo; import androidx.annotation.NonNull; @@ -35,15 +34,7 @@ import java.util.Set; @Implements(CrossProfileApps.class) public class ShadowCrossProfileApps extends org.robolectric.shadows.ShadowCrossProfileApps { private static final Set configurableInteractAcrossProfilePackages = new HashSet<>(); - private Context mContext; - private PackageManager mPackageManager; - @Implementation - protected void __constructor__(Context context, ICrossProfileApps service) { - super.__constructor__(context, service); - this.mContext = context; - this.mPackageManager = context.getPackageManager(); - } public void addCrossProfilePackage(String packageName) { configurableInteractAcrossProfilePackages.add(packageName); } @@ -57,7 +48,9 @@ public class ShadowCrossProfileApps extends org.robolectric.shadows.ShadowCrossP protected boolean canUserAttemptToConfigureInteractAcrossProfiles(@NonNull String packageName) { PackageInfo packageInfo; try { - packageInfo = mPackageManager.getPackageInfo(packageName, /* flags= */ 0); + packageInfo = getContext().getPackageManager().getPackageInfo( + packageName, + /* flags= */ 0); } catch (PackageManager.NameNotFoundException e) { return false; } diff --git a/tests/unit/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceControllerTest.java b/tests/unit/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceControllerTest.java index ab1f46926fa..2aa10bba45f 100644 --- a/tests/unit/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/development/bluetooth/BluetoothStackLogPreferenceControllerTest.java @@ -16,13 +16,9 @@ package com.android.settings.development.bluetooth; -import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY; -import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST; -import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BTSTACK_LOG_MODE_VERBOSE_INDEX; -import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BTSTACK_LOG_MODE_DEBUG_INDEX; -import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BTSTACK_LOG_MODE_INFO_INDEX; -import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BTSTACK_LOG_MODE_WARN_INDEX; -import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BTSTACK_LOG_MODE_ERROR_INDEX; +import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BT_LOG_LEVEL_DEFAULT_INDEX; +import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BT_LOG_LEVEL_PROP; +import static com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController.BT_LOG_LEVEL_PROP_PERSIST; import static com.google.common.truth.Truth.assertThat; @@ -37,18 +33,21 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) -@Ignore("b/339148064") public class BluetoothStackLogPreferenceControllerTest { - private static final String TAG = "BluetoothStackLogPreferenceControllerTest"; + private static final String COM_ANDROID_SETTINGS = "com.android.settings"; + private static final String TYPE_ARRAY = "array"; - @Mock private Context mContext; + private static final String XML_DEFINED_PREFERENCE_KEY = "bt_stack_log_level"; + private static final String XML_DEFINED_ENTRIES_RESOURCE = "bt_stack_log_level_entries"; + private static final String XML_DEFINED_VALUES_RESOURCE = "bt_stack_log_level_values"; + + private static final String PROPERTY_CLEARED = ""; + + private Context mContext; private ListPreference mPreference; private PreferenceManager mPreferenceManager; @@ -61,7 +60,6 @@ public class BluetoothStackLogPreferenceControllerTest { @Before public void setup() { - MockitoAnnotations.initMocks(this); mContext = ApplicationProvider.getApplicationContext(); if (Looper.myLooper() == null) { @@ -71,12 +69,11 @@ public class BluetoothStackLogPreferenceControllerTest { mPreferenceManager = new PreferenceManager(mContext); mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext); mPreference = new ListPreference(mContext); - mController = new BluetoothStackLogPreferenceController(mContext); mPreference.setKey(mController.getPreferenceKey()); - mPreference.setEntries(com.android.settings.R.array.bt_stack_log_level_entries); - mPreference.setEntryValues(com.android.settings.R.array.bt_stack_log_level_values); + mPreference.setEntries(getStringArrayResourceId(XML_DEFINED_ENTRIES_RESOURCE)); + mPreference.setEntryValues(getStringArrayResourceId(XML_DEFINED_VALUES_RESOURCE)); mPreferenceScreen.addPreference(mPreference); mController.displayPreference(mPreferenceScreen); @@ -86,134 +83,109 @@ public class BluetoothStackLogPreferenceControllerTest { } /** - * Test that default log level is set to INFO + * Get the resource ID associated with a resource name + * + * This looks up the resource id by name using our device's context. This way, we can avoid + * hardcoding a resource ID or value from the R class which may not match the resource IDs on + * the device under test. + * + * Usage: int valuesResId = getStringArrayResource("bt_stack_log_level_values"); + * Usage: int entriesResId = getStringArrayResource("bt_stack_log_level_entries"); + * + * @param res - The resource name to look up + * @return The integer resource ID corresponding to the given resource name */ - @Test - public void verifyDefaultState_enablesDefaultLogLevelEntriesAndValuesSameSize() { - mController.onPreferenceChange(mPreference, mController.getDefaultModeIndex()); - assertThat(mPreference.getValue().toString()).isEqualTo(mListValues - [BTSTACK_LOG_MODE_INFO_INDEX].toString()); - assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries - [BTSTACK_LOG_MODE_INFO_INDEX].toString()); + public int getStringArrayResourceId(String res) { + return mContext.getResources().getIdentifier(res, TYPE_ARRAY, COM_ANDROID_SETTINGS); } /** - * Test that log level is changed to VERBOSE when VERBOSE is selected + * Test that, for each possible value a user can select, our controller properly handles the + * value to update the underlying system property _and_ set the UI entry to the proper value. */ @Test - public void onPreferenceChanged_enableBluetoothStackVerboseLogLevel() { - mController.onPreferenceChange(mPreference, mListValues[BTSTACK_LOG_MODE_VERBOSE_INDEX] - .toString()); + public void onPreferenceChange_withEachValue_uiSetProperlyAndAllValuesWrittenToProperties() { + for (int index = 0; index < mListValues.length; index++) { + String value = mListValues[index].toString(); + String entry = mListEntries[index].toString(); - final String persistedLogLevel = SystemProperties.get( - BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST); - final String logLevel = SystemProperties.get(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY); - assertThat(persistedLogLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_VERBOSE_INDEX] - .toString()); - assertThat(logLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_VERBOSE_INDEX].toString()); + mController.onPreferenceChange(mPreference, value); - assertThat(mPreference.getValue().toString()).isEqualTo(mListValues - [BTSTACK_LOG_MODE_VERBOSE_INDEX].toString()); - assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries - [BTSTACK_LOG_MODE_VERBOSE_INDEX].toString()); + final String persistedLogLevel = SystemProperties.get(BT_LOG_LEVEL_PROP_PERSIST); + final String logLevel = SystemProperties.get(BT_LOG_LEVEL_PROP); + final String currentValue = mPreference.getValue().toString(); + final String currentEntry = mPreference.getEntry().toString(); + final String currentSummary = mPreference.getSummary().toString(); + final int currentIndex = mPreference.findIndexOfValue(currentValue); + + assertThat(persistedLogLevel).isEqualTo(value); + assertThat(logLevel).isEqualTo(value); + assertThat(currentIndex).isEqualTo(index); + assertThat(currentValue).isEqualTo(value); + assertThat(currentEntry).isEqualTo(entry); + assertThat(currentSummary).isEqualTo(entry); + } } /** - * Test that log level is changed to DEBUG when DEBUG is selected + * Test that, for each possible log tag log level value, our controller properly handles the + * value to set the UI entry to the proper value. */ @Test - public void onPreferenceChanged_enableBluetoothStackDebugLogLevel() { - mController.onPreferenceChange(mPreference, mListValues[BTSTACK_LOG_MODE_DEBUG_INDEX] - .toString()); + public void updateState_withEachValue_uiSetProperly() { + for (int index = 0; index < mListValues.length; index++) { + String value = mListValues[index].toString(); + String entry = mListEntries[index].toString(); - final String persistedLogLevel = SystemProperties.get( - BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST); - final String logLevel = SystemProperties.get(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY); - assertThat(persistedLogLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_DEBUG_INDEX] - .toString()); - assertThat(logLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_DEBUG_INDEX].toString()); + SystemProperties.set(BT_LOG_LEVEL_PROP_PERSIST, value); + SystemProperties.set(BT_LOG_LEVEL_PROP, value); - assertThat(mPreference.getValue().toString()).isEqualTo(mListValues - [BTSTACK_LOG_MODE_DEBUG_INDEX].toString()); - assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries - [BTSTACK_LOG_MODE_DEBUG_INDEX].toString()); + mController.updateState(mPreference); + + final String currentValue = mPreference.getValue().toString(); + final String currentEntry = mPreference.getEntry().toString(); + final String currentSummary = mPreference.getSummary().toString(); + final int currentIndex = mPreference.findIndexOfValue(currentValue); + + assertThat(currentIndex).isEqualTo(index); + assertThat(currentValue).isEqualTo(value); + assertThat(currentEntry).isEqualTo(entry); + assertThat(currentSummary).isEqualTo(entry); + } } /** - * Test that log level is changed to INFO when INFO is selected + * Test that our controller reverts the log level back to a missing/default value when we're + * notified that Developer Options has been disabled. */ @Test - public void onPreferenceChanged_enableBluetoothStackInfoLogLevel() { - mController.onPreferenceChange(mPreference, mListValues[BTSTACK_LOG_MODE_INFO_INDEX] - .toString()); + public void onDeveloperOptionsSwitchDisabled_preferenceSetToDefault() { + mController.onDeveloperOptionsSwitchDisabled(); - final String persistedLogLevel = SystemProperties.get( - BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST); - final String logLevel = SystemProperties.get(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY); - assertThat(persistedLogLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_INFO_INDEX] - .toString()); - assertThat(logLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_INFO_INDEX].toString()); + final String defaultEntry = mListEntries[BT_LOG_LEVEL_DEFAULT_INDEX].toString(); + final String defaultValue = mListValues[BT_LOG_LEVEL_DEFAULT_INDEX].toString(); - assertThat(mPreference.getValue().toString()).isEqualTo(mListValues - [BTSTACK_LOG_MODE_INFO_INDEX].toString()); - assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries - [BTSTACK_LOG_MODE_INFO_INDEX].toString()); + final String persistedLogLevel = SystemProperties.get(BT_LOG_LEVEL_PROP_PERSIST); + final String logLevel = SystemProperties.get(BT_LOG_LEVEL_PROP); + final String currentValue = mPreference.getValue().toString(); + final String currentEntry = mPreference.getEntry().toString(); + final String currentSummary = mPreference.getSummary().toString(); + final int currentIndex = mPreference.findIndexOfValue(currentValue); + + assertThat(persistedLogLevel).isEqualTo(PROPERTY_CLEARED); + assertThat(logLevel).isEqualTo(PROPERTY_CLEARED); + assertThat(currentIndex).isEqualTo(BT_LOG_LEVEL_DEFAULT_INDEX); + assertThat(currentValue).isEqualTo(defaultValue); + assertThat(currentEntry).isEqualTo(defaultEntry); + assertThat(currentSummary).isEqualTo(defaultEntry); } /** - * Test that log level is changed to WARN when WARN is selected + * Test that our preference key returned by our controller matches the one defined in the XML + * definition. */ @Test - public void onPreferenceChanged_enableBluetoothStackWarnLogLevel() { - mController.onPreferenceChange(mPreference, mListValues[BTSTACK_LOG_MODE_WARN_INDEX] - .toString()); - - final String persistedLogLevel = SystemProperties.get( - BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST); - final String logLevel = SystemProperties.get(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY); - assertThat(persistedLogLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_WARN_INDEX] - .toString()); - assertThat(logLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_WARN_INDEX].toString()); - - assertThat(mPreference.getValue().toString()).isEqualTo(mListValues - - [BTSTACK_LOG_MODE_WARN_INDEX].toString()); - assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries - [BTSTACK_LOG_MODE_WARN_INDEX].toString()); - } - - /** - * Test that log level is changed to ERROR when ERROR is selected - */ - @Test - public void onPreferenceChanged_enableBluetoothStackErrorLogLevel() { - mController.onPreferenceChange(mPreference, mListValues[BTSTACK_LOG_MODE_ERROR_INDEX] - .toString()); - - final String persistedLogLevel = SystemProperties.get( - BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY_PERSIST); - final String logLevel = SystemProperties.get(BLUETOOTH_BTSTACK_LOG_MODE_PROPERTY); - assertThat(persistedLogLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_ERROR_INDEX] - .toString()); - assertThat(logLevel).isEqualTo(mListValues[BTSTACK_LOG_MODE_ERROR_INDEX].toString()); - - assertThat(mPreference.getValue().toString()).isEqualTo(mListValues - [BTSTACK_LOG_MODE_ERROR_INDEX].toString()); - assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries - [BTSTACK_LOG_MODE_ERROR_INDEX].toString()); - } - - /** - * Test that preference is disabled when developer options is disabled - * Log level is also reset to default - */ - @Test - public void onDeveloperOptionsDisabled_shouldDisablePreference() { - mController.onDeveloperOptionsDisabled(); - assertThat(mPreference.isEnabled()).isFalse(); - assertThat(mPreference.getValue().toString()).isEqualTo(mListValues[mController - .getDefaultModeIndex()].toString()); - assertThat(mPreference.getSummary().toString()).isEqualTo(mListEntries[mController - .getDefaultModeIndex()].toString()); + public void getPreferenceKey_matchesXmlDefinedPreferenceKey() { + assertThat(mController.getPreferenceKey()).isEqualTo(XML_DEFINED_PREFERENCE_KEY); } }