diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 177e750c892..de54b83a220 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5515,12 +5515,12 @@ android:exported="true" android:permission="android.permission.BLUETOOTH_PRIVILEGED" /> - - + + android:featureFlag="com.android.settings.flags.catalyst_service" + android:permission="android.permission.READ_SYSTEM_PREFERENCES"> diff --git a/res/drawable/search_bar_selected_background.xml b/res/drawable/search_bar_selected_background.xml index b98ed9f7502..a1299c5969b 100644 --- a/res/drawable/search_bar_selected_background.xml +++ b/res/drawable/search_bar_selected_background.xml @@ -16,5 +16,5 @@ - + diff --git a/res/layout-land/bluetooth_audio_streams_qr_code.xml b/res/layout-land/bluetooth_audio_streams_qr_code.xml index 432d75e69d6..918b3ac60b9 100644 --- a/res/layout-land/bluetooth_audio_streams_qr_code.xml +++ b/res/layout-land/bluetooth_audio_streams_qr_code.xml @@ -45,8 +45,8 @@ diff --git a/res/layout/bluetooth_audio_streams_qr_code.xml b/res/layout/bluetooth_audio_streams_qr_code.xml index ab61f50e836..7c023551ab1 100644 --- a/res/layout/bluetooth_audio_streams_qr_code.xml +++ b/res/layout/bluetooth_audio_streams_qr_code.xml @@ -38,11 +38,11 @@ + android:layout_marginTop="70dp"/> + + \ No newline at end of file diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml index df061b74193..79949ceb85b 100644 --- a/res/values-night/colors.xml +++ b/res/values-night/colors.xml @@ -19,7 +19,6 @@ #783BE5 #3F5FBD @*android:color/material_grey_900 - @androidprv:color/materialColorSurfaceBright #5F6368 diff --git a/res/values/colors.xml b/res/values/colors.xml index 5c516d59024..73efeb232fa 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -93,9 +93,6 @@ @*android:color/accent_device_default_light - - @androidprv:color/materialColorSurfaceBright - #42a5f5 #4182ef diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 3bdea5abfe5..0d0b9e6e6e7 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -529,6 +529,7 @@ 16dp 264dp + 1.8dp 300dp 30dp 16dp diff --git a/res/values/integers.xml b/res/values/integers.xml index 5631e401ba1..9610b323be9 100644 --- a/res/values/integers.xml +++ b/res/values/integers.xml @@ -42,4 +42,6 @@ 3 2147483647 + + 50 diff --git a/res/values/strings.xml b/res/values/strings.xml index f301fff2877..d83029a2be3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -965,6 +965,8 @@ Fingerprint Fingerprints + + Use fingerprint to @@ -4711,6 +4713,11 @@ Custom custom value + + Bounce key threshold time + + Slow key threshold time + Slow keys @@ -4746,17 +4753,17 @@ Mouse keys for %s - Use the \“%s\” keys to move the mouse pointer + Use the \"%s\" keys to move the mouse pointer - Use the \“%s\” key to click the primary mouse button + Use the \"%s\" key to click the primary mouse button - Use the \“%s\” key to press & hold the primary mouse button + Use the \"%s\" key to press & hold the primary mouse button - Use the \“%s\” key to release the primary mouse button + Use the \"%s\" key to release the primary mouse button - Use the \“%1$s\” key to toggle scroll mode. This will make the \“%2$s\” keys scroll the view top, down, left or right + Use the \"%1$s\" key to toggle scroll mode. This will make the \"%2$s\" keys scroll the view top, down, left or right - Use the \“%s\” key to click the secondary mouse button + Use the \"%s\" key to click the secondary mouse button View keyboard shortcuts @@ -4776,7 +4783,7 @@ Mouse - Pointer speed, swap buttons, button customisation + Pointer speed, swap buttons, button customization Pointer speed, gestures @@ -4801,7 +4808,7 @@ Cursor speed - Customise 3-finger tap + Customize 3-finger tap Touchpad acceleration @@ -13927,6 +13934,10 @@ Close Connect another pair of compatible headphones, or share your stream\'s name and password with the other person + + Let others scan this code and listen to your audio\n\nStream name: %1$s\nPassword: %2$s + + or pair another set of compatible headphones Pair another set of compatible headphones, or share your audio stream QR code with the other person @@ -14035,7 +14046,7 @@ Listening now - Paused by host + Stream paused Stop listening @@ -14090,7 +14101,7 @@ Device only - New contacts won\'t be synced with an account + Contacts may not sync or be available on your other devices Contacts will be saved to your device and synced to your account by default diff --git a/res/xml/security_settings_fingerprint.xml b/res/xml/security_settings_fingerprint.xml index 701d493e749..2868351f802 100644 --- a/res/xml/security_settings_fingerprint.xml +++ b/res/xml/security_settings_fingerprint.xml @@ -19,6 +19,11 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/security_settings_fingerprint_preference_title"> + + mExtPrefKeys = new ArrayList<>(); private long mChallenge; @@ -469,6 +476,7 @@ public class FingerprintSettings extends SubSettings { if (preference instanceof PrimarySwitchIntentPreference) { preference.setOnPreferenceClickListener(this::onExtIntentPreferenceClick); } + mExtPrefKeys.add(preference.getKey()); mFingerprintUnlockCategory.addPreference(preference); } } @@ -648,13 +656,16 @@ public class FingerprintSettings extends SubSettings { mFooterColumns.add(column2); } else { final FooterColumn column = new FooterColumn(); + final FingerprintSettingsFeatureProvider featureProvider = + FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider() + .getFingerprintSettingsFeatureProvider(); column.mTitle = getString(isPrivateProfile() ? R.string.private_space_fingerprint_enroll_introduction_message : R.string.security_settings_fingerprint_enroll_introduction_v3_message, DeviceHelper.getDeviceName(getActivity())); column.mLearnMoreClickListener = learnMoreClickListener; column.mLearnMoreOverrideText = getText( - R.string.security_settings_fingerprint_settings_footer_learn_more); + featureProvider.getSettingPageFooterLearnMoreDescription()); mFooterColumns.add(column); } } @@ -735,6 +746,14 @@ public class FingerprintSettings extends SubSettings { scrollToPreference(fpPrefKey); addFingerprintUnlockCategory(); } + final int descriptionRes = FeatureFactory.getFeatureFactory() + .getFingerprintFeatureProvider().getFingerprintSettingsFeatureProvider() + .getSettingPageDescription(); + if (ResourceId.isValid(descriptionRes)) { + final Preference preference = findPreference(KEY_FINGERPRINT_DESCRIPTION); + preference.setTitle(descriptionRes); + preference.setVisible(true); + } createFooterPreference(root); } @@ -748,8 +767,7 @@ public class FingerprintSettings extends SubSettings { R.string.security_settings_fingerprint_title)); } - String keyToReturn = mIsExpressiveThemeStyle - ? KEY_FINGERPRINT_ADD_EXPRESSIVE : KEY_FINGERPRINT_ADD; + String keyToReturn = getAddFingerprintPreferenceKey(); final List items = mFingerprintManager.getEnrolledFingerprints(mUserId); final int fingerprintCount = items.size(); for (int i = 0; i < fingerprintCount; i++) { @@ -785,8 +803,7 @@ public class FingerprintSettings extends SubSettings { mFingerprintsEnrolledCategory.addPreference(pref); pref.setOnPreferenceChangeListener(this); } - mAddFingerprintPreference = findPreference(mIsExpressiveThemeStyle - ? KEY_FINGERPRINT_ADD_EXPRESSIVE : KEY_FINGERPRINT_ADD); + mAddFingerprintPreference = findPreference(getAddFingerprintPreferenceKey()); setupAddFingerprintPreference(); return keyToReturn; } @@ -812,8 +829,17 @@ public class FingerprintSettings extends SubSettings { updateAddPreference(); } + /** + * Lambda function for setCategoryHasChildrenSupplier + */ + private boolean fingerprintUnlockCategoryHasChild() { + return mFingerprintUnlockCategory.getPreferenceCount() > 0; + } + private void addFingerprintUnlockCategory() { mFingerprintUnlockCategory = findPreference(KEY_FINGERPRINT_UNLOCK_CATEGORY); + mFingerprintUnlockCategoryPreferenceController.setCategoryHasChildrenSupplier( + this::fingerprintUnlockCategoryHasChild); if (isSfps()) { // For both SFPS "screen on to auth" and "rest to unlock" final Preference restToUnlockPreference = FeatureFactory.getFeatureFactory() @@ -839,18 +865,39 @@ public class FingerprintSettings extends SubSettings { } private void updateFingerprintUnlockCategoryVisibility() { - final boolean fingerprintUnlockCategoryAvailable = - mFingerprintUnlockCategoryPreferenceController.isAvailable(); - if (mFingerprintUnlockCategory.isVisible() != fingerprintUnlockCategoryAvailable) { - mFingerprintUnlockCategory.setVisible(fingerprintUnlockCategoryAvailable); - } + final int categoryStatus = + mFingerprintUnlockCategoryPreferenceController.getAvailabilityStatus(); + updatePreferenceVisibility(categoryStatus, mFingerprintUnlockCategory); + if (mRequireScreenOnToAuthPreferenceController != null) { - mRequireScreenOnToAuthPreference.setVisible( - mRequireScreenOnToAuthPreferenceController.isAvailable()); + final int status = + mRequireScreenOnToAuthPreferenceController.getAvailabilityStatus(); + updatePreferenceVisibility(status, mRequireScreenOnToAuthPreference); } if (mScreenOffUnlockUdfpsPreferenceController != null) { - mScreenOffUnlockUdfpsPreference.setVisible( - mScreenOffUnlockUdfpsPreferenceController.isAvailable()); + final int status = + mScreenOffUnlockUdfpsPreferenceController.getAvailabilityStatus(); + updatePreferenceVisibility(status, mScreenOffUnlockUdfpsPreference); + } + if (!mExtPrefKeys.isEmpty()) { + for (String key: mExtPrefKeys) { + Preference preference = mFingerprintUnlockCategory.findPreference(key); + if (preference != null) { + updatePreferenceVisibility(categoryStatus, preference); + } + } + } + } + + private void updatePreferenceVisibility(int availabilityStatus, Preference preference) { + if (availabilityStatus == AVAILABLE) { + preference.setVisible(true); + preference.setEnabled(true); + } else if (availabilityStatus == CONDITIONALLY_UNAVAILABLE) { + preference.setVisible(true); + preference.setEnabled(false); + } else { + preference.setVisible(false); } } @@ -898,8 +945,29 @@ public class FingerprintSettings extends SubSettings { .setOnPreferenceChangeListener(fingerprintAppController); } + private void updateUseFingerprintToEnableStatus() { + final PreferenceCategory category = + findPreference(KEY_BIOMETRICS_USE_FINGERPRINT_TO_CATEGORY); + if (!category.isVisible()) { + return; + } + final boolean hasFingerprintEnrolled = + mFingerprintManager.getEnrolledFingerprints(mUserId).size() > 0; + + final FingerprintSettingsKeyguardUnlockPreferenceController fpUnlockController = + use(FingerprintSettingsKeyguardUnlockPreferenceController.class); + findPreference(fpUnlockController.getPreferenceKey()) + .setEnabled(hasFingerprintEnrolled); + + final FingerprintSettingsAppsPreferenceController fingerprintAppController = + use(FingerprintSettingsAppsPreferenceController.class); + findPreference(fingerprintAppController.getPreferenceKey()) + .setEnabled(hasFingerprintEnrolled); + } + private void updatePreferencesAfterFingerprintRemoved() { updateAddPreference(); + updateUseFingerprintToEnableStatus(); if (isSfps() || (screenOffUnlockUdfps() && isScreenOffUnlcokSupported())) { updateFingerprintUnlockCategoryVisibility(); } @@ -911,8 +979,7 @@ public class FingerprintSettings extends SubSettings { return; // Activity went away } - mAddFingerprintPreference = findPreference( - mIsExpressiveThemeStyle ? KEY_FINGERPRINT_ADD_EXPRESSIVE : KEY_FINGERPRINT_ADD); + mAddFingerprintPreference = findPreference(getAddFingerprintPreferenceKey()); if (mAddFingerprintPreference == null) { return; // b/275519315 Skip if updateAddPreference() invoke before addPreference() @@ -945,11 +1012,11 @@ public class FingerprintSettings extends SubSettings { findPreference(KEY_FINGERPRINT_ADD_EXPRESSIVE); if (nonExpressiveBtnPreference != null) { - nonExpressiveBtnPreference.setVisible(!mIsExpressiveThemeStyle); + nonExpressiveBtnPreference.setVisible(!shouldShowExpressiveAddFingerprintPref()); } if (expressiveBtnPreference != null) { - expressiveBtnPreference.setVisible(mIsExpressiveThemeStyle); + expressiveBtnPreference.setVisible(shouldShowExpressiveAddFingerprintPref()); } } @@ -1053,7 +1120,7 @@ public class FingerprintSettings extends SubSettings { @Override public boolean onPreferenceTreeClick(Preference pref) { final String key = pref.getKey(); - if (!mIsExpressiveThemeStyle && KEY_FINGERPRINT_ADD.equals(key)) { + if (KEY_FINGERPRINT_ADD.equals(key)) { mIsEnrolling = true; Intent intent = new Intent(); intent.setClassName(SETTINGS_PACKAGE_NAME, @@ -1451,6 +1518,16 @@ public class FingerprintSettings extends SubSettings { return Utils.isPrivateProfile(mUserId, getContext()); } + private String getAddFingerprintPreferenceKey() { + return shouldShowExpressiveAddFingerprintPref() + ? KEY_FINGERPRINT_ADD_EXPRESSIVE : KEY_FINGERPRINT_ADD; + } + + private boolean shouldShowExpressiveAddFingerprintPref() { + return Flags.biometricsOnboardingEducation() && mIsExpressiveThemeStyle + && mFingerprintManager.hasEnrolledTemplates(mUserId); + } + public static class DeleteFingerprintDialog extends InstrumentedDialogFragment implements DialogInterface.OnClickListener { diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java index 2cd92fc03e4..e53ba85bea5 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsAppsPreferenceController.java @@ -23,6 +23,7 @@ import android.hardware.fingerprint.FingerprintManager; import android.provider.Settings; import androidx.annotation.NonNull; +import androidx.preference.Preference; import com.android.settings.Utils; import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; @@ -53,6 +54,20 @@ public class FingerprintSettingsAppsPreferenceController isChecked ? ON : OFF, getUserId()); } + @Override + public void updateState(Preference preference) { + super.updateState(preference); + if (!FingerprintSettings.isFingerprintHardwareDetected(mContext)) { + preference.setEnabled(false); + } else if (!mFingerprintManager.hasEnrolledTemplates(getUserId())) { + preference.setEnabled(false); + } else if (getRestrictingAdmin() != null) { + preference.setEnabled(false); + } else { + preference.setEnabled(true); + } + } + @Override public int getAvailabilityStatus() { final ActiveUnlockStatusUtils activeUnlockStatusUtils = diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFeatureProvider.kt b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFeatureProvider.kt new file mode 100644 index 00000000000..3138a0bbe00 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFeatureProvider.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.biometrics.fingerprint + +import com.android.settings.R + +/** + * Provide features for FingerprintSettings page. + */ +open class FingerprintSettingsFeatureProvider { + /** + * Get the description shown in the FingerprintSetting page. + */ + open fun getSettingPageDescription(): Int { + return 0 + } + + /** + * Get the learn more description shown in the footer of the FingerprintSetting page. + */ + open fun getSettingPageFooterLearnMoreDescription(): Int { + return R.string.security_settings_fingerprint_settings_footer_learn_more + } + + companion object { + @JvmStatic + val instance = FingerprintSettingsFeatureProvider() + } +} \ No newline at end of file diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java index 55c75abbcae..6b17584320f 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsKeyguardUnlockPreferenceController.java @@ -19,9 +19,11 @@ package com.android.settings.biometrics.fingerprint; import static android.provider.Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED; import android.content.Context; +import android.hardware.fingerprint.FingerprintManager; import android.provider.Settings; import androidx.annotation.NonNull; +import androidx.preference.Preference; import com.android.settings.Utils; import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils; @@ -33,9 +35,12 @@ public class FingerprintSettingsKeyguardUnlockPreferenceController private static final int OFF = 0; private static final int DEFAULT = ON; + private FingerprintManager mFingerprintManager; + public FingerprintSettingsKeyguardUnlockPreferenceController( @NonNull Context context, @NonNull String key) { super(context, key); + mFingerprintManager = Utils.getFingerprintManagerOrNull(context); } @Override @@ -50,6 +55,20 @@ public class FingerprintSettingsKeyguardUnlockPreferenceController FINGERPRINT_KEYGUARD_ENABLED, isChecked ? ON : OFF, getUserId()); } + @Override + public void updateState(Preference preference) { + super.updateState(preference); + if (!FingerprintSettings.isFingerprintHardwareDetected(mContext)) { + preference.setEnabled(false); + } else if (!mFingerprintManager.hasEnrolledTemplates(getUserId())) { + preference.setEnabled(false); + } else if (getRestrictingAdmin() != null) { + preference.setEnabled(false); + } else { + preference.setEnabled(true); + } + } + @Override public int getAvailabilityStatus() { final ActiveUnlockStatusUtils activeUnlockStatusUtils = diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java index c949d3da4d8..2febce12cf5 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java @@ -16,8 +16,7 @@ package com.android.settings.biometrics.fingerprint; -import static android.hardware.biometrics.Flags.screenOffUnlockUdfps; - +import android.annotation.Nullable; import android.content.Context; import android.hardware.fingerprint.FingerprintManager; @@ -25,6 +24,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.settings.Utils; import com.android.settings.core.BasePreferenceController; +import java.util.function.Supplier; + /** * Preference controller that controls the fingerprint unlock features to be shown / be hidden. */ @@ -34,17 +35,31 @@ public class FingerprintUnlockCategoryController extends BasePreferenceControlle private int mUserId; @VisibleForTesting protected FingerprintManager mFingerprintManager; + @Nullable + private Supplier mCategoryHasChildSupplier = null; public FingerprintUnlockCategoryController(Context context, String key) { super(context, key); mFingerprintManager = Utils.getFingerprintManagerOrNull(context); } + public void setCategoryHasChildrenSupplier( + @Nullable Supplier categoryHasChildSupplier + ) { + mCategoryHasChildSupplier = categoryHasChildSupplier; + } + @Override public int getAvailabilityStatus() { + Supplier categoryHasChildSupplier = mCategoryHasChildSupplier; + boolean hasChild = false; + if (categoryHasChildSupplier != null) { + hasChild = categoryHasChildSupplier.get(); + } + if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected() - && (mFingerprintManager.isPowerbuttonFps() || screenOffUnlockUdfps())) { + && hasChild) { return mFingerprintManager.hasEnrolledTemplates(getUserId()) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } else { diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java index defa7e9326d..8dbfedf2f10 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java @@ -129,8 +129,11 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends Bluetooth @Override public boolean isAvailable() { - return mCachedDevice.isHearingDevice() && mCachedDevice.getProfiles().stream().anyMatch( - profile -> profile instanceof VolumeControlProfile); + return mCachedDevice.isHearingDevice() + && mCachedDevice.getProfiles().stream().anyMatch( + profile -> profile instanceof VolumeControlProfile) + && mAmbientUiController != null + && mAmbientUiController.isAmbientControlAvailable(); } @Nullable diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeController.java index e7c4c19a3f0..39305808096 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeController.java @@ -16,14 +16,12 @@ package com.android.settings.bluetooth; -import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_OTHER; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; -import static android.media.audio.Flags.automaticBtDeviceType; import android.content.Context; import android.media.AudioManager; @@ -108,15 +106,8 @@ public class BluetoothDetailsAudioDeviceTypeController extends BluetoothDetailsC final int index = pref.findIndexOfValue(value); if (index >= 0) { pref.setSummary(pref.getEntries()[index]); - if (automaticBtDeviceType()) { - mAudioManager.setBluetoothAudioDeviceCategory( - mCachedDevice.getAddress(), Integer.parseInt(value)); - } else { - mAudioManager.setBluetoothAudioDeviceCategory_legacy( - mCachedDevice.getAddress(), - mCachedDevice.getDevice().getType() == DEVICE_TYPE_LE, - Integer.parseInt(value)); - } + mAudioManager.setBluetoothAudioDeviceCategory( + mCachedDevice.getAddress(), Integer.parseInt(value)); mCachedDevice.onAudioDeviceCategoryChanged(); } } @@ -174,15 +165,8 @@ public class BluetoothDetailsAudioDeviceTypeController extends BluetoothDetailsC Integer.toString(AUDIO_DEVICE_CATEGORY_OTHER), }); - @AudioDeviceCategory int deviceCategory; - if (automaticBtDeviceType()) { - deviceCategory = mAudioManager.getBluetoothAudioDeviceCategory( - mCachedDevice.getAddress()); - } else { - deviceCategory = mAudioManager.getBluetoothAudioDeviceCategory_legacy( - mCachedDevice.getAddress(), - mCachedDevice.getDevice().getType() == DEVICE_TYPE_LE); - } + @AudioDeviceCategory int deviceCategory = mAudioManager.getBluetoothAudioDeviceCategory( + mCachedDevice.getAddress()); if (DEBUG) { Log.v(TAG, "getBluetoothAudioDeviceCategory() device: " + mCachedDevice.getDevice().getAnonymizedAddress() @@ -190,10 +174,8 @@ public class BluetoothDetailsAudioDeviceTypeController extends BluetoothDetailsC } mAudioDeviceTypePreference.setValue(Integer.toString(deviceCategory)); - if (automaticBtDeviceType()) { - if (mAudioManager.isBluetoothAudioDeviceCategoryFixed(mCachedDevice.getAddress())) { - mAudioDeviceTypePreference.setEnabled(false); - } + if (mAudioManager.isBluetoothAudioDeviceCategoryFixed(mCachedDevice.getAddress())) { + mAudioDeviceTypePreference.setEnabled(false); } mAudioDeviceTypePreference.setSummary(mAudioDeviceTypePreference.getEntry()); diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFactory.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFactory.java index b16bceb7c10..3d46361f983 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFactory.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFactory.java @@ -18,7 +18,7 @@ package com.android.settings.connecteddevice.audiosharing; import android.content.Context; import android.content.DialogInterface; -import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; @@ -162,13 +162,13 @@ public class AudioSharingDialogFactory { /** * Sets the custom image of the dialog custom body. * - * @param bitmap The bitmap to be used for the image. + * @param drawable The drawable to be used for the image. * @return This builder. */ @NonNull - public AudioSharingDialogFactory.DialogBuilder setCustomImage(Bitmap bitmap) { + public AudioSharingDialogFactory.DialogBuilder setCustomImage(Drawable drawable) { ImageView image = mCustomBody.findViewById(R.id.description_image); - image.setImageBitmap(bitmap); + image.setImageDrawable(drawable); image.setVisibility(View.VISIBLE); return this; } @@ -202,6 +202,21 @@ public class AudioSharingDialogFactory { return this; } + /** + * Sets the custom message below image. + * + * @param messageRes Resource ID of the string to be used for the message body. + * @return This builder. + */ + @NonNull + public AudioSharingDialogFactory.DialogBuilder setCustomMessage2( + @StringRes int messageRes) { + TextView subTitle = mCustomBody.findViewById(R.id.description_text_2); + subTitle.setText(messageRes); + subTitle.setVisibility(View.VISIBLE); + return this; + } + /** * Sets the custom device actions of the dialog custom body. * diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java index c121f550687..cf71d5f4685 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java @@ -17,13 +17,13 @@ package com.android.settings.connecteddevice.audiosharing; import static com.android.settings.connecteddevice.audiosharing.AudioSharingDashboardFragment.SHARE_THEN_PAIR_REQUEST_CODE; -import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsQrCodeFragment.getQrCodeBitmap; +import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsQrCodeFragment.getQrCodeDrawable; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PAIR_AND_JOIN_SHARING; import android.app.Dialog; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothLeBroadcastMetadata; -import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.Log; import android.util.Pair; @@ -45,6 +45,7 @@ import com.android.settingslib.bluetooth.BluetoothUtils; import com.google.common.collect.Iterables; +import java.nio.charset.StandardCharsets; import java.util.List; public class AudioSharingDialogFragment extends InstrumentedDialogFragment { @@ -159,7 +160,6 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment { } if (deviceItems.isEmpty()) { builder.setTitle(R.string.audio_sharing_share_dialog_title) - .setCustomMessage(R.string.audio_sharing_dialog_connect_device_content) .setCustomPositiveButton( R.string.audio_sharing_pair_button_label, v -> { @@ -183,14 +183,23 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment { }); BluetoothLeBroadcastMetadata metadata = arguments.getParcelable( BUNDLE_KEY_BROADCAST_METADATA, BluetoothLeBroadcastMetadata.class); - Bitmap qrCodeBitmap = metadata == null ? null : getQrCodeBitmap(metadata, + Drawable qrCodeDrawable = metadata == null ? null : getQrCodeDrawable(metadata, getContext()).orElse(null); - if (qrCodeBitmap != null) { - builder.setCustomImage(qrCodeBitmap) - .setCustomNegativeButton(com.android.settings.R.string.cancel, + if (qrCodeDrawable != null) { + builder.setCustomImage(qrCodeDrawable) + .setCustomMessage( + getString( + R.string.audio_sharing_dialog_qr_code_content, + metadata.getBroadcastName(), + new String( + metadata.getBroadcastCode(), + StandardCharsets.UTF_8))) + .setCustomMessage2(R.string.audio_sharing_dialog_pair_new_device_content) + .setCustomNegativeButton(R.string.audio_streams_dialog_close, v -> onCancelClick()); } else { builder.setCustomImage(R.drawable.audio_sharing_guidance) + .setCustomMessage(R.string.audio_sharing_dialog_connect_device_content) .setCustomNegativeButton( R.string.audio_sharing_qrcode_button_label, v -> { diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java index 50231665865..031f29fcaa6 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java @@ -16,6 +16,9 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING; + import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -23,6 +26,7 @@ import android.app.Service; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothVolumeControl; import android.content.Intent; @@ -64,7 +68,8 @@ public class AudioStreamMediaService extends Service { static final String DEVICES = "audio_stream_media_service_devices"; private static final String TAG = "AudioStreamMediaService"; private static final int NOTIFICATION_ID = 1; - private static final int BROADCAST_CONTENT_TEXT = R.string.audio_streams_listening_now; + private static final int BROADCAST_LISTENING_NOW_TEXT = R.string.audio_streams_listening_now; + private static final int BROADCAST_STREAM_PAUSED_TEXT = R.string.audio_streams_present_now; @VisibleForTesting static final String LEAVE_BROADCAST_ACTION = "leave_broadcast_action"; private static final String LEAVE_BROADCAST_TEXT = "Leave Broadcast"; private static final String CHANNEL_ID = "bluetooth_notification_channel"; @@ -94,11 +99,22 @@ public class AudioStreamMediaService extends Service { LEAVE_BROADCAST_ACTION, LEAVE_BROADCAST_TEXT, com.android.settings.R.drawable.ic_clear); + private final PlaybackState.Builder mPlayStateHysteresisBuilder = + new PlaybackState.Builder() + .setState( + PlaybackState.STATE_STOPPED, + STATIC_PLAYBACK_POSITION, + ZERO_PLAYBACK_SPEED) + .addCustomAction( + LEAVE_BROADCAST_ACTION, + LEAVE_BROADCAST_TEXT, + com.android.settings.R.drawable.ic_clear); private final MetricsFeatureProvider mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); private final AtomicBoolean mIsMuted = new AtomicBoolean(false); + private final AtomicBoolean mIsHysteresis = new AtomicBoolean(false); // Set 25 as default as the volume range from `VolumeControlProfile` is from 0 to 255. // If the initial volume from `onDeviceVolumeChanged` is larger than zero (not muted), we will // override this value. Otherwise, we raise the volume to 25 when the play button is clicked. @@ -255,6 +271,9 @@ public class AudioStreamMediaService extends Service { } private PlaybackState getPlaybackState() { + if (mIsHysteresis.get()) { + return mPlayStateHysteresisBuilder.build(); + } return mIsMuted.get() ? mPlayStatePausingBuilder.build() : mPlayStatePlayingBuilder.build(); } @@ -283,7 +302,9 @@ public class AudioStreamMediaService extends Service { new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing) .setStyle(mediaStyle) - .setContentText(getString(BROADCAST_CONTENT_TEXT)) + .setContentText(getString( + mIsHysteresis.get() ? BROADCAST_STREAM_PAUSED_TEXT : + BROADCAST_LISTENING_NOW_TEXT)) .setSilent(true); return notificationBuilder.build(); } @@ -307,6 +328,38 @@ public class AudioStreamMediaService extends Service { handleRemoveSource(); } + @Override + public void onReceiveStateChanged( + BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) { + super.onReceiveStateChanged(sink, sourceId, state); + if (!mHysteresisModeFixAvailable || mDevices == null || !mDevices.contains(sink)) { + return; + } + var sourceState = LocalBluetoothLeBroadcastAssistant.getLocalSourceState(state); + boolean streaming = sourceState == STREAMING; + boolean paused = sourceState == PAUSED; + // Exit early if the state is neither streaming nor paused + if (!streaming && !paused) { + return; + } + // Atomically update mIsHysteresis if its current value is not the current paused state + if (mIsHysteresis.compareAndSet(!paused, paused)) { + synchronized (mLocalSessionLock) { + if (mLocalSession == null) { + return; + } + mLocalSession.setPlaybackState(getPlaybackState()); + if (mNotificationManager != null) { + mNotificationManager.notify( + NOTIFICATION_ID, + buildNotification(mLocalSession.getSessionToken()) + ); + } + Log.d(TAG, "updating hysteresis mode to : " + paused); + } + } + } + private void handleRemoveSource() { if (mAudioStreamsHelper != null && !mAudioStreamsHelper.getConnectedBroadcastIdAndState( diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java index 656694d1b2b..daa7a2e1c23 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java @@ -19,7 +19,9 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.content.Context; +import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -30,6 +32,8 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.graphics.drawable.RoundedBitmapDrawable; +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; import com.android.settings.R; import com.android.settings.bluetooth.Utils; @@ -70,15 +74,16 @@ public class AudioStreamsQrCodeFragment extends InstrumentedFragment { if (broadcastMetadata == null) { return; } - Bitmap bm = getQrCodeBitmap(broadcastMetadata, getActivity()).orElse(null); - if (bm == null) { + Drawable drawable = getQrCodeDrawable(broadcastMetadata, getActivity()).orElse( + null); + if (drawable == null) { return; } ThreadUtils.postOnMainThread( () -> { ((ImageView) view.requireViewById(R.id.qrcode_view)) - .setImageBitmap(bm); + .setImageDrawable(drawable); if (broadcastMetadata.getBroadcastCode() != null) { String password = new String( @@ -101,28 +106,33 @@ public class AudioStreamsQrCodeFragment extends InstrumentedFragment { }); } - /** Gets an optional bitmap from metadata. */ - public static Optional getQrCodeBitmap(@Nullable BluetoothLeBroadcastMetadata metadata, + /** Gets an optional drawable from metadata. */ + public static Optional getQrCodeDrawable( + @Nullable BluetoothLeBroadcastMetadata metadata, Context context) { if (metadata == null) { - Log.d(TAG, "getQrCodeBitmap: broadcastMetadata is empty!"); + Log.d(TAG, "getQrCodeDrawable: broadcastMetadata is empty!"); return Optional.empty(); } String metadataStr = BluetoothLeBroadcastMetadataExt.INSTANCE.toQrCodeString(metadata); if (metadataStr.isEmpty()) { - Log.d(TAG, "getQrCodeBitmap: metadataStr is empty!"); + Log.d(TAG, "getQrCodeDrawable: metadataStr is empty!"); return Optional.empty(); } - Log.d(TAG, "getQrCodeBitmap: metadata : " + metadata); + Log.d(TAG, "getQrCodeDrawable: metadata : " + metadata); try { - int qrcodeSize = - context.getResources().getDimensionPixelSize(R.dimen.audio_streams_qrcode_size); - Bitmap bitmap = QrCodeGenerator.encodeQrCode(metadataStr, qrcodeSize); - return Optional.of(bitmap); + Resources resources = context.getResources(); + int qrcodeSize = resources.getDimensionPixelSize(R.dimen.audio_streams_qrcode_size); + int margin = resources.getDimensionPixelSize(R.dimen.audio_streams_qrcode_margin); + Bitmap bitmap = QrCodeGenerator.encodeQrCode(metadataStr, qrcodeSize, margin); + RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(resources, bitmap); + drawable.setCornerRadius(resources.getDimensionPixelSize( + R.dimen.audio_streams_qrcode_preview_radius)); + return Optional.of(drawable); } catch (WriterException e) { Log.d( TAG, - "getQrCodeBitmap: broadcastMetadata " + "getQrCodeDrawable: broadcastMetadata " + metadata + " qrCode generation exception " + e); diff --git a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java index 849a80b3592..4afe987dece 100644 --- a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java +++ b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java @@ -47,6 +47,7 @@ import com.android.settings.safetycenter.MoreSecurityPrivacyFragment; import com.android.settings.security.LockscreenDashboardFragment; import com.android.settings.security.SecurityAdvancedSettings; import com.android.settings.security.SecuritySettings; +import com.android.settings.supervision.SupervisionDashboardFragment; import com.android.settings.system.SystemDashboardFragment; import com.android.settingslib.drawer.CategoryKey; @@ -132,6 +133,8 @@ public class DashboardFragmentRegistry { CategoryKey.CATEGORY_SPECIAL_APP_ACCESS); PARENT_TO_CATEGORY_KEY_MAP.put(MoreSecurityPrivacyFragment.class.getName(), CategoryKey.CATEGORY_MORE_SECURITY_PRIVACY_SETTINGS); + PARENT_TO_CATEGORY_KEY_MAP.put(SupervisionDashboardFragment.class.getName(), + CategoryKey.CATEGORY_SUPERVISION); CATEGORY_KEY_TO_PARENT_MAP = new ArrayMap<>(PARENT_TO_CATEGORY_KEY_MAP.size()); diff --git a/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt b/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt index b46fe99c254..33d756ee811 100644 --- a/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt +++ b/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt @@ -16,7 +16,6 @@ package com.android.settings.datausage -import android.Manifest import android.app.settings.SettingsEnums.ACTION_DATA_SAVER_MODE import android.content.Context import com.android.settings.PreferenceActionMetricsProvider @@ -50,11 +49,9 @@ class DataSaverMainSwitchPreference : override fun storage(context: Context) = createDataStore(context) - override fun getReadPermissions(context: Context) = - Permissions.allOf(Manifest.permission.MANAGE_NETWORK_POLICY) + override fun getReadPermissions(context: Context) = Permissions.EMPTY - override fun getWritePermissions(context: Context) = - Permissions.allOf(Manifest.permission.MANAGE_NETWORK_POLICY) + override fun getWritePermissions(context: Context) = Permissions.EMPTY override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = ReadWritePermit.ALLOW diff --git a/src/com/android/settings/fuelgauge/batteryusage/ScreenOnTimeController.java b/src/com/android/settings/fuelgauge/batteryusage/ScreenOnTimeController.java index 379cef3a22a..9c56c178392 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/ScreenOnTimeController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/ScreenOnTimeController.java @@ -30,6 +30,7 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.BasePreferenceController; import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settingslib.widget.SettingsThemeHelper; import java.util.Locale; import java.util.regex.Matcher; @@ -119,10 +120,11 @@ public class ScreenOnTimeController extends BasePreferenceController { } final SpannableString spannableText = new SpannableString(text); + final int enlargeFontSizeDp = SettingsThemeHelper.isExpressiveTheme(context) ? 64 : 36; final Matcher matcher = NUMBER_PATTERN.matcher(text); while (matcher.find()) { spannableText.setSpan( - new AbsoluteSizeSpan(36, true /* dip */), + new AbsoluteSizeSpan(enlargeFontSizeDp, true /* dip */), matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); diff --git a/src/com/android/settings/fuelgauge/batteryusage/TextViewPreference.java b/src/com/android/settings/fuelgauge/batteryusage/TextViewPreference.java index b20ef39f160..c9047385e33 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/TextViewPreference.java +++ b/src/com/android/settings/fuelgauge/batteryusage/TextViewPreference.java @@ -25,9 +25,10 @@ import androidx.preference.PreferenceViewHolder; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; +import com.android.settingslib.widget.GroupSectionDividerMixin; /** A preference for a single text view. */ -public class TextViewPreference extends Preference { +public class TextViewPreference extends Preference implements GroupSectionDividerMixin { private static final String TAG = "TextViewPreference"; @VisibleForTesting CharSequence mText; diff --git a/src/com/android/settings/inputmethod/InputSettingPreferenceController.java b/src/com/android/settings/inputmethod/InputSettingPreferenceController.java index f18c9f4ed49..17b70b3da3a 100644 --- a/src/com/android/settings/inputmethod/InputSettingPreferenceController.java +++ b/src/com/android/settings/inputmethod/InputSettingPreferenceController.java @@ -25,36 +25,29 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.UserHandle; -import android.view.View; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.SeekBar; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.OnLifecycleEvent; import androidx.preference.Preference; -import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; import com.android.settings.keyboard.Flags; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import java.text.NumberFormat; -import java.util.concurrent.TimeUnit; - /** * Abstract class for toggle controllers of Keyboard input setting related function. */ public abstract class InputSettingPreferenceController extends TogglePreferenceController implements LifecycleObserver { - private static final int CUSTOM_PROGRESS_INTERVAL = 100; - private static final long MILLISECOND_IN_SECONDS = TimeUnit.SECONDS.toMillis(1); private final ContentResolver mContentResolver; protected final MetricsFeatureProvider mMetricsFeatureProvider; + protected FragmentManager mFragmentManager; + private final ContentObserver mContentObserver = new ContentObserver(new Handler(true)) { @Override public void onChange(boolean selfChange, Uri uri) { @@ -72,13 +65,6 @@ public abstract class InputSettingPreferenceController extends TogglePreferenceC protected void updateInputSettingKeysValue(int thresholdTimeMillis) { } - protected int getInputSettingKeysValue() { - return 0; - } - - protected void onCustomValueUpdated(int thresholdTimeMillis) { - } - public InputSettingPreferenceController(@NonNull Context context, @NonNull String preferenceKey) { super(context, preferenceKey); @@ -86,6 +72,10 @@ public abstract class InputSettingPreferenceController extends TogglePreferenceC mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); } + public void setFragment(Fragment fragment) { + mFragmentManager = fragment.getParentFragmentManager(); + } + @Override public void updateState(@NonNull Preference preference) { super.updateState(preference); @@ -127,113 +117,4 @@ public abstract class InputSettingPreferenceController extends TogglePreferenceC private void unregisterSettingsObserver() { mContentResolver.unregisterContentObserver(mContentObserver); } - - protected void constructDialog(Context context, int titleRes, int subtitleRes) { - mAlertDialog = new AlertDialog.Builder(context) - .setView(R.layout.dialog_keyboard_a11y_input_setting_keys) - .setPositiveButton(android.R.string.ok, - (dialog, which) -> { - RadioGroup radioGroup = - mAlertDialog.findViewById( - R.id.input_setting_keys_value_group); - SeekBar seekbar = mAlertDialog.findViewById( - R.id.input_setting_keys_value_custom_slider); - RadioButton customRadioButton = mAlertDialog.findViewById( - R.id.input_setting_keys_value_custom); - int threshold; - if (customRadioButton.isChecked()) { - threshold = seekbar.getProgress() * CUSTOM_PROGRESS_INTERVAL; - } else { - int checkedRadioButtonId = radioGroup.getCheckedRadioButtonId(); - if (checkedRadioButtonId == R.id.input_setting_keys_value_600) { - threshold = 600; - } else if (checkedRadioButtonId - == R.id.input_setting_keys_value_400) { - threshold = 400; - } else if (checkedRadioButtonId - == R.id.input_setting_keys_value_200) { - threshold = 200; - } else { - threshold = 0; - } - } - updateInputSettingKeysValue(threshold); - onCustomValueUpdated(threshold); - }) - .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()) - .create(); - mAlertDialog.setOnShowListener(dialog -> { - RadioGroup cannedValueRadioGroup = mAlertDialog.findViewById( - R.id.input_setting_keys_value_group); - RadioButton customRadioButton = mAlertDialog.findViewById( - R.id.input_setting_keys_value_custom); - TextView customValueTextView = mAlertDialog.findViewById( - R.id.input_setting_keys_value_custom_value); - SeekBar customProgressBar = mAlertDialog.findViewById( - R.id.input_setting_keys_value_custom_slider); - TextView titleTextView = mAlertDialog.findViewById( - R.id.input_setting_keys_dialog_title); - TextView subTitleTextView = mAlertDialog.findViewById( - R.id.input_setting_keys_dialog_subtitle); - titleTextView.setText(titleRes); - subTitleTextView.setText(subtitleRes); - - customProgressBar.incrementProgressBy(CUSTOM_PROGRESS_INTERVAL); - customProgressBar.setProgress(1); - View customValueView = mAlertDialog.findViewById( - R.id.input_setting_keys_custom_value_option); - customValueView.setOnClickListener(l -> customRadioButton.performClick()); - customRadioButton.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked) { - cannedValueRadioGroup.clearCheck(); - } - customValueTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE); - customValueTextView.setText( - progressToThresholdInSecond(customProgressBar.getProgress())); - customProgressBar.setVisibility(isChecked ? View.VISIBLE : View.GONE); - buttonView.setChecked(isChecked); - }); - cannedValueRadioGroup.setOnCheckedChangeListener( - (group, checkedId) -> customRadioButton.setChecked(false)); - customProgressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - customValueTextView.setText(progressToThresholdInSecond(progress)); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - } - }); - initStateBasedOnThreshold(cannedValueRadioGroup, customRadioButton, customValueTextView, - customProgressBar); - }); - } - - private static String progressToThresholdInSecond(int progress) { - return NumberFormat.getInstance().format((float) progress * CUSTOM_PROGRESS_INTERVAL - / MILLISECOND_IN_SECONDS); - } - - private void initStateBasedOnThreshold(RadioGroup cannedValueRadioGroup, - RadioButton customRadioButton, TextView customValueTextView, - SeekBar customProgressBar) { - int inputSettingKeysThreshold = getInputSettingKeysValue(); - switch (inputSettingKeysThreshold) { - case 600 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_600); - case 400 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_400); - case 0, 200 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_200); - default -> { - customValueTextView.setText( - String.valueOf( - (double) inputSettingKeysThreshold / MILLISECOND_IN_SECONDS)); - customProgressBar.setProgress(inputSettingKeysThreshold / CUSTOM_PROGRESS_INTERVAL); - customRadioButton.setChecked(true); - } - } - } } diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysController.java b/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysController.java index 9a0f1c0d6e3..69fbe6b7e3a 100644 --- a/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysController.java +++ b/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysController.java @@ -16,7 +16,6 @@ package com.android.settings.inputmethod; -import static android.app.settings.SettingsEnums.ACTION_BOUNCE_KEYS_CUSTOM_VALUE_CHANGE; import static android.app.settings.SettingsEnums.ACTION_BOUNCE_KEYS_DISABLED; import static android.app.settings.SettingsEnums.ACTION_BOUNCE_KEYS_ENABLED; @@ -32,13 +31,13 @@ import androidx.lifecycle.LifecycleObserver; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import com.android.settings.R; import com.android.settingslib.PrimarySwitchPreference; public class KeyboardAccessibilityBounceKeysController extends InputSettingPreferenceController implements LifecycleObserver { public static final int BOUNCE_KEYS_THRESHOLD = 500; + private static final String KEY_TAG = "bounce_keys_dialog_tag"; @Nullable private PrimarySwitchPreference mPrimaryPreference; @@ -46,8 +45,6 @@ public class KeyboardAccessibilityBounceKeysController extends public KeyboardAccessibilityBounceKeysController(@NonNull Context context, @NonNull String key) { super(context, key); - constructDialog(context, R.string.bounce_keys_dialog_title, - R.string.bounce_keys_dialog_subtitle); } @Override @@ -65,12 +62,11 @@ public class KeyboardAccessibilityBounceKeysController extends @Override public boolean handlePreferenceTreeClick(@NonNull Preference preference) { - if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey()) + || mFragmentManager == null) { return false; } - if (mAlertDialog != null) { - mAlertDialog.show(); - } + KeyboardAccessibilityBounceKeysDialogFragment.getInstance().show(mFragmentManager, KEY_TAG); return true; } @@ -87,12 +83,6 @@ public class KeyboardAccessibilityBounceKeysController extends return true; } - @Override - protected void onCustomValueUpdated(int thresholdTimeMillis) { - mMetricsFeatureProvider.action(mContext, ACTION_BOUNCE_KEYS_CUSTOM_VALUE_CHANGE, - thresholdTimeMillis); - } - @Override protected void onInputSettingUpdated() { if (mPrimaryPreference != null) { @@ -111,9 +101,4 @@ public class KeyboardAccessibilityBounceKeysController extends protected void updateInputSettingKeysValue(int thresholdTimeMillis) { InputSettings.setAccessibilityBounceKeysThreshold(mContext, thresholdTimeMillis); } - - @Override - protected int getInputSettingKeysValue() { - return InputSettings.getAccessibilityBounceKeysThreshold(mContext); - } } diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragment.java b/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragment.java new file mode 100644 index 00000000000..5e5bd0661ed --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragment.java @@ -0,0 +1,56 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import static android.app.settings.SettingsEnums.ACTION_BOUNCE_KEYS_CUSTOM_VALUE_CHANGE; + +import android.hardware.input.InputSettings; +import android.os.Bundle; + +import com.android.settings.R; + +public class KeyboardAccessibilityBounceKeysDialogFragment extends + KeyboardAccessibilityKeysDialogFragment { + + static KeyboardAccessibilityBounceKeysDialogFragment getInstance() { + final KeyboardAccessibilityBounceKeysDialogFragment result = + new KeyboardAccessibilityBounceKeysDialogFragment(); + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_TITLE_RES, R.string.bounce_keys_dialog_title); + bundle.putInt(EXTRA_SUBTITLE_RES, R.string.bounce_keys_dialog_subtitle); + bundle.putInt(EXTRA_SEEKBAR_CONTENT_DESCRIPTION, + R.string.input_setting_bounce_keys_seekbar_desc); + result.setArguments(bundle); + return result; + } + + @Override + protected void updateInputSettingKeysValue(int thresholdTimeMillis) { + InputSettings.setAccessibilityBounceKeysThreshold(getContext(), thresholdTimeMillis); + } + + @Override + protected void onCustomValueUpdated(int thresholdTimeMillis) { + mMetricsFeatureProvider.action(getContext(), ACTION_BOUNCE_KEYS_CUSTOM_VALUE_CHANGE, + thresholdTimeMillis); + } + + @Override + protected int getInputSettingKeysValue() { + return InputSettings.getAccessibilityBounceKeysThreshold(getContext()); + } +} diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilityKeysDialogFragment.java b/src/com/android/settings/inputmethod/KeyboardAccessibilityKeysDialogFragment.java new file mode 100644 index 00000000000..252ce54768a --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardAccessibilityKeysDialogFragment.java @@ -0,0 +1,198 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.fragment.app.DialogFragment; + +import com.android.settings.R; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; + +import org.jspecify.annotations.Nullable; + +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFragment { + private static final int CUSTOM_PROGRESS_INTERVAL = 100; + private static final long MILLISECOND_IN_SECONDS = TimeUnit.SECONDS.toMillis(1); + protected static final String EXTRA_TITLE_RES = "extra_title_res"; + protected static final String EXTRA_SUBTITLE_RES = "extra_subtitle_res"; + protected static final String EXTRA_SEEKBAR_CONTENT_DESCRIPTION = + "extra_seekbar_content_description_res"; + + protected final MetricsFeatureProvider mMetricsFeatureProvider; + + public KeyboardAccessibilityKeysDialogFragment() { + mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); + } + + protected void updateInputSettingKeysValue(int thresholdTimeMillis) { + } + + protected void onCustomValueUpdated(int thresholdTimeMillis) { + } + + protected int getInputSettingKeysValue() { + return 0; + } + + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + super.onCreateDialog(savedInstanceState); + int titleRes = getArguments().getInt(EXTRA_TITLE_RES); + int subtitleRes = getArguments().getInt(EXTRA_SUBTITLE_RES); + int seekbarContentDescriptionRes = getArguments().getInt(EXTRA_SEEKBAR_CONTENT_DESCRIPTION); + + Activity activity = getActivity(); + View dialoglayout = + LayoutInflater.from(activity).inflate( + R.layout.dialog_keyboard_a11y_input_setting_keys, null); + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity); + dialogBuilder.setView(dialoglayout); + dialogBuilder.setPositiveButton(android.R.string.ok, + (dialog, which) -> { + RadioGroup radioGroup = + dialoglayout.findViewById( + R.id.input_setting_keys_value_group); + SeekBar seekbar = dialoglayout.findViewById( + R.id.input_setting_keys_value_custom_slider); + RadioButton customRadioButton = dialoglayout.findViewById( + R.id.input_setting_keys_value_custom); + int threshold; + if (customRadioButton.isChecked()) { + threshold = seekbar.getProgress() * CUSTOM_PROGRESS_INTERVAL; + } else { + int checkedRadioButtonId = radioGroup.getCheckedRadioButtonId(); + if (checkedRadioButtonId == R.id.input_setting_keys_value_600) { + threshold = 600; + } else if (checkedRadioButtonId + == R.id.input_setting_keys_value_400) { + threshold = 400; + } else if (checkedRadioButtonId + == R.id.input_setting_keys_value_200) { + threshold = 200; + } else { + threshold = 0; + } + } + updateInputSettingKeysValue(threshold); + onCustomValueUpdated(threshold); + }) + .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()); + AlertDialog accessibilityKeyDialog = dialogBuilder.create(); + accessibilityKeyDialog.setOnShowListener(dialog -> { + RadioGroup cannedValueRadioGroup = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_value_group); + RadioButton customRadioButton = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_value_custom); + TextView customValueTextView = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_value_custom_value); + SeekBar customProgressBar = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_value_custom_slider); + TextView titleTextView = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_dialog_title); + TextView subTitleTextView = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_dialog_subtitle); + titleTextView.setText(titleRes); + subTitleTextView.setText(subtitleRes); + + if (seekbarContentDescriptionRes != 0) { + customProgressBar.setContentDescription( + getContext().getString(seekbarContentDescriptionRes)); + } + customProgressBar.incrementProgressBy(CUSTOM_PROGRESS_INTERVAL); + customProgressBar.setProgress(1); + View customValueView = accessibilityKeyDialog.findViewById( + R.id.input_setting_keys_custom_value_option); + customValueView.setOnClickListener(l -> customRadioButton.performClick()); + customRadioButton.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + cannedValueRadioGroup.clearCheck(); + } + customValueTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE); + customValueTextView.setText( + progressToThresholdInSecond(customProgressBar.getProgress())); + customProgressBar.setVisibility(isChecked ? View.VISIBLE : View.GONE); + buttonView.setChecked(isChecked); + }); + cannedValueRadioGroup.setOnCheckedChangeListener( + (group, checkedId) -> customRadioButton.setChecked(false)); + customProgressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + String threshold = progressToThresholdInSecond(progress); + customValueTextView.setText(threshold); + customProgressBar.setContentDescription(threshold); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + initStateBasedOnThreshold(cannedValueRadioGroup, customRadioButton, customValueTextView, + customProgressBar); + }); + + final Window window = accessibilityKeyDialog.getWindow(); + window.setType(TYPE_SYSTEM_DIALOG); + + return accessibilityKeyDialog; + } + + private String progressToThresholdInSecond(int progress) { + return (double) progress * CUSTOM_PROGRESS_INTERVAL + / MILLISECOND_IN_SECONDS + " " + TimeUnit.SECONDS.name().toLowerCase( + Locale.getDefault()); + } + + private void initStateBasedOnThreshold(RadioGroup cannedValueRadioGroup, + RadioButton customRadioButton, TextView customValueTextView, + SeekBar customProgressBar) { + int inputSettingKeysThreshold = getInputSettingKeysValue(); + switch (inputSettingKeysThreshold) { + case 600 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_600); + case 400 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_400); + case 0, 200 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_200); + default -> { + customValueTextView.setText( + String.valueOf( + (double) inputSettingKeysThreshold / MILLISECOND_IN_SECONDS)); + customProgressBar.setProgress(inputSettingKeysThreshold / CUSTOM_PROGRESS_INTERVAL); + customRadioButton.setChecked(true); + } + } + } +} diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilityMouseKeysController.java b/src/com/android/settings/inputmethod/KeyboardAccessibilityMouseKeysController.java index 2db0e05a2de..a8d9f30de0d 100644 --- a/src/com/android/settings/inputmethod/KeyboardAccessibilityMouseKeysController.java +++ b/src/com/android/settings/inputmethod/KeyboardAccessibilityMouseKeysController.java @@ -35,7 +35,7 @@ import com.android.settingslib.widget.MainSwitchPreference; public class KeyboardAccessibilityMouseKeysController extends InputSettingPreferenceController implements LifecycleObserver { - private static final String KEY_MOUSE_KEY = "accessibility_mouse_keys"; + private static final String KEY_MOUSE_KEY = "keyboard_a11y_page_mouse_keys"; private static final String KEY_MOUSE_KEY_MAIN_PAGE = "mouse_keys_main_switch"; @Nullable diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysController.java b/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysController.java index 451742ff2d4..f3d74075846 100644 --- a/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysController.java +++ b/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysController.java @@ -16,7 +16,6 @@ package com.android.settings.inputmethod; -import static android.app.settings.SettingsEnums.ACTION_SLOW_KEYS_CUSTOM_VALUE_CHANGE; import static android.app.settings.SettingsEnums.ACTION_SLOW_KEYS_DISABLED; import static android.app.settings.SettingsEnums.ACTION_SLOW_KEYS_ENABLED; @@ -32,20 +31,19 @@ import androidx.lifecycle.LifecycleObserver; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import com.android.settings.R; import com.android.settingslib.PrimarySwitchPreference; public class KeyboardAccessibilitySlowKeysController extends InputSettingPreferenceController implements LifecycleObserver { public static final int SLOW_KEYS_THRESHOLD = 500; + private static final String KEY_TAG = "slow_keys_dialog_tag"; @Nullable private PrimarySwitchPreference mPrimarySwitchPreference; public KeyboardAccessibilitySlowKeysController(@NonNull Context context, @NonNull String key) { super(context, key); - constructDialog(context, R.string.slow_keys, R.string.slow_keys_summary); } @Override @@ -90,12 +88,11 @@ public class KeyboardAccessibilitySlowKeysController extends @Override public boolean handlePreferenceTreeClick(@NonNull Preference preference) { - if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey()) + || mFragmentManager == null) { return false; } - if (mAlertDialog != null) { - mAlertDialog.show(); - } + KeyboardAccessibilitySlowKeysDialogFragment.getInstance().show(mFragmentManager, KEY_TAG); return true; } @@ -103,15 +100,4 @@ public class KeyboardAccessibilitySlowKeysController extends protected void updateInputSettingKeysValue(int thresholdTimeMillis) { InputSettings.setAccessibilitySlowKeysThreshold(mContext, thresholdTimeMillis); } - - @Override - protected void onCustomValueUpdated(int thresholdTimeMillis) { - mMetricsFeatureProvider.action(mContext, - ACTION_SLOW_KEYS_CUSTOM_VALUE_CHANGE, thresholdTimeMillis); - } - - @Override - protected int getInputSettingKeysValue() { - return InputSettings.getAccessibilitySlowKeysThreshold(mContext); - } } diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragment.java b/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragment.java new file mode 100644 index 00000000000..e411d7ab5b6 --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragment.java @@ -0,0 +1,56 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import static android.app.settings.SettingsEnums.ACTION_SLOW_KEYS_CUSTOM_VALUE_CHANGE; + +import android.hardware.input.InputSettings; +import android.os.Bundle; + +import com.android.settings.R; + +public class KeyboardAccessibilitySlowKeysDialogFragment extends + KeyboardAccessibilityKeysDialogFragment { + + static KeyboardAccessibilitySlowKeysDialogFragment getInstance() { + final KeyboardAccessibilitySlowKeysDialogFragment result = + new KeyboardAccessibilitySlowKeysDialogFragment(); + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_TITLE_RES, R.string.slow_keys); + bundle.putInt(EXTRA_SUBTITLE_RES, R.string.slow_keys_summary); + bundle.putInt(EXTRA_SEEKBAR_CONTENT_DESCRIPTION, + R.string.input_setting_slow_keys_seekbar_desc); + result.setArguments(bundle); + return result; + } + + @Override + protected void updateInputSettingKeysValue(int thresholdTimeMillis) { + InputSettings.setAccessibilitySlowKeysThreshold(getContext(), thresholdTimeMillis); + } + + @Override + protected void onCustomValueUpdated(int thresholdTimeMillis) { + mMetricsFeatureProvider.action(getContext(), + ACTION_SLOW_KEYS_CUSTOM_VALUE_CHANGE, thresholdTimeMillis); + } + + @Override + protected int getInputSettingKeysValue() { + return InputSettings.getAccessibilitySlowKeysThreshold(getContext()); + } +} diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardA11yFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardA11yFragment.java index 117aadbe4c8..67a808f48fa 100644 --- a/src/com/android/settings/inputmethod/PhysicalKeyboardA11yFragment.java +++ b/src/com/android/settings/inputmethod/PhysicalKeyboardA11yFragment.java @@ -51,6 +51,8 @@ public class PhysicalKeyboardA11yFragment extends DashboardFragment super.onAttach(context); mInputManager = Preconditions.checkNotNull(getActivity() .getSystemService(InputManager.class)); + use(KeyboardAccessibilitySlowKeysController.class).setFragment(this /*parent*/); + use(KeyboardAccessibilityBounceKeysController.class).setFragment(this /*parent*/); } @Override diff --git a/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt b/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt index ec5061ee0fa..c9ba7141b49 100644 --- a/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt +++ b/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt @@ -16,7 +16,6 @@ package com.android.settings.network -import android.Manifest import android.content.Context import android.net.wifi.WifiManager import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED @@ -39,8 +38,7 @@ class AdaptiveConnectivityTogglePreference : override fun getReadPermissions(context: Context) = SettingsSecureStore.getReadPermissions() - override fun getWritePermissions(context: Context) = - SettingsSecureStore.getWritePermissions() and Manifest.permission.NETWORK_SETTINGS + override fun getWritePermissions(context: Context) = SettingsSecureStore.getWritePermissions() override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = ReadWritePermit.ALLOW diff --git a/src/com/android/settings/network/MobileDataPreference.kt b/src/com/android/settings/network/MobileDataPreference.kt index 2f2dbe2c760..80f58e0c704 100644 --- a/src/com/android/settings/network/MobileDataPreference.kt +++ b/src/com/android/settings/network/MobileDataPreference.kt @@ -58,8 +58,6 @@ class MobileDataPreference : override fun getWritePermissions(context: Context) = Permissions.allOf( - // SubscriptionManager.createForAllUserProfiles - Manifest.permission.INTERACT_ACROSS_PROFILES, // TelephonyManager.setDataEnabledForReason Manifest.permission.MODIFY_PHONE_STATE, ) diff --git a/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java b/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java index ebc5575146e..73f80f688c7 100644 --- a/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java +++ b/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java @@ -165,9 +165,9 @@ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragmen } final CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName( info, getContext()); - mNameView.setText(displayName); if (!TextUtils.isEmpty(displayName)) { - mNameView.setSelection(displayName.length()); + mNameView.setSelection(Math.min(displayName.length(), + getResources().getInteger(R.integer.sim_label_max_length))); } mColorSpinner = view.findViewById(R.id.color_spinner); @@ -176,7 +176,7 @@ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragmen mColorSpinner.setAdapter(adapter); mColorSpinner.setSelection(getSimColorIndex(info.getIconTint())); - if(Flags.isDualSimOnboardingEnabled()){ + if (Flags.isDualSimOnboardingEnabled()) { return; } @@ -293,10 +293,10 @@ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragmen } /* - * Get the color index from previous color that defined in Android OS - * (frameworks/base/core/res/res/values/arrays.xml). If can't find the color, continue to look - * for it in the new color plattee. If not, give it the first index. - */ + * Get the color index from previous color that defined in Android OS + * (frameworks/base/core/res/res/values/arrays.xml). If can't find the color, continue to look + * for it in the new color plattee. If not, give it the first index. + */ private int getSimColorIndex(int color) { int index = -1; diff --git a/src/com/android/settings/notification/OWNERS b/src/com/android/settings/notification/OWNERS index 29484c69ff4..424e6cca1c0 100644 --- a/src/com/android/settings/notification/OWNERS +++ b/src/com/android/settings/notification/OWNERS @@ -1,7 +1,6 @@ # Default reviewers for this and subdirectories. -aroederer@google.com beverlyt@google.com dsandler@android.com juliacr@google.com matiashe@google.com -yurilin@google.com \ No newline at end of file +yurilin@google.com diff --git a/src/com/android/settings/safetycenter/LockScreenSafetySource.java b/src/com/android/settings/safetycenter/LockScreenSafetySource.java index 14ad268c3cf..00a4c676a80 100644 --- a/src/com/android/settings/safetycenter/LockScreenSafetySource.java +++ b/src/com/android/settings/safetycenter/LockScreenSafetySource.java @@ -95,8 +95,7 @@ public final class LockScreenSafetySource { new SafetySourceStatus.Builder( context.getString(R.string.unlock_set_unlock_launch_picker_title), lockScreenAllowedByAdmin - ? screenLockPreferenceDetailsUtils.getSummary( - UserHandle.myUserId()) + ? getScreenLockSummary(screenLockPreferenceDetailsUtils) : context.getString(R.string.disabled_by_policy_title), severityLevel) .setPendingIntent(lockScreenAllowedByAdmin ? pendingIntent : null) @@ -114,6 +113,12 @@ public final class LockScreenSafetySource { .setSafetySourceData(context, SAFETY_SOURCE_ID, safetySourceData, safetyEvent); } + private static String getScreenLockSummary( + ScreenLockPreferenceDetailsUtils screenLockPreferenceDetailsUtils) { + String summary = screenLockPreferenceDetailsUtils.getSummary(UserHandle.myUserId()); + return summary != null ? summary : ""; + } + /** Notifies Safety Center of a change in lock screen settings. */ public static void onLockScreenChange(Context context) { setSafetySourceData( diff --git a/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java b/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java index bc38feb20dc..b1685685bb4 100644 --- a/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java +++ b/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java @@ -23,6 +23,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import com.android.internal.app.UnlaunchableAppActivity; @@ -43,6 +44,7 @@ public class ScreenLockPreferenceDetailsUtils { private final int mUserId = UserHandle.myUserId(); private final Context mContext; + @Nullable private final LockPatternUtils mLockPatternUtils; private final int mProfileChallengeUserId; private final UserManager mUm; @@ -85,7 +87,7 @@ public class ScreenLockPreferenceDetailsUtils { * Returns whether the lock pattern is secure. */ public boolean isLockPatternSecure() { - return mLockPatternUtils.isSecure(mUserId); + return mLockPatternUtils != null && mLockPatternUtils.isSecure(mUserId); } /** @@ -148,6 +150,7 @@ public class ScreenLockPreferenceDetailsUtils { // profile with unified challenge on FBE-enabled devices. Otherwise, vold would not be // able to complete the operation due to the lack of (old) encryption key. if (mProfileChallengeUserId != UserHandle.USER_NULL + && mLockPatternUtils != null && !mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileChallengeUserId) && StorageManager.isFileEncrypted()) { if (mUm.isQuietModeEnabled(UserHandle.of(mProfileChallengeUserId))) { @@ -166,8 +169,12 @@ public class ScreenLockPreferenceDetailsUtils { .toIntent(); } + @Nullable @StringRes private Integer getSummaryResId(int userId) { + if (mLockPatternUtils == null) { + return null; + } if (!mLockPatternUtils.isSecure(userId)) { if (userId == mProfileChallengeUserId || mLockPatternUtils.isLockScreenDisabled(userId)) { diff --git a/src/com/android/settings/service/PreferenceService.kt b/src/com/android/settings/service/PreferenceService.kt index 9843847304e..6710f927515 100644 --- a/src/com/android/settings/service/PreferenceService.kt +++ b/src/com/android/settings/service/PreferenceService.kt @@ -16,6 +16,8 @@ package com.android.settings.service +import android.Manifest.permission.WRITE_SYSTEM_PREFERENCES +import android.app.AppOpsManager.OP_WRITE_SYSTEM_PREFERENCES import android.os.Binder import android.os.OutcomeReceiver import android.service.settings.preferences.GetValueRequest @@ -32,6 +34,7 @@ import com.android.settingslib.graph.PreferenceGetterApiHandler import com.android.settingslib.graph.PreferenceGetterFlags import com.android.settingslib.graph.PreferenceSetterApiHandler import com.android.settingslib.ipc.ApiPermissionChecker +import com.android.settingslib.ipc.AppOpApiPermissionChecker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -47,10 +50,15 @@ class PreferenceService : SettingsPreferenceService() { init { val metricsLogger = SettingsRemoteOpMetricsLogger() + // PreferenceService specifies READ_SYSTEM_PREFERENCES permission in AndroidManifest.xml getApiHandler = PreferenceGetterApiHandler(1, ApiPermissionChecker.alwaysAllow(), metricsLogger) setApiHandler = - PreferenceSetterApiHandler(2, ApiPermissionChecker.alwaysAllow(), metricsLogger) + PreferenceSetterApiHandler( + 2, + AppOpApiPermissionChecker(OP_WRITE_SYSTEM_PREFERENCES, WRITE_SYSTEM_PREFERENCES), + metricsLogger, + ) graphApi = GetPreferenceGraphApiHandler(3, ApiPermissionChecker.alwaysAllow(), metricsLogger) } diff --git a/src/com/android/settings/wifi/calling/WifiCallingSettings.java b/src/com/android/settings/wifi/calling/WifiCallingSettings.java index 0c3457ca77f..6110d2844b8 100644 --- a/src/com/android/settings/wifi/calling/WifiCallingSettings.java +++ b/src/com/android/settings/wifi/calling/WifiCallingSettings.java @@ -94,6 +94,7 @@ public class WifiCallingSettings extends SettingsPreferenceFragment @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); if (MobileNetworkUtils.isMobileNetworkUserRestricted(getActivity())) { return new ViewStub(getActivity()); } diff --git a/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java b/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java index d99dd30d419..e1bf87408bd 100644 --- a/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java +++ b/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java @@ -155,7 +155,7 @@ public class ContactsStorageSettingsTest { assertThat(deviceOnlyPreference.getTitle()).isEqualTo("Device only"); assertThat(deviceOnlyPreference.getSummary()).isEqualTo( - "New contacts won't be synced with an account"); + "Contacts may not sync or be available on your other devices"); assertThat(deviceOnlyPreference.getOrder()).isEqualTo(999); assertThat(mContactsStorageSettings.findPreference( PREF_KEY_ACCOUNT_CATEGORY).getTitle()).isEqualTo("Where to save contacts"); @@ -345,7 +345,8 @@ public class ContactsStorageSettingsTest { SelectorWithWidgetPreference simPreference = accountCategory.findPreference( String.valueOf(SIM_ACCOUNT.hashCode())); assertThat(simPreference.getTitle()).isEqualTo("SIM"); - assertThat(simPreference.getSummary()).isEqualTo("SIM"); + assertThat(simPreference.getSummary()).isEqualTo( + "Contacts may not sync or be available on your other devices"); assertThat(simPreference.getIcon()).isNotNull(); assertThat(simPreference.isChecked()).isTrue(); } 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 02825d2aa50..47ba523c7b2 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java @@ -135,16 +135,20 @@ public class FingerprintSettingsFragmentTest { @Mock private Vibrator mVibrator; + private FingerprintSettingsFeatureProvider mFingerprintSettingsFeatureProvider; + + private FakeFeatureFactory mFakeFeatureFactory; private FingerprintAuthenticateSidecar mFingerprintAuthenticateSidecar; private FingerprintRemoveSidecar mFingerprintRemoveSidecar; @Before public void setUp() { ShadowUtils.setFingerprintManager(mFingerprintManager); - FakeFeatureFactory.setupForTest(); + mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); mContext = spy(ApplicationProvider.getApplicationContext()); mFragment = spy(new FingerprintSettingsFragment()); + mFingerprintSettingsFeatureProvider = new FingerprintSettingsFeatureProvider(); doReturn(mContext).when(mFragment).getContext(); doReturn(mBiometricManager).when(mContext).getSystemService(BiometricManager.class); doReturn(true).when(mFingerprintManager).isHardwareDetected(); @@ -152,6 +156,9 @@ public class FingerprintSettingsFragmentTest { when(mBiometricManager.canAuthenticate(PRIMARY_USER_ID, BiometricManager.Authenticators.IDENTITY_CHECK)) .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE); + when(mFakeFeatureFactory.getFingerprintFeatureProvider() + .getFingerprintSettingsFeatureProvider()) + .thenReturn(mFingerprintSettingsFeatureProvider); } @After diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java index 4c20a8888bc..a7523efcf96 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java @@ -98,30 +98,44 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends @Test public void isAvailable_notHearingDevice_returnFalse() { when(mCachedDevice.isHearingDevice()).thenReturn(false); + when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile)); + when(mUiController.isAmbientControlAvailable()).thenReturn(true); assertThat(mController.isAvailable()).isFalse(); } @Test - public void isAvailable_isHearingDeviceAndNotSupportVcp_returnFalse() { + public void isAvailable_notSupportVcp_returnFalse() { when(mCachedDevice.isHearingDevice()).thenReturn(true); when(mCachedDevice.getProfiles()).thenReturn(List.of()); + when(mUiController.isAmbientControlAvailable()).thenReturn(true); assertThat(mController.isAvailable()).isFalse(); } @Test - public void isAvailable_isHearingDeviceAndSupportVcp_returnTrue() { + public void isAvailable_noValidAmbientControlPoint_returnFalse() { when(mCachedDevice.isHearingDevice()).thenReturn(true); when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile)); + when(mUiController.isAmbientControlAvailable()).thenReturn(false); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_isHearingDevice_supportVcp_validAmbientControl_returnTrue() { + when(mCachedDevice.isHearingDevice()).thenReturn(true); + when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile)); + when(mUiController.isAmbientControlAvailable()).thenReturn(true); assertThat(mController.isAvailable()).isTrue(); } @Test - public void refresh_isHearingDeviceAndNotSupportVcp_verifyUiControllerNoRefresh() { - when(mCachedDevice.isHearingDevice()).thenReturn(true); - when(mCachedDevice.getProfiles()).thenReturn(List.of()); + public void refresh_notHearingDevice_verifyUiControllerNotRefresh() { + when(mCachedDevice.isHearingDevice()).thenReturn(false); + when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile)); + when(mUiController.isAmbientControlAvailable()).thenReturn(true); mController.refresh(); @@ -129,9 +143,32 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends } @Test - public void refresh_isHearingDeviceAndSupportVcp_verifyUiControllerRefresh() { + public void refresh_notSupportVcp_verifyUiControllerNotRefresh() { + when(mCachedDevice.isHearingDevice()).thenReturn(true); + when(mCachedDevice.getProfiles()).thenReturn(List.of()); + when(mUiController.isAmbientControlAvailable()).thenReturn(true); + + mController.refresh(); + + verify(mUiController, never()).refresh(); + } + + @Test + public void refresh_noValidAmbientControl_verifyUiControllerNotRefresh() { when(mCachedDevice.isHearingDevice()).thenReturn(true); when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile)); + when(mUiController.isAmbientControlAvailable()).thenReturn(false); + + mController.refresh(); + + verify(mUiController, never()).refresh(); + } + + @Test + public void refresh_isHearingDevice_supportVcp_validAmbientControl_verifyUiControllerRefresh() { + when(mCachedDevice.isHearingDevice()).thenReturn(true); + when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile)); + when(mUiController.isAmbientControlAvailable()).thenReturn(true); mController.refresh(); diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeControllerTest.java index 20105022ef1..3cdb302d673 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeControllerTest.java @@ -18,7 +18,6 @@ package com.android.settings.bluetooth; import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER; -import static android.media.audio.Flags.automaticBtDeviceType; import static com.google.common.truth.Truth.assertThat; @@ -98,12 +97,7 @@ public class BluetoothDetailsAudioDeviceTypeControllerTest extends @Test public void createAudioDeviceTypePreference_btDeviceIsCategorized_checkSelection() { int deviceType = AUDIO_DEVICE_CATEGORY_SPEAKER; - if (automaticBtDeviceType()) { - when(mAudioManager.getBluetoothAudioDeviceCategory(MAC_ADDRESS)).thenReturn(deviceType); - } else { - when(mAudioManager.getBluetoothAudioDeviceCategory_legacy(MAC_ADDRESS, /*isBle=*/ - true)).thenReturn(deviceType); - } + when(mAudioManager.getBluetoothAudioDeviceCategory(MAC_ADDRESS)).thenReturn(deviceType); mController.createAudioDeviceTypePreference(mContext); mAudioDeviceTypePref = mController.getAudioDeviceTypePreference(); @@ -118,12 +112,7 @@ public class BluetoothDetailsAudioDeviceTypeControllerTest extends mController.onPreferenceChange(mAudioDeviceTypePref, Integer.toString(deviceType)); - if (automaticBtDeviceType()) { - verify(mAudioManager).setBluetoothAudioDeviceCategory(eq(MAC_ADDRESS), - eq(AUDIO_DEVICE_CATEGORY_SPEAKER)); - } else { - verify(mAudioManager).setBluetoothAudioDeviceCategory_legacy(eq(MAC_ADDRESS), eq(true), - eq(AUDIO_DEVICE_CATEGORY_SPEAKER)); - } + verify(mAudioManager).setBluetoothAudioDeviceCategory(eq(MAC_ADDRESS), + eq(AUDIO_DEVICE_CATEGORY_SPEAKER)); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java index b4c0a2047be..145a5c7a549 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java @@ -60,6 +60,7 @@ import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.androidx.fragment.FragmentController; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; @@ -263,6 +264,18 @@ public class AudioSharingDialogFragmentTest { assertThat(dialog).isNotNull(); ImageView image = dialog.findViewById(R.id.description_image); assertThat(image).isNotNull(); + TextView text = dialog.findViewById(R.id.description_text); + assertThat(text).isNotNull(); + assertThat(METADATA).isNotNull(); + assertThat(text.getText().toString()).isEqualTo( + mParent.getString(R.string.audio_sharing_dialog_qr_code_content, + METADATA.getBroadcastName(), new String( + METADATA.getBroadcastCode(), + StandardCharsets.UTF_8))); + TextView textBottom = dialog.findViewById(R.id.description_text_2); + assertThat(textBottom).isNotNull(); + assertThat(textBottom.getText().toString()).isEqualTo( + mParent.getString(R.string.audio_sharing_dialog_pair_new_device_content)); Button cancelBtn = dialog.findViewById(R.id.negative_btn); assertThat(cancelBtn).isNotNull(); cancelBtn.performClick(); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java index bfb474b711f..a0e971b1d45 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java @@ -40,6 +40,7 @@ import android.app.NotificationManager; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; import android.content.Context; @@ -86,6 +87,7 @@ import org.robolectric.shadow.api.Shadow; import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; +import java.util.List; import java.util.Set; @RunWith(RobolectricTestRunner.class) @@ -99,11 +101,13 @@ import java.util.Set; public class AudioStreamMediaServiceTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final String DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; private static final String CHANNEL_ID = "bluetooth_notification_channel"; private static final String DEVICE_NAME = "name"; @Mock private Resources mResources; @Mock private LocalBluetoothManager mLocalBtManager; @Mock private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; + @Mock private BluetoothLeBroadcastReceiveState mBroadcastReceiveState; @Mock private AudioStreamsHelper mAudioStreamsHelper; @Mock private NotificationManager mNotificationManager; @Mock private MediaSessionManager mMediaSessionManager; @@ -304,6 +308,63 @@ public class AudioStreamMediaServiceTest { verify(mAudioStreamMediaService).stopSelf(); } + @Test + public void assistantCallback_onReceiveStateChanged_connected_doNothing() { + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + + mAudioStreamMediaService.onCreate(); + mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0); + + assertThat(mAudioStreamMediaService.mBroadcastAssistantCallback).isNotNull(); + List bisSyncState = new ArrayList<>(); + bisSyncState.add(1L); + when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState); + when(mDevice.getAddress()).thenReturn(DEVICE_ADDRESS); + when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mDevice); + + mAudioStreamMediaService.mBroadcastAssistantCallback.onReceiveStateChanged( + mDevice, /* sourceId= */ 0, /* state= */ mBroadcastReceiveState); + + verify(mNotificationManager, never()).notify(anyInt(), any()); + } + + @Test + public void assistantCallback_onReceiveStateChanged_hysteresis_updateNotification() { + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + + mAudioStreamMediaService.onCreate(); + mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0); + + assertThat(mAudioStreamMediaService.mBroadcastAssistantCallback).isNotNull(); + when(mBroadcastReceiveState.getBisSyncState()).thenReturn(new ArrayList<>()); + when(mDevice.getAddress()).thenReturn(DEVICE_ADDRESS); + when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mDevice); + + mAudioStreamMediaService.mBroadcastAssistantCallback.onReceiveStateChanged( + mDevice, /* sourceId= */ 0, /* state= */ mBroadcastReceiveState); + + verify(mNotificationManager).notify(anyInt(), any()); + } + + @Test + public void assistantCallback_onReceiveStateChanged_hysteresis_flagOff_doNothing() { + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + + mAudioStreamMediaService.onCreate(); + mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0); + + assertThat(mAudioStreamMediaService.mBroadcastAssistantCallback).isNotNull(); + mAudioStreamMediaService.mBroadcastAssistantCallback.onReceiveStateChanged( + mDevice, /* sourceId= */ 0, /* state= */ mBroadcastReceiveState); + + verify(mBroadcastReceiveState, never()).getBisSyncState(); + verify(mBroadcastReceiveState, never()).getSourceDevice(); + verify(mNotificationManager, never()).notify(anyInt(), any()); + } + @Test public void bluetoothCallback_onBluetoothOff_stopSelf() { mAudioStreamMediaService.onCreate(); diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysControllerTest.java index b385b2f32a5..d9aa79c19b7 100644 --- a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysControllerTest.java +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysControllerTest.java @@ -18,6 +18,9 @@ package com.android.settings.inputmethod; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; @@ -25,12 +28,12 @@ import android.hardware.input.InputSettings; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; -import android.widget.RadioGroup; -import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import androidx.preference.Preference; -import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.keyboard.Flags; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; @@ -45,10 +48,13 @@ import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowLooper; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; @RunWith(RobolectricTestRunner.class) @Config(shadows = { + KeyboardAccessibilityBounceKeysControllerTest + .ShadowKeyboardAccessibilityBounceKeysDialogFragment.class, com.android.settings.testutils.shadow.ShadowFragment.class, ShadowAlertDialogCompat.class, }) @@ -60,6 +66,15 @@ public class KeyboardAccessibilityBounceKeysControllerTest { private static final String PREFERENCE_KEY = "keyboard_a11y_page_bounce_keys"; @Mock private Preference mPreference; + @Mock + private Fragment mFragment; + @Mock + private FragmentManager mFragmentManager; + @Mock + private FragmentTransaction mFragmentTransaction; + @Mock + private KeyboardAccessibilityBounceKeysDialogFragment + mKeyboardAccessibilityBounceKeysDialogFragment; private Context mContext; private KeyboardAccessibilityBounceKeysController mKeyboardAccessibilityBounceKeysController; @@ -71,6 +86,11 @@ public class KeyboardAccessibilityBounceKeysControllerTest { mContext, PREFERENCE_KEY); when(mPreference.getKey()).thenReturn(PREFERENCE_KEY); + when(mFragment.getParentFragmentManager()).thenReturn(mFragmentManager); + when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction); + mKeyboardAccessibilityBounceKeysController.setFragment(mFragment); + ShadowKeyboardAccessibilityBounceKeysDialogFragment.setInstance( + mKeyboardAccessibilityBounceKeysDialogFragment); } @Test @@ -107,23 +127,25 @@ public class KeyboardAccessibilityBounceKeysControllerTest { public void handlePreferenceTreeClick_dialogShows() { mKeyboardAccessibilityBounceKeysController.handlePreferenceTreeClick(mPreference); - AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); - - assertThat(alertDialog.isShowing()).isTrue(); + verify(mKeyboardAccessibilityBounceKeysDialogFragment).show(any(FragmentManager.class), + anyString()); } - @Test - public void handlePreferenceTreeClick_performClickOn200_updatesBounceKeysThreshold() { - mKeyboardAccessibilityBounceKeysController.handlePreferenceTreeClick(mPreference); - AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); - RadioGroup radioGroup = alertDialog.findViewById(R.id.input_setting_keys_value_group); - radioGroup.check(R.id.input_setting_keys_value_200); + /** + * Note: Actually, shadow of KeyboardAccessibilitySlowKeysDialogFragment will not be used. + * Instance that returned with {@link #getInstance} should be set with {@link #setInstance} + */ + @Implements(KeyboardAccessibilityBounceKeysDialogFragment.class) + public static class ShadowKeyboardAccessibilityBounceKeysDialogFragment { + static KeyboardAccessibilityBounceKeysDialogFragment sInstance = null; - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); - ShadowLooper.idleMainLooper(); + @Implementation + protected static KeyboardAccessibilityBounceKeysDialogFragment getInstance() { + return sInstance; + } - assertThat(alertDialog.isShowing()).isFalse(); - int threshold = InputSettings.getAccessibilityBounceKeysThreshold(mContext); - assertThat(threshold).isEqualTo(200); + public static void setInstance(KeyboardAccessibilityBounceKeysDialogFragment instance) { + sInstance = instance; + } } } diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragmentTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragmentTest.java new file mode 100644 index 00000000000..8e5f84e2191 --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilityBounceKeysDialogFragmentTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import static com.android.settings.inputmethod.KeyboardAccessibilityKeysDialogFragment.EXTRA_SUBTITLE_RES; +import static com.android.settings.inputmethod.KeyboardAccessibilityKeysDialogFragment.EXTRA_TITLE_RES; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AlertDialog; +import android.hardware.input.InputSettings; +import android.os.Bundle; +import android.widget.RadioGroup; + +import androidx.fragment.app.testing.FragmentScenario; +import androidx.lifecycle.Lifecycle; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowLooper; + +@RunWith(RobolectricTestRunner.class) +public class KeyboardAccessibilityBounceKeysDialogFragmentTest { + private AlertDialog mAlertDialog; + + @Before + public void setUp() { + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_TITLE_RES, R.string.bounce_keys_dialog_title); + bundle.putInt(EXTRA_SUBTITLE_RES, R.string.bounce_keys_dialog_subtitle); + + FragmentScenario mFragmentScenario = + FragmentScenario.launch( + KeyboardAccessibilityBounceKeysDialogFragment.class, + bundle, + R.style.Theme_AlertDialog_SettingsLib, + Lifecycle.State.INITIALIZED); + mFragmentScenario.moveToState(Lifecycle.State.RESUMED); + + mFragmentScenario.onFragment(fragment -> { + assertThat(fragment.getDialog()).isNotNull(); + assertThat(fragment.requireDialog().isShowing()).isTrue(); + assertThat(fragment.requireDialog()).isInstanceOf(AlertDialog.class); + mAlertDialog = (AlertDialog) fragment.requireDialog(); + }); + } + + @Test + public void handlePreferenceTreeClick_performClickOn200_updatesBounceKeysThreshold() { + assertThat(mAlertDialog.isShowing()).isTrue(); + RadioGroup radioGroup = mAlertDialog.findViewById(R.id.input_setting_keys_value_group); + radioGroup.check(R.id.input_setting_keys_value_200); + + mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); + ShadowLooper.idleMainLooper(); + + assertThat(mAlertDialog.isShowing()).isFalse(); + int threshold = InputSettings.getAccessibilityBounceKeysThreshold( + ApplicationProvider.getApplicationContext()); + assertThat(threshold).isEqualTo(200); + } +} diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysControllerTest.java index 9f82b759cdc..137f15ba9d8 100644 --- a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysControllerTest.java +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysControllerTest.java @@ -18,6 +18,9 @@ package com.android.settings.inputmethod; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; @@ -25,12 +28,12 @@ import android.hardware.input.InputSettings; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; -import android.widget.RadioGroup; -import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import androidx.preference.Preference; -import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.keyboard.Flags; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; @@ -45,10 +48,13 @@ import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowLooper; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; @RunWith(RobolectricTestRunner.class) @Config(shadows = { + KeyboardAccessibilitySlowKeysControllerTest + .ShadowKeyboardAccessibilitySlowKeysDialogFragment.class, com.android.settings.testutils.shadow.ShadowFragment.class, ShadowAlertDialogCompat.class, }) @@ -60,6 +66,15 @@ public class KeyboardAccessibilitySlowKeysControllerTest { private static final String PREFERENCE_KEY = "keyboard_a11y_page_slow_keys"; @Mock private Preference mPreference; + @Mock + private Fragment mFragment; + @Mock + private FragmentManager mFragmentManager; + @Mock + private FragmentTransaction mFragmentTransaction; + @Mock + private KeyboardAccessibilitySlowKeysDialogFragment + mKeyboardAccessibilitySlowKeysDialogFragment; private Context mContext; private KeyboardAccessibilitySlowKeysController mKeyboardAccessibilitySlowKeysController; @@ -71,6 +86,11 @@ public class KeyboardAccessibilitySlowKeysControllerTest { mContext, PREFERENCE_KEY); when(mPreference.getKey()).thenReturn(PREFERENCE_KEY); + when(mFragment.getParentFragmentManager()).thenReturn(mFragmentManager); + when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction); + mKeyboardAccessibilitySlowKeysController.setFragment(mFragment); + ShadowKeyboardAccessibilitySlowKeysDialogFragment.setInstance( + mKeyboardAccessibilitySlowKeysDialogFragment); } @Test @@ -107,23 +127,25 @@ public class KeyboardAccessibilitySlowKeysControllerTest { public void handlePreferenceTreeClick_dialogShows() { mKeyboardAccessibilitySlowKeysController.handlePreferenceTreeClick(mPreference); - AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); - - assertThat(alertDialog.isShowing()).isTrue(); + verify(mKeyboardAccessibilitySlowKeysDialogFragment).show(any(FragmentManager.class), + anyString()); } - @Test - public void handlePreferenceTreeClick_performClickOn200_updatesSlowKeysThreshold() { - mKeyboardAccessibilitySlowKeysController.handlePreferenceTreeClick(mPreference); - AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); - RadioGroup radioGroup = alertDialog.findViewById(R.id.input_setting_keys_value_group); - radioGroup.check(R.id.input_setting_keys_value_200); + /** + * Note: Actually, shadow of KeyboardAccessibilitySlowKeysDialogFragment will not be used. + * Instance that returned with {@link #getInstance} should be set with {@link #setInstance} + */ + @Implements(KeyboardAccessibilitySlowKeysDialogFragment.class) + public static class ShadowKeyboardAccessibilitySlowKeysDialogFragment { + static KeyboardAccessibilitySlowKeysDialogFragment sInstance = null; - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); - ShadowLooper.idleMainLooper(); + @Implementation + protected static KeyboardAccessibilitySlowKeysDialogFragment getInstance() { + return sInstance; + } - assertThat(alertDialog.isShowing()).isFalse(); - int threshold = InputSettings.getAccessibilitySlowKeysThreshold(mContext); - assertThat(threshold).isEqualTo(200); + public static void setInstance(KeyboardAccessibilitySlowKeysDialogFragment instance) { + sInstance = instance; + } } } diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragmentTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragmentTest.java new file mode 100644 index 00000000000..3a3010e7293 --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardAccessibilitySlowKeysDialogFragmentTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import static com.android.settings.inputmethod.KeyboardAccessibilityKeysDialogFragment.EXTRA_SUBTITLE_RES; +import static com.android.settings.inputmethod.KeyboardAccessibilityKeysDialogFragment.EXTRA_TITLE_RES; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AlertDialog; +import android.hardware.input.InputSettings; +import android.os.Bundle; +import android.widget.RadioGroup; + +import androidx.fragment.app.testing.FragmentScenario; +import androidx.lifecycle.Lifecycle; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowLooper; + +@RunWith(RobolectricTestRunner.class) +public class KeyboardAccessibilitySlowKeysDialogFragmentTest { + private AlertDialog mAlertDialog; + + @Before + public void setUp() { + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_TITLE_RES, R.string.slow_keys); + bundle.putInt(EXTRA_SUBTITLE_RES, R.string.slow_keys_summary); + + FragmentScenario mFragmentScenario = + FragmentScenario.launch( + KeyboardAccessibilitySlowKeysDialogFragment.class, + bundle, + R.style.Theme_AlertDialog_SettingsLib, + Lifecycle.State.INITIALIZED); + mFragmentScenario.moveToState(Lifecycle.State.RESUMED); + + mFragmentScenario.onFragment(fragment -> { + assertThat(fragment.getDialog()).isNotNull(); + assertThat(fragment.requireDialog().isShowing()).isTrue(); + assertThat(fragment.requireDialog()).isInstanceOf(AlertDialog.class); + mAlertDialog = (AlertDialog) fragment.requireDialog(); + }); + } + + @Test + public void handlePreferenceTreeClick_performClickOn200_updatesSlowKeysThreshold() { + assertThat(mAlertDialog.isShowing()).isTrue(); + RadioGroup radioGroup = mAlertDialog.findViewById(R.id.input_setting_keys_value_group); + radioGroup.check(R.id.input_setting_keys_value_200); + + mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); + ShadowLooper.idleMainLooper(); + + assertThat(mAlertDialog.isShowing()).isFalse(); + int threshold = InputSettings.getAccessibilitySlowKeysThreshold( + ApplicationProvider.getApplicationContext()); + assertThat(threshold).isEqualTo(200); + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java index c9cf5a2a852..c6bdebdcc14 100644 --- a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java @@ -56,6 +56,7 @@ import com.android.wifitrackerlib.WifiEntry; import com.android.wifitrackerlib.WifiEntry.ConnectedState; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -72,6 +73,7 @@ import org.robolectric.shadows.ShadowBinder; import java.util.ArrayList; import java.util.List; +@Ignore("b/394813533") @Deprecated(forRemoval = true) @RunWith(RobolectricTestRunner.class) @Config(shadows = { diff --git a/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java b/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java index abc982db33c..25358708043 100644 --- a/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java +++ b/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java @@ -78,6 +78,7 @@ public class ScreenLockPreferenceDetailsUtilsTest { private StorageManager mStorageManager; private Context mContext; + private FakeFeatureFactory mFeatureFactory; private ScreenLockPreferenceDetailsUtils mScreenLockPreferenceDetailsUtils; @@ -95,8 +96,8 @@ public class ScreenLockPreferenceDetailsUtilsTest { doNothing().when(mContext).startActivity(any()); when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{}); - final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); - when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) + mFeatureFactory = FakeFeatureFactory.setupForTest(); + when(mFeatureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) .thenReturn(mLockPatternUtils); mScreenLockPreferenceDetailsUtils = new ScreenLockPreferenceDetailsUtils(mContext); @@ -230,6 +231,15 @@ public class ScreenLockPreferenceDetailsUtilsTest { assertNull(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)); } + @Test + public void getSummary_noLockPatternUtils_shouldReturnNull() { + when(mFeatureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) + .thenReturn(null); + mScreenLockPreferenceDetailsUtils = new ScreenLockPreferenceDetailsUtils(mContext); + + assertNull(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)); + } + @Test public void isPasswordQualityManaged_withoutAdmin_shouldReturnFalse() { final RestrictedLockUtils.EnforcedAdmin admin = null; @@ -274,6 +284,15 @@ public class ScreenLockPreferenceDetailsUtilsTest { assertThat(mScreenLockPreferenceDetailsUtils.isLockPatternSecure()).isFalse(); } + @Test + public void isLockPatternSecure_noLockPatterUtils_shouldReturnFalse() { + when(mFeatureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) + .thenReturn(null); + mScreenLockPreferenceDetailsUtils = new ScreenLockPreferenceDetailsUtils(mContext); + + assertThat(mScreenLockPreferenceDetailsUtils.isLockPatternSecure()).isFalse(); + } + @Test @RequiresFlagsEnabled(Flags.FLAG_BIOMETRIC_ONBOARDING_EDUCATION) public void shouldShowGearMenu_patternIsSecure_flagOn_shouldReturnFalse() { @@ -341,6 +360,20 @@ public class ScreenLockPreferenceDetailsUtilsTest { ChooseLockGeneric.ChooseLockGenericFragment.class.getName()); } + @Test + public void getLaunchChooseLockGenericFragmentIntent_noLockPatternUtils_returnsIntent() { + when(mFeatureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) + .thenReturn(null); + mScreenLockPreferenceDetailsUtils = new ScreenLockPreferenceDetailsUtils(mContext); + when(mUserManager.isQuietModeEnabled(any())).thenReturn(false); + + final Intent intent = mScreenLockPreferenceDetailsUtils + .getLaunchChooseLockGenericFragmentIntent(SOURCE_METRICS_CATEGORY); + + assertFragmentLaunchIntent(intent, + ChooseLockGeneric.ChooseLockGenericFragment.class.getName()); + } + private void whenConfigShowUnlockSetOrChangeIsEnabled(boolean enabled) { final int resId = ResourcesUtils.getResourcesId( ApplicationProvider.getApplicationContext(), "bool",