From 7e8e683e6017068cfd35b29417d22e7946849fa4 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Thu, 9 Jan 2025 14:38:11 +0800 Subject: [PATCH 1/8] Migrate to AppStorageRepository Bug: 321861088 Flag: EXEMPT bug fix Test: manual - on All apps and App info Test: atest AppStorageSizesControllerTest Test: atest AppStoragePreferenceTest Change-Id: I3143de720493cb09854621b115d07e76bebf6699 --- .../AppStorageSizesController.java | 15 +++++------- .../spa/app/storage/StorageAppList.kt | 8 ++++--- .../AppStorageSizesControllerTest.java | 24 +++++++++---------- .../app/appinfo/AppStoragePreferenceTest.kt | 5 ++-- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/com/android/settings/applications/AppStorageSizesController.java b/src/com/android/settings/applications/AppStorageSizesController.java index 43734b272c4..7cf86f8d72d 100644 --- a/src/com/android/settings/applications/AppStorageSizesController.java +++ b/src/com/android/settings/applications/AppStorageSizesController.java @@ -17,7 +17,6 @@ package com.android.settings.applications; import android.content.Context; -import android.text.format.Formatter; import androidx.annotation.Nullable; import androidx.annotation.StringRes; @@ -25,6 +24,7 @@ import androidx.preference.Preference; import com.android.internal.util.Preconditions; import com.android.settingslib.applications.StorageStatsSource; +import com.android.settingslib.spaprivileged.model.app.AppStorageRepositoryImpl; /** * Handles setting the sizes for the app info screen. @@ -70,27 +70,28 @@ public class AppStorageSizesController { mCacheSize.setSummary(errorRes); mTotalSize.setSummary(errorRes); } else { + var appStorageRepository = new AppStorageRepositoryImpl(context); long codeSize = mLastResult.getCodeBytes(); long dataSize = mDataCleared ? 0 : mLastResult.getDataBytes() - mLastResult.getCacheBytes(); if (mLastCodeSize != codeSize) { mLastCodeSize = codeSize; - mAppSize.setSummary(getSizeStr(context, codeSize)); + mAppSize.setSummary(appStorageRepository.formatSizeBytes(codeSize)); } if (mLastDataSize != dataSize) { mLastDataSize = dataSize; - mDataSize.setSummary(getSizeStr(context, dataSize)); + mDataSize.setSummary(appStorageRepository.formatSizeBytes(dataSize)); } long cacheSize = (mDataCleared || mCachedCleared) ? 0 : mLastResult.getCacheBytes(); if (mLastCacheSize != cacheSize) { mLastCacheSize = cacheSize; - mCacheSize.setSummary(getSizeStr(context, cacheSize)); + mCacheSize.setSummary(appStorageRepository.formatSizeBytes(cacheSize)); } long totalSize = codeSize + dataSize + cacheSize; if (mLastTotalSize != totalSize) { mLastTotalSize = totalSize; - mTotalSize.setSummary(getSizeStr(context, totalSize)); + mTotalSize.setSummary(appStorageRepository.formatSizeBytes(totalSize)); } } } @@ -129,10 +130,6 @@ public class AppStorageSizesController { return mLastResult; } - private String getSizeStr(Context context, long size) { - return Formatter.formatFileSize(context, size); - } - public static class Builder { private Preference mTotalSize; private Preference mAppSize; diff --git a/src/com/android/settings/spa/app/storage/StorageAppList.kt b/src/com/android/settings/spa/app/storage/StorageAppList.kt index c33de33db59..be145e021ca 100644 --- a/src/com/android/settings/spa/app/storage/StorageAppList.kt +++ b/src/com/android/settings/spa/app/storage/StorageAppList.kt @@ -34,12 +34,12 @@ import com.android.settingslib.spa.framework.util.mapItem import com.android.settingslib.spaprivileged.model.app.AppEntry import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.AppStorageRepositoryImpl import com.android.settingslib.spaprivileged.template.app.AppList import com.android.settingslib.spaprivileged.template.app.AppListInput import com.android.settingslib.spaprivileged.template.app.AppListItem import com.android.settingslib.spaprivileged.template.app.AppListItemModel import com.android.settingslib.spaprivileged.template.app.AppListPage -import com.android.settingslib.spaprivileged.template.app.calculateSizeBytes import com.android.settingslib.spaprivileged.template.app.getStorageSize import kotlinx.coroutines.flow.Flow @@ -109,9 +109,11 @@ class StorageAppListModel( getStorageSize() } ) : AppListModel { + private val appStorageRepository = AppStorageRepositoryImpl(context) + override fun transform(userIdFlow: Flow, appListFlow: Flow>) = - appListFlow.mapItem { - AppRecordWithSize(it, it.calculateSizeBytes(context) ?: 0L) + appListFlow.mapItem { app -> + AppRecordWithSize(app, appStorageRepository.calculateSizeBytes(app) ?: 0L) } override fun filter( diff --git a/tests/robotests/src/com/android/settings/applications/AppStorageSizesControllerTest.java b/tests/robotests/src/com/android/settings/applications/AppStorageSizesControllerTest.java index 49d4aac9780..bd2579e6482 100644 --- a/tests/robotests/src/com/android/settings/applications/AppStorageSizesControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/AppStorageSizesControllerTest.java @@ -83,10 +83,10 @@ public class AppStorageSizesControllerTest { mController.setResult(result); mController.updateUi(mContext); - assertThat(mAppPreference.getSummary()).isEqualTo("1 B"); - assertThat(mCachePreference.getSummary()).isEqualTo("10 B"); - assertThat(mDataPreference.getSummary()).isEqualTo("90 B"); - assertThat(mTotalPreference.getSummary()).isEqualTo("101 B"); + assertThat(mAppPreference.getSummary()).isEqualTo("1 byte"); + assertThat(mCachePreference.getSummary()).isEqualTo("10 byte"); + assertThat(mDataPreference.getSummary()).isEqualTo("90 byte"); + assertThat(mTotalPreference.getSummary()).isEqualTo("101 byte"); } @Test @@ -101,10 +101,10 @@ public class AppStorageSizesControllerTest { mController.setCacheCleared(true); mController.updateUi(mContext); - assertThat(mAppPreference.getSummary()).isEqualTo("1 B"); - assertThat(mCachePreference.getSummary()).isEqualTo("0 B"); - assertThat(mDataPreference.getSummary()).isEqualTo("90 B"); - assertThat(mTotalPreference.getSummary()).isEqualTo("91 B"); + assertThat(mAppPreference.getSummary()).isEqualTo("1 byte"); + assertThat(mCachePreference.getSummary()).isEqualTo("0 byte"); + assertThat(mDataPreference.getSummary()).isEqualTo("90 byte"); + assertThat(mTotalPreference.getSummary()).isEqualTo("91 byte"); } @Test @@ -119,9 +119,9 @@ public class AppStorageSizesControllerTest { mController.setDataCleared(true); mController.updateUi(mContext); - assertThat(mAppPreference.getSummary()).isEqualTo("1 B"); - assertThat(mCachePreference.getSummary()).isEqualTo("0 B"); - assertThat(mDataPreference.getSummary()).isEqualTo("0 B"); - assertThat(mTotalPreference.getSummary()).isEqualTo("1 B"); + assertThat(mAppPreference.getSummary()).isEqualTo("1 byte"); + assertThat(mCachePreference.getSummary()).isEqualTo("0 byte"); + assertThat(mDataPreference.getSummary()).isEqualTo("0 byte"); + assertThat(mTotalPreference.getSummary()).isEqualTo("1 byte"); } } diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt index ebbd57a6bc8..5a29a7d22b1 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt @@ -89,7 +89,7 @@ class AppStoragePreferenceTest { composeTestRule.onNodeWithText(context.getString(R.string.storage_settings_for_app)) .assertIsDisplayed() - composeTestRule.waitUntilExists(hasText("120 B used in internal storage")) + composeTestRule.waitUntilExists(hasText("120 byte used in internal storage")) } @Test @@ -104,7 +104,7 @@ class AppStoragePreferenceTest { composeTestRule.onNodeWithText(context.getString(R.string.storage_settings_for_app)) .assertIsDisplayed() - composeTestRule.waitUntilExists(hasText("120 B used in external storage")) + composeTestRule.waitUntilExists(hasText("120 byte used in external storage")) } private fun setContent(app: ApplicationInfo) { @@ -122,7 +122,6 @@ class AppStoragePreferenceTest { private val STATS = StorageStats().apply { codeBytes = 100 dataBytes = 20 - cacheBytes = 3 } } } From d05145553b487e00ed30377edaaf4b5eb7af4eb5 Mon Sep 17 00:00:00 2001 From: shaoweishen Date: Thu, 9 Jan 2025 08:40:24 +0000 Subject: [PATCH 2/8] [Physical Keyboard] Fix bugs adjust properties of dialog to match requirements Bug: 374035349 Bug: 374030364 Test: atest SettingsRoboTests Flag: com.android.settings.keyboard.keyboard_and_touchpad_a11y_new_page_enabled Change-Id: I4ee77a7ea249e2bd00ef5a05270c1ab3f257f547 --- ...dialog_keyboard_a11y_input_setting_keys.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/res/layout/dialog_keyboard_a11y_input_setting_keys.xml b/res/layout/dialog_keyboard_a11y_input_setting_keys.xml index dfa3c46603d..d826fee96fd 100644 --- a/res/layout/dialog_keyboard_a11y_input_setting_keys.xml +++ b/res/layout/dialog_keyboard_a11y_input_setting_keys.xml @@ -55,7 +55,7 @@ android:text="@string/input_setting_keys_dialog_option_200" android:paddingStart="12dp" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="48dp" android:layout_gravity="start|center_vertical" android:background="@null"/> + android:layout_marginVertical="6dp"> @@ -123,8 +124,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginTop="8dp" - android:visibility="gone" - android:background="@null"/> + android:visibility="gone"/> From 9b3f964f18390887e30d353e5ed342cd6f95cd78 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Thu, 9 Jan 2025 02:52:14 -0800 Subject: [PATCH 3/8] BasePreferenceController: add some nullability annotations One fixes a NullAway warning reported on change I32ba2b6b8c05c1379b4d921350050979b1866805, and the other is just nice to have. Bug: 245989146 Change-Id: I8a7ffbddf865bab317fdabf5110262bb0626c529 Test: TreeHugger Flag: EXEMPT unable to flag this kind of change --- src/com/android/settings/core/BasePreferenceController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/com/android/settings/core/BasePreferenceController.java b/src/com/android/settings/core/BasePreferenceController.java index 156b0620566..7acf95c8deb 100644 --- a/src/com/android/settings/core/BasePreferenceController.java +++ b/src/com/android/settings/core/BasePreferenceController.java @@ -120,7 +120,9 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl */ public static final int DISABLED_DEPENDENT_SETTING = 5; + @NonNull protected final String mPreferenceKey; + @Nullable protected UiBlockListener mUiBlockListener; protected boolean mUiBlockerFinished; private boolean mIsForWork; From 0f73d15bb3adf05a1267d7b9cce549b5ab6dee30 Mon Sep 17 00:00:00 2001 From: chelseahao Date: Fri, 10 Jan 2025 17:51:24 +0800 Subject: [PATCH 4/8] Allow only password between 4 to 16 character. Test: atest Bug: 308368124 Flag: com.android.settingslib.flags.enable_le_audio_sharing Change-Id: I997cdef02079f74e82698fe052e29d64956cad60 --- .../audiosharing/audiostreams/SyncedState.java | 17 +++++++++++++++++ .../audiostreams/SyncedStateTest.java | 14 +++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedState.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedState.java index dffb235c5af..bdb62e8ae30 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedState.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedState.java @@ -18,9 +18,12 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; import android.app.AlertDialog; import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.text.Editable; +import android.text.TextWatcher; import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.widget.Button; import android.widget.EditText; import android.widget.TextView; @@ -109,6 +112,20 @@ class SyncedState extends AudioStreamStateHandler { controller.handleSourceAddRequest(preference, metadata); }) .create(); + EditText editText = layout.requireViewById(R.id.broadcast_edit_text); + editText.addTextChangedListener(new TextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) + .setEnabled(s.length() >= 4 && s.length() <= 16); + } + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override + public void afterTextChanged(Editable s) {} + }); alertDialog.show(); + Button positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + positiveButton.setEnabled(false); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java index 2b19e2058b3..12125a4390a 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java @@ -30,6 +30,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata; import android.content.Context; import android.content.DialogInterface; import android.widget.Button; +import android.widget.EditText; import android.widget.TextView; import androidx.preference.Preference; @@ -59,6 +60,8 @@ import org.robolectric.shadows.ShadowLooper; }) public class SyncedStateTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + private static final String INVALID_PASSWORD = "PAS"; + private static final String VALID_PASSWORD = "PASSWORD"; private static final String ENCRYPTED_METADATA = "BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;" + "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;"; @@ -143,15 +146,24 @@ public class SyncedStateTest { Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); assertThat(positiveButton).isNotNull(); + assertThat(positiveButton.isEnabled()).isFalse(); assertThat(positiveButton.getText().toString()) .isEqualTo( mMockContext.getString(R.string.bluetooth_connect_access_dialog_positive)); + ShadowAlertDialog shadowDialog = Shadow.extract(dialog); + EditText editText = shadowDialog.getView().findViewById(R.id.broadcast_edit_text); + assertThat(editText).isNotNull(); + editText.setText(VALID_PASSWORD); + assertThat(positiveButton.isEnabled()).isTrue(); + editText.setText(INVALID_PASSWORD); + assertThat(positiveButton.isEnabled()).isFalse(); + + editText.setText(VALID_PASSWORD); positiveButton.callOnClick(); ShadowLooper.idleMainLooper(); verify(mMockController).handleSourceAddRequest(any(), any()); - ShadowAlertDialog shadowDialog = Shadow.extract(dialog); TextView title = shadowDialog.getView().findViewById(R.id.broadcast_name_text); assertThat(title).isNotNull(); assertThat(title.getText().toString()).isEqualTo(BROADCAST_TITLE); From 406b5dfacedd49c23e53d993777cd71c731d0291 Mon Sep 17 00:00:00 2001 From: Jan Tomljanovic Date: Thu, 9 Jan 2025 17:30:19 +0000 Subject: [PATCH 5/8] Split biometrics resource into three. Bug: 388476345 Test: Treehugger Flag: com.android.settings.flags.biometrics_onboarding_education Relnote: N/a Change-Id: Ie6761f318e64e1cf2782c538d7b6af5b5094faa1 --- res/values/strings.xml | 10 + .../biometrics/face/FaceStatusUtils.java | 70 ++-- .../settings/biometrics/face/FaceUpdater.java | 83 ++-- .../fingerprint/FingerprintStatusUtils.java | 68 ++-- .../fingerprint/FingerprintUpdater.java | 37 +- .../safetycenter/BiometricSourcesUtils.java | 68 ++++ .../safetycenter/BiometricsSafetySource.java | 104 ++--- .../safetycenter/FaceSafetySource.java | 107 +++++ .../safetycenter/FingerprintSafetySource.java | 108 +++++ .../safetycenter/LockScreenSafetySource.java | 8 +- .../SafetySourceBroadcastReceiver.java | 53 ++- .../BiometricsSafetySourceTest.java | 33 +- .../safetycenter/FaceSafetySourceTest.java | 302 ++++++++++++++ .../FingerprintSafetySourceTest.java | 381 ++++++++++++++++++ .../LockScreenSafetySourceTest.java | 31 +- .../SafetySourceBroadcastReceiverTest.java | 120 +++++- 16 files changed, 1374 insertions(+), 209 deletions(-) create mode 100644 src/com/android/settings/safetycenter/BiometricSourcesUtils.java create mode 100644 src/com/android/settings/safetycenter/FaceSafetySource.java create mode 100644 src/com/android/settings/safetycenter/FingerprintSafetySource.java create mode 100644 tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java create mode 100644 tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 0ddab5bba51..9ab843be571 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -809,6 +809,12 @@ Setup needed + Face + + Face for work + + Face for private space + Face Unlock Face Unlock for work @@ -938,6 +944,8 @@ When using Fingerprint Unlock Fingerprint for work + + Fingerprint for work Check enrolled fingerprints @@ -1381,6 +1389,8 @@ Tap to set up Fingerprint Unlock for private space + + Fingerprint for private space Face Unlock for private space diff --git a/src/com/android/settings/biometrics/face/FaceStatusUtils.java b/src/com/android/settings/biometrics/face/FaceStatusUtils.java index 5af0a8a220b..302d6773213 100644 --- a/src/com/android/settings/biometrics/face/FaceStatusUtils.java +++ b/src/com/android/settings/biometrics/face/FaceStatusUtils.java @@ -26,12 +26,11 @@ import com.android.settings.R; import com.android.settings.Settings; import com.android.settings.Utils; import com.android.settings.biometrics.ParentalControlsUtils; +import com.android.settings.flags.Flags; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; -/** - * Utilities for face details shared between Security Settings and Safety Center. - */ +/** Utilities for face details shared between Security Settings and Safety Center. */ public class FaceStatusUtils { private final int mUserId; @@ -44,9 +43,7 @@ public class FaceStatusUtils { mUserId = userId; } - /** - * Returns whether the face settings entity should be shown. - */ + /** Returns whether the face settings entity should be shown. */ public boolean isAvailable() { return !Utils.isMultipleBiometricsSupported(mContext) && Utils.hasFaceHardware(mContext); } @@ -61,55 +58,70 @@ public class FaceStatusUtils { mContext, BiometricAuthenticator.TYPE_FACE); } - /** - * Returns the title of face settings entity. - */ + /** Returns the title of face settings entity. */ public String getTitle() { UserManager userManager = mContext.getSystemService(UserManager.class); if (userManager != null && userManager.isProfile()) { return mContext.getString( Utils.isPrivateProfile(mUserId, mContext) - ? R.string.private_space_face_unlock_title - : R.string.security_settings_face_profile_preference_title); + ? getPrivateSpaceTitle() + : getWorkProfileTitle()); } else { - return mContext.getString(R.string.security_settings_face_preference_title); + return mContext.getString(getRegularTitle()); } } - /** - * Returns the summary of face settings entity. - */ + private int getPrivateSpaceTitle() { + if (Flags.biometricsOnboardingEducation()) { + return R.string.private_space_face_unlock_title_new; + } + return R.string.private_space_face_unlock_title; + } + + private int getWorkProfileTitle() { + if (Flags.biometricsOnboardingEducation()) { + return R.string.security_settings_face_profile_preference_title_new; + } + return R.string.security_settings_face_profile_preference_title; + } + + private int getRegularTitle() { + if (Flags.biometricsOnboardingEducation()) { + return R.string.security_settings_face_preference_title_new; + } + return R.string.security_settings_face_preference_title; + } + + /** Returns the summary of face settings entity. */ public String getSummary() { if (shouldShowDisabledByAdminStr()) { return mContext.getString( com.android.settingslib.widget.restricted.R.string.disabled_by_admin); } else { - return mContext.getResources().getString(hasEnrolled() - ? R.string.security_settings_face_preference_summary - : R.string.security_settings_face_preference_summary_none); + return mContext.getResources() + .getString( + hasEnrolled() + ? R.string.security_settings_face_preference_summary + : R.string.security_settings_face_preference_summary_none); } } - /** - * Returns the class name of the Settings page corresponding to face settings. - */ + /** Returns the class name of the Settings page corresponding to face settings. */ public String getSettingsClassName() { - return hasEnrolled() ? Settings.FaceSettingsInternalActivity.class.getName() + return hasEnrolled() + ? Settings.FaceSettingsInternalActivity.class.getName() : FaceEnrollIntroductionInternal.class.getName(); } - /** - * Returns whether at least one face template has been enrolled. - */ + /** Returns whether at least one face template has been enrolled. */ public boolean hasEnrolled() { return mFaceManager.hasEnrolledTemplates(mUserId); } - /** - * Indicates if the face feature is enabled or disabled by the Device Admin. - */ + /** Indicates if the face feature is enabled or disabled by the Device Admin. */ private boolean shouldShowDisabledByAdminStr() { return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( - mContext, DevicePolicyManager.KEYGUARD_DISABLE_FACE, mUserId) != null; + mContext, DevicePolicyManager.KEYGUARD_DISABLE_FACE, mUserId) + != null; } } diff --git a/src/com/android/settings/biometrics/face/FaceUpdater.java b/src/com/android/settings/biometrics/face/FaceUpdater.java index ddb68129df1..99d6f09fc12 100644 --- a/src/com/android/settings/biometrics/face/FaceUpdater.java +++ b/src/com/android/settings/biometrics/face/FaceUpdater.java @@ -29,7 +29,9 @@ import androidx.annotation.Nullable; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricUtils; +import com.android.settings.flags.Flags; import com.android.settings.safetycenter.BiometricsSafetySource; +import com.android.settings.safetycenter.FaceSafetySource; /** * Responsible for making {@link FaceManager#enroll} and {@link FaceManager#remove} calls and thus @@ -51,20 +53,43 @@ public class FaceUpdater { } /** Wrapper around the {@link FaceManager#enroll} method. */ - public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel, - FaceManager.EnrollmentCallback callback, int[] disabledFeatures, Intent intent) { - this.enroll(userId, hardwareAuthToken, cancel, - new NotifyingEnrollmentCallback(mContext, callback), disabledFeatures, - null, false, intent); + public void enroll( + int userId, + byte[] hardwareAuthToken, + CancellationSignal cancel, + FaceManager.EnrollmentCallback callback, + int[] disabledFeatures, + Intent intent) { + this.enroll( + userId, + hardwareAuthToken, + cancel, + new NotifyingEnrollmentCallback(mContext, callback), + disabledFeatures, + null, + false, + intent); } /** Wrapper around the {@link FaceManager#enroll} method. */ - public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel, - FaceManager.EnrollmentCallback callback, int[] disabledFeatures, - @Nullable Surface previewSurface, boolean debugConsent, Intent intent) { - mFaceManager.enroll(userId, hardwareAuthToken, cancel, - new NotifyingEnrollmentCallback(mContext, callback), disabledFeatures, - previewSurface, debugConsent, toFaceEnrollOptions(intent)); + public void enroll( + int userId, + byte[] hardwareAuthToken, + CancellationSignal cancel, + FaceManager.EnrollmentCallback callback, + int[] disabledFeatures, + @Nullable Surface previewSurface, + boolean debugConsent, + Intent intent) { + mFaceManager.enroll( + userId, + hardwareAuthToken, + cancel, + new NotifyingEnrollmentCallback(mContext, callback), + disabledFeatures, + previewSurface, + debugConsent, + toFaceEnrollOptions(intent)); } /** Wrapper around the {@link FaceManager#remove} method. */ @@ -73,17 +98,15 @@ public class FaceUpdater { } /** - * Decorator of the {@link FaceManager.EnrollmentCallback} class that notifies other - * interested parties that a face setting has changed. + * Decorator of the {@link FaceManager.EnrollmentCallback} class that notifies other interested + * parties that a face setting has changed. */ - private static class NotifyingEnrollmentCallback - extends FaceManager.EnrollmentCallback { + private static class NotifyingEnrollmentCallback extends FaceManager.EnrollmentCallback { private final Context mContext; private final FaceManager.EnrollmentCallback mCallback; - NotifyingEnrollmentCallback(Context context, - FaceManager.EnrollmentCallback callback) { + NotifyingEnrollmentCallback(Context context, FaceManager.EnrollmentCallback callback) { mContext = context; mCallback = callback; } @@ -99,8 +122,14 @@ public class FaceUpdater { } @Override - public void onEnrollmentFrame(int helpCode, @Nullable CharSequence helpMessage, - @Nullable FaceEnrollCell cell, int stage, float pan, float tilt, float distance) { + public void onEnrollmentFrame( + int helpCode, + @Nullable CharSequence helpMessage, + @Nullable FaceEnrollCell cell, + int stage, + float pan, + float tilt, + float distance) { mCallback.onEnrollmentFrame(helpCode, helpMessage, cell, stage, pan, tilt, distance); } @@ -108,14 +137,18 @@ public class FaceUpdater { public void onEnrollmentProgress(int remaining) { mCallback.onEnrollmentProgress(remaining); if (remaining == 0) { - BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed + if (Flags.biometricsOnboardingEducation()) { + FaceSafetySource.onBiometricsChanged(mContext); + } else { + BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed + } } } } /** - * Decorator of the {@link FaceManager.RemovalCallback} class that notifies other - * interested parties that a face setting has changed. + * Decorator of the {@link FaceManager.RemovalCallback} class that notifies other interested + * parties that a face setting has changed. */ private static class NotifyingRemovalCallback extends FaceManager.RemovalCallback { @@ -135,7 +168,11 @@ public class FaceUpdater { @Override public void onRemovalSucceeded(@Nullable Face fp, int remaining) { mCallback.onRemovalSucceeded(fp, remaining); - BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed + if (Flags.biometricsOnboardingEducation()) { + FaceSafetySource.onBiometricsChanged(mContext); + } else { + BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed + } } } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java index a7c9e9d7be8..1ca564c8e35 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java @@ -25,29 +25,26 @@ import android.os.UserManager; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.ParentalControlsUtils; +import com.android.settings.flags.Flags; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.utils.StringUtil; -/** - * Utilities for fingerprint details shared between Security Settings and Safety Center. - */ +/** Utilities for fingerprint details shared between Security Settings and Safety Center. */ public class FingerprintStatusUtils { private final int mUserId; private final Context mContext; private final FingerprintManager mFingerprintManager; - public FingerprintStatusUtils(Context context, FingerprintManager fingerprintManager, - int userId) { + public FingerprintStatusUtils( + Context context, FingerprintManager fingerprintManager, int userId) { mContext = context; mFingerprintManager = fingerprintManager; mUserId = userId; } - /** - * Returns whether the fingerprint settings entity should be shown. - */ + /** Returns whether the fingerprint settings entity should be shown. */ public boolean isAvailable() { return !Utils.isMultipleBiometricsSupported(mContext) && Utils.hasFingerprintHardware(mContext); @@ -62,24 +59,42 @@ public class FingerprintStatusUtils { return ParentalControlsUtils.parentConsentRequired( mContext, BiometricAuthenticator.TYPE_FINGERPRINT); } - /** - * Returns the title of fingerprint settings entity. - */ + + /** Returns the title of fingerprint settings entity. */ public String getTitle() { UserManager userManager = mContext.getSystemService(UserManager.class); if (userManager != null && userManager.isProfile()) { return mContext.getString( Utils.isPrivateProfile(mUserId, mContext) - ? R.string.private_space_fingerprint_unlock_title - : R.string.security_settings_work_fingerprint_preference_title); + ? getPrivateSpaceTitle() + : getWorkProfileTitle()); } else { - return mContext.getString(R.string.security_settings_fingerprint_preference_title); + return mContext.getString(getRegularTitle()); } } - /** - * Returns the summary of fingerprint settings entity. - */ + private int getPrivateSpaceTitle() { + if (Flags.biometricsOnboardingEducation()) { + return R.string.private_space_fingerprint_unlock_title_new; + } + return R.string.private_space_fingerprint_unlock_title; + } + + private int getWorkProfileTitle() { + if (Flags.biometricsOnboardingEducation()) { + return R.string.security_settings_work_fingerprint_preference_title_new; + } + return R.string.security_settings_work_fingerprint_preference_title; + } + + private int getRegularTitle() { + if (Flags.biometricsOnboardingEducation()) { + return R.string.security_settings_fingerprint; // doesn't have an overlay + } + return R.string.security_settings_fingerprint_preference_title; + } + + /** Returns the summary of fingerprint settings entity. */ public String getSummary() { if (shouldShowDisabledByAdminStr()) { return mContext.getString( @@ -87,7 +102,9 @@ public class FingerprintStatusUtils { } if (hasEnrolled()) { final int numEnrolled = mFingerprintManager.getEnrolledFingerprints(mUserId).size(); - return StringUtil.getIcuPluralsString(mContext, numEnrolled, + return StringUtil.getIcuPluralsString( + mContext, + numEnrolled, R.string.security_settings_fingerprint_preference_summary); } else { return mContext.getString( @@ -95,25 +112,20 @@ public class FingerprintStatusUtils { } } - /** - * Returns the class name of the Settings page corresponding to fingerprint settings. - */ + /** Returns the class name of the Settings page corresponding to fingerprint settings. */ public String getSettingsClassName() { return FingerprintSettings.class.getName(); } - /** - * Returns whether at least one fingerprint has been enrolled. - */ + /** Returns whether at least one fingerprint has been enrolled. */ public boolean hasEnrolled() { return mFingerprintManager.hasEnrolledFingerprints(mUserId); } - /** - * Indicates if the fingerprint feature should show the "Disabled by Admin" string. - */ + /** Indicates if the fingerprint feature should show the "Disabled by Admin" string. */ private boolean shouldShowDisabledByAdminStr() { return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( - mContext, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId) != null; + mContext, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId) + != null; } } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java b/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java index ea9abf1bb5c..84ee27e3586 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintUpdater.java @@ -27,7 +27,9 @@ import androidx.annotation.Nullable; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricUtils; +import com.android.settings.flags.Flags; import com.android.settings.safetycenter.BiometricsSafetySource; +import com.android.settings.safetycenter.FingerprintSafetySource; /** * Responsible for making {@link FingerprintManager#enroll} and {@link FingerprintManager#remove} @@ -49,11 +51,19 @@ public class FingerprintUpdater { } /** Wrapper around the {@link FingerprintManager#enroll} method. */ - public void enroll(byte [] hardwareAuthToken, CancellationSignal cancel, int userId, + public void enroll( + byte[] hardwareAuthToken, + CancellationSignal cancel, + int userId, FingerprintManager.EnrollmentCallback callback, - @FingerprintManager.EnrollReason int enrollReason, Intent intent) { - mFingerprintManager.enroll(hardwareAuthToken, cancel, userId, - new NotifyingEnrollmentCallback(mContext, callback), enrollReason, + @FingerprintManager.EnrollReason int enrollReason, + Intent intent) { + mFingerprintManager.enroll( + hardwareAuthToken, + cancel, + userId, + new NotifyingEnrollmentCallback(mContext, callback), + enrollReason, toFingerprintEnrollOptions(intent)); } @@ -66,14 +76,13 @@ public class FingerprintUpdater { * Decorator of the {@link FingerprintManager.EnrollmentCallback} class that notifies other * interested parties that a fingerprint setting has changed. */ - private static class NotifyingEnrollmentCallback - extends FingerprintManager.EnrollmentCallback { + private static class NotifyingEnrollmentCallback extends FingerprintManager.EnrollmentCallback { private final Context mContext; private final FingerprintManager.EnrollmentCallback mCallback; - NotifyingEnrollmentCallback(Context context, - FingerprintManager.EnrollmentCallback callback) { + NotifyingEnrollmentCallback( + Context context, FingerprintManager.EnrollmentCallback callback) { mContext = context; mCallback = callback; } @@ -92,7 +101,11 @@ public class FingerprintUpdater { public void onEnrollmentProgress(int remaining) { mCallback.onEnrollmentProgress(remaining); if (remaining == 0) { - BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed + if (Flags.biometricsOnboardingEducation()) { + FingerprintSafetySource.onBiometricsChanged(mContext); + } else { + BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed + } } } @@ -139,7 +152,11 @@ public class FingerprintUpdater { @Override public void onRemovalSucceeded(@Nullable Fingerprint fp, int remaining) { mCallback.onRemovalSucceeded(fp, remaining); - BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed + if (Flags.biometricsOnboardingEducation()) { + FingerprintSafetySource.onBiometricsChanged(mContext); + } else { + BiometricsSafetySource.onBiometricsChanged(mContext); // biometrics data changed + } } } diff --git a/src/com/android/settings/safetycenter/BiometricSourcesUtils.java b/src/com/android/settings/safetycenter/BiometricSourcesUtils.java new file mode 100644 index 00000000000..fcda654b761 --- /dev/null +++ b/src/com/android/settings/safetycenter/BiometricSourcesUtils.java @@ -0,0 +1,68 @@ +/* + * 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.safetycenter; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.safetycenter.SafetyEvent; +import android.safetycenter.SafetySourceData; +import android.safetycenter.SafetySourceStatus; + +/** Static helpers for setting SafetyCenter data for biometric safety sources. */ +public final class BiometricSourcesUtils { + + public static final int REQUEST_CODE_COMBINED_BIOMETRIC_SETTING = 10; + public static final int REQUEST_CODE_FACE_SETTING = 20; + public static final int REQUEST_CODE_FINGERPRINT_SETTING = 30; + + private BiometricSourcesUtils() {} + + /** Sets data for one of the biometrics sources */ + public static void setBiometricSafetySourceData( + String safetySourceId, + Context context, + String title, + String summary, + PendingIntent pendingIntent, + boolean enabled, + boolean hasEnrolled, + SafetyEvent safetyEvent) { + int severityLevel = + enabled && hasEnrolled + ? SafetySourceData.SEVERITY_LEVEL_INFORMATION + : SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED; + + SafetySourceStatus status = + new SafetySourceStatus.Builder(title, summary, severityLevel) + .setPendingIntent(pendingIntent) + .setEnabled(enabled) + .build(); + SafetySourceData safetySourceData = + new SafetySourceData.Builder().setStatus(status).build(); + + SafetyCenterManagerWrapper.get() + .setSafetySourceData(context, safetySourceId, safetySourceData, safetyEvent); + } + + /** Helper method for creating a pending intent. */ + public static PendingIntent createPendingIntent( + Context context, Intent intent, int requestCode) { + return PendingIntent.getActivity( + context, requestCode, intent, PendingIntent.FLAG_IMMUTABLE); + } +} diff --git a/src/com/android/settings/safetycenter/BiometricsSafetySource.java b/src/com/android/settings/safetycenter/BiometricsSafetySource.java index c93ced1a50d..97a922e1831 100644 --- a/src/com/android/settings/safetycenter/BiometricsSafetySource.java +++ b/src/com/android/settings/safetycenter/BiometricsSafetySource.java @@ -16,9 +16,11 @@ package com.android.settings.safetycenter; -import android.app.PendingIntent; +import static com.android.settings.safetycenter.BiometricSourcesUtils.REQUEST_CODE_COMBINED_BIOMETRIC_SETTING; +import static com.android.settings.safetycenter.BiometricSourcesUtils.REQUEST_CODE_FACE_SETTING; +import static com.android.settings.safetycenter.BiometricSourcesUtils.REQUEST_CODE_FINGERPRINT_SETTING; + import android.content.Context; -import android.content.Intent; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; @@ -26,8 +28,6 @@ import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.safetycenter.SafetyEvent; -import android.safetycenter.SafetySourceData; -import android.safetycenter.SafetySourceStatus; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricNavigationUtils; @@ -41,9 +41,6 @@ import com.android.settingslib.RestrictedLockUtils; public final class BiometricsSafetySource { public static final String SAFETY_SOURCE_ID = "AndroidBiometrics"; - private static final int REQUEST_CODE_COMBINED_BIOMETRIC_SETTING = 10; - private static final int REQUEST_CODE_FACE_SETTING = 20; - private static final int REQUEST_CODE_FINGERPRINT_SETTING = 30; private BiometricsSafetySource() {} @@ -53,42 +50,38 @@ public final class BiometricsSafetySource { return; } - final UserHandle userHandle = Process.myUserHandle(); - final int userId = userHandle.getIdentifier(); - final UserManager userManager = UserManager.get(context); + UserHandle userHandle = Process.myUserHandle(); + int userId = userHandle.getIdentifier(); + UserManager userManager = UserManager.get(context); UserHandle profileParentUserHandle = userManager.getProfileParent(userHandle); if (profileParentUserHandle == null) { profileParentUserHandle = userHandle; } - final Context profileParentContext = - context.createContextAsUser(profileParentUserHandle, 0); + Context profileParentContext = context.createContextAsUser(profileParentUserHandle, 0); if (android.os.Flags.allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures() && userManager.isPrivateProfile()) { // SC always expects a response from the source if the broadcast has been sent for this // source, therefore, we need to send a null SafetySourceData. - SafetyCenterManagerWrapper.get().setSafetySourceData( - context, - SAFETY_SOURCE_ID, - /* safetySourceData= */ null, - safetyEvent); + SafetyCenterManagerWrapper.get() + .setSafetySourceData( + context, SAFETY_SOURCE_ID, /* safetySourceData= */ null, safetyEvent); return; } - final BiometricNavigationUtils biometricNavigationUtils = - new BiometricNavigationUtils(userId); - final CombinedBiometricStatusUtils combinedBiometricStatusUtils = + BiometricNavigationUtils biometricNavigationUtils = new BiometricNavigationUtils(userId); + CombinedBiometricStatusUtils combinedBiometricStatusUtils = new CombinedBiometricStatusUtils(context, userId); - final ActiveUnlockStatusUtils activeUnlockStatusUtils = - new ActiveUnlockStatusUtils(context); + ActiveUnlockStatusUtils activeUnlockStatusUtils = new ActiveUnlockStatusUtils(context); if (!userManager.isProfile() && activeUnlockStatusUtils.isAvailable()) { - final RestrictedLockUtils.EnforcedAdmin disablingAdmin = + RestrictedLockUtils.EnforcedAdmin disablingAdmin = combinedBiometricStatusUtils.getDisablingAdmin(); - setBiometricSafetySourceData( + BiometricSourcesUtils.setBiometricSafetySourceData( + SAFETY_SOURCE_ID, context, activeUnlockStatusUtils.getTitleForActiveUnlock(), combinedBiometricStatusUtils.getSummary(), - createPendingIntent( + BiometricSourcesUtils.createPendingIntent( context, biometricNavigationUtils.getBiometricSettingsIntent( context, @@ -102,13 +95,14 @@ public final class BiometricsSafetySource { return; } if (combinedBiometricStatusUtils.isAvailable()) { - final RestrictedLockUtils.EnforcedAdmin disablingAdmin = + RestrictedLockUtils.EnforcedAdmin disablingAdmin = combinedBiometricStatusUtils.getDisablingAdmin(); - setBiometricSafetySourceData( + BiometricSourcesUtils.setBiometricSafetySourceData( + SAFETY_SOURCE_ID, context, combinedBiometricStatusUtils.getTitle(), combinedBiometricStatusUtils.getSummary(), - createPendingIntent( + BiometricSourcesUtils.createPendingIntent( profileParentContext, biometricNavigationUtils .getBiometricSettingsIntent( @@ -125,17 +119,17 @@ public final class BiometricsSafetySource { return; } - final FaceManager faceManager = Utils.getFaceManagerOrNull(context); - final FaceStatusUtils faceStatusUtils = new FaceStatusUtils(context, faceManager, userId); + FaceManager faceManager = Utils.getFaceManagerOrNull(context); + FaceStatusUtils faceStatusUtils = new FaceStatusUtils(context, faceManager, userId); if (faceStatusUtils.isAvailable()) { - final RestrictedLockUtils.EnforcedAdmin disablingAdmin = - faceStatusUtils.getDisablingAdmin(); - setBiometricSafetySourceData( + RestrictedLockUtils.EnforcedAdmin disablingAdmin = faceStatusUtils.getDisablingAdmin(); + BiometricSourcesUtils.setBiometricSafetySourceData( + SAFETY_SOURCE_ID, context, faceStatusUtils.getTitle(), faceStatusUtils.getSummary(), - createPendingIntent( + BiometricSourcesUtils.createPendingIntent( profileParentContext, biometricNavigationUtils .getBiometricSettingsIntent( @@ -152,18 +146,19 @@ public final class BiometricsSafetySource { return; } - final FingerprintManager fingerprintManager = Utils.getFingerprintManagerOrNull(context); - final FingerprintStatusUtils fingerprintStatusUtils = + FingerprintManager fingerprintManager = Utils.getFingerprintManagerOrNull(context); + FingerprintStatusUtils fingerprintStatusUtils = new FingerprintStatusUtils(context, fingerprintManager, userId); if (fingerprintStatusUtils.isAvailable()) { - final RestrictedLockUtils.EnforcedAdmin disablingAdmin = + RestrictedLockUtils.EnforcedAdmin disablingAdmin = fingerprintStatusUtils.getDisablingAdmin(); - setBiometricSafetySourceData( + BiometricSourcesUtils.setBiometricSafetySourceData( + SAFETY_SOURCE_ID, context, fingerprintStatusUtils.getTitle(), fingerprintStatusUtils.getSummary(), - createPendingIntent( + BiometricSourcesUtils.createPendingIntent( profileParentContext, biometricNavigationUtils .getBiometricSettingsIntent( @@ -191,35 +186,4 @@ public final class BiometricsSafetySource { new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED) .build()); } - - private static void setBiometricSafetySourceData( - Context context, - String title, - String summary, - PendingIntent pendingIntent, - boolean enabled, - boolean hasEnrolled, - SafetyEvent safetyEvent) { - final int severityLevel = - enabled && hasEnrolled - ? SafetySourceData.SEVERITY_LEVEL_INFORMATION - : SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED; - - final SafetySourceStatus status = - new SafetySourceStatus.Builder(title, summary, severityLevel) - .setPendingIntent(pendingIntent) - .setEnabled(enabled) - .build(); - final SafetySourceData safetySourceData = - new SafetySourceData.Builder().setStatus(status).build(); - - SafetyCenterManagerWrapper.get() - .setSafetySourceData(context, SAFETY_SOURCE_ID, safetySourceData, safetyEvent); - } - - private static PendingIntent createPendingIntent( - Context context, Intent intent, int requestCode) { - return PendingIntent.getActivity( - context, requestCode, intent, PendingIntent.FLAG_IMMUTABLE); - } } diff --git a/src/com/android/settings/safetycenter/FaceSafetySource.java b/src/com/android/settings/safetycenter/FaceSafetySource.java new file mode 100644 index 00000000000..a945bc02c70 --- /dev/null +++ b/src/com/android/settings/safetycenter/FaceSafetySource.java @@ -0,0 +1,107 @@ +/* + * 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.safetycenter; + +import static com.android.settings.safetycenter.BiometricSourcesUtils.REQUEST_CODE_FACE_SETTING; + +import android.content.Context; +import android.hardware.face.FaceManager; +import android.os.Bundle; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import android.safetycenter.SafetyEvent; + +import com.android.settings.Utils; +import com.android.settings.biometrics.BiometricNavigationUtils; +import com.android.settings.biometrics.face.FaceStatusUtils; +import com.android.settingslib.RestrictedLockUtils; + +/** Face biometrics Safety Source for Safety Center. */ +public final class FaceSafetySource { + + public static final String SAFETY_SOURCE_ID = "AndroidFaceUnlock"; + + private FaceSafetySource() {} + + /** Sets biometric safety data for Safety Center. */ + public static void setSafetySourceData(Context context, SafetyEvent safetyEvent) { + if (!SafetyCenterManagerWrapper.get().isEnabled(context)) { + return; + } + + // Handle private profile case + UserManager userManager = UserManager.get(context); + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && userManager.isPrivateProfile()) { + // SC always expects a response from the source if the broadcast has been sent for this + // source, therefore, we need to send a null SafetySourceData. + SafetyCenterManagerWrapper.get() + .setSafetySourceData( + context, SAFETY_SOURCE_ID, /* safetySourceData= */ null, safetyEvent); + return; + } + + UserHandle userHandle = Process.myUserHandle(); + int userId = userHandle.getIdentifier(); + FaceManager faceManager = Utils.getFaceManagerOrNull(context); + FaceStatusUtils faceStatusUtils = new FaceStatusUtils(context, faceManager, userId); + BiometricNavigationUtils biometricNavigationUtils = new BiometricNavigationUtils(userId); + UserHandle profileParentUserHandle = userManager.getProfileParent(userHandle); + if (profileParentUserHandle == null) { + profileParentUserHandle = userHandle; + } + Context profileParentContext = context.createContextAsUser(profileParentUserHandle, 0); + + if (Utils.hasFaceHardware(context)) { + RestrictedLockUtils.EnforcedAdmin disablingAdmin = faceStatusUtils.getDisablingAdmin(); + BiometricSourcesUtils.setBiometricSafetySourceData( + SAFETY_SOURCE_ID, + context, + faceStatusUtils.getTitle(), + faceStatusUtils.getSummary(), + BiometricSourcesUtils.createPendingIntent( + profileParentContext, + biometricNavigationUtils + .getBiometricSettingsIntent( + context, + faceStatusUtils.getSettingsClassName(), + disablingAdmin, + Bundle.EMPTY) + .setIdentifier(Integer.toString(userId)), + REQUEST_CODE_FACE_SETTING), + disablingAdmin == null /* enabled */, + faceStatusUtils.hasEnrolled(), + safetyEvent); + + return; + } + + SafetyCenterManagerWrapper.get() + .setSafetySourceData( + context, SAFETY_SOURCE_ID, /* safetySourceData= */ null, safetyEvent); + } + + /** Notifies Safety Center of a change in face biometrics settings. */ + public static void onBiometricsChanged(Context context) { + setSafetySourceData( + context, + new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED) + .build()); + } +} diff --git a/src/com/android/settings/safetycenter/FingerprintSafetySource.java b/src/com/android/settings/safetycenter/FingerprintSafetySource.java new file mode 100644 index 00000000000..9d5d3b7e36e --- /dev/null +++ b/src/com/android/settings/safetycenter/FingerprintSafetySource.java @@ -0,0 +1,108 @@ +/* + * 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.safetycenter; + +import static com.android.settings.safetycenter.BiometricSourcesUtils.REQUEST_CODE_FINGERPRINT_SETTING; + +import android.content.Context; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Bundle; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import android.safetycenter.SafetyEvent; + +import com.android.settings.Utils; +import com.android.settings.biometrics.BiometricNavigationUtils; +import com.android.settings.biometrics.fingerprint.FingerprintStatusUtils; +import com.android.settingslib.RestrictedLockUtils; + +/** Fingerprint biometrics Safety Source for Safety Center. */ +public final class FingerprintSafetySource { + + public static final String SAFETY_SOURCE_ID = "AndroidFingerprintUnlock"; + + private FingerprintSafetySource() {} + + /** Sets biometric safety data for Safety Center. */ + public static void setSafetySourceData(Context context, SafetyEvent safetyEvent) { + if (!SafetyCenterManagerWrapper.get().isEnabled(context)) { + return; + } + + // Handle private profile case + UserManager userManager = UserManager.get(context); + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && userManager.isPrivateProfile()) { + // SC always expects a response from the source if the broadcast has been sent for this + // source, therefore, we need to send a null SafetySourceData. + SafetyCenterManagerWrapper.get() + .setSafetySourceData( + context, SAFETY_SOURCE_ID, /* safetySourceData= */ null, safetyEvent); + return; + } + + UserHandle userHandle = Process.myUserHandle(); + int userId = userHandle.getIdentifier(); + FingerprintManager fingerprintManager = Utils.getFingerprintManagerOrNull(context); + FingerprintStatusUtils fingerprintStatusUtils = + new FingerprintStatusUtils(context, fingerprintManager, userId); + BiometricNavigationUtils biometricNavigationUtils = new BiometricNavigationUtils(userId); + UserHandle profileParentUserHandle = userManager.getProfileParent(userHandle); + if (profileParentUserHandle == null) { + profileParentUserHandle = userHandle; + } + Context profileParentContext = context.createContextAsUser(profileParentUserHandle, 0); + + if (Utils.hasFingerprintHardware(context)) { + RestrictedLockUtils.EnforcedAdmin disablingAdmin = + fingerprintStatusUtils.getDisablingAdmin(); + BiometricSourcesUtils.setBiometricSafetySourceData( + SAFETY_SOURCE_ID, + context, + fingerprintStatusUtils.getTitle(), + fingerprintStatusUtils.getSummary(), + BiometricSourcesUtils.createPendingIntent( + profileParentContext, + biometricNavigationUtils + .getBiometricSettingsIntent( + context, + fingerprintStatusUtils.getSettingsClassName(), + disablingAdmin, + Bundle.EMPTY) + .setIdentifier(Integer.toString(userId)), + REQUEST_CODE_FINGERPRINT_SETTING), + disablingAdmin == null /* enabled */, + fingerprintStatusUtils.hasEnrolled(), + safetyEvent); + return; + } + + SafetyCenterManagerWrapper.get() + .setSafetySourceData( + context, SAFETY_SOURCE_ID, /* safetySourceData= */ null, safetyEvent); + } + + /** Notifies Safety Center of a change in fingerprint biometrics settings. */ + public static void onBiometricsChanged(Context context) { + setSafetySourceData( + context, + new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED) + .build()); + } +} diff --git a/src/com/android/settings/safetycenter/LockScreenSafetySource.java b/src/com/android/settings/safetycenter/LockScreenSafetySource.java index 6960fc6c2d2..14ad268c3cf 100644 --- a/src/com/android/settings/safetycenter/LockScreenSafetySource.java +++ b/src/com/android/settings/safetycenter/LockScreenSafetySource.java @@ -31,6 +31,7 @@ import android.safetycenter.SafetySourceStatus; import android.safetycenter.SafetySourceStatus.IconAction; import com.android.settings.R; +import com.android.settings.flags.Flags; import com.android.settings.security.ScreenLockPreferenceDetailsUtils; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; @@ -122,7 +123,12 @@ public final class LockScreenSafetySource { // Also send refreshed safety center data for biometrics, since changing lockscreen settings // can unset biometrics. - BiometricsSafetySource.onBiometricsChanged(context); + if (Flags.biometricsOnboardingEducation()) { + FaceSafetySource.onBiometricsChanged(context); + FingerprintSafetySource.onBiometricsChanged(context); + } else { + BiometricsSafetySource.onBiometricsChanged(context); + } } private static IconAction createGearMenuIconAction( diff --git a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java index cc0f892e873..992c0eca589 100644 --- a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java +++ b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java @@ -28,6 +28,7 @@ import android.content.Intent; import android.safetycenter.SafetyCenterManager; import android.safetycenter.SafetyEvent; +import com.android.settings.flags.Flags; import com.android.settings.privatespace.PrivateSpaceSafetySource; import com.android.settings.security.ScreenLockPreferenceDetailsUtils; @@ -48,48 +49,60 @@ public class SafetySourceBroadcastReceiver extends BroadcastReceiver { } if (ACTION_REFRESH_SAFETY_SOURCES.equals(intent.getAction())) { - String[] sourceIdsExtra = - intent.getStringArrayExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS); - final String refreshBroadcastId = intent.getStringExtra( - SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID); + String[] sourceIdsExtra = intent.getStringArrayExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS); + final String refreshBroadcastId = + intent.getStringExtra( + SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID); if (sourceIdsExtra != null && sourceIdsExtra.length > 0 && refreshBroadcastId != null) { - final SafetyEvent safetyEvent = new SafetyEvent.Builder( - SAFETY_EVENT_TYPE_REFRESH_REQUESTED) - .setRefreshBroadcastId(refreshBroadcastId).build(); - refreshSafetySources( - context, - ImmutableList.copyOf(sourceIdsExtra), - safetyEvent); + final SafetyEvent safetyEvent = + new SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId(refreshBroadcastId) + .build(); + refreshSafetySources(context, ImmutableList.copyOf(sourceIdsExtra), safetyEvent); } return; } - if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) { refreshAllSafetySources(context, EVENT_DEVICE_REBOOTED); } } - private static void refreshSafetySources(Context context, List sourceIds, - SafetyEvent safetyEvent) { + private static void refreshSafetySources( + Context context, List sourceIds, SafetyEvent safetyEvent) { if (sourceIds.contains(LockScreenSafetySource.SAFETY_SOURCE_ID)) { - LockScreenSafetySource.setSafetySourceData(context, - new ScreenLockPreferenceDetailsUtils(context), safetyEvent); + LockScreenSafetySource.setSafetySourceData( + context, new ScreenLockPreferenceDetailsUtils(context), safetyEvent); } - if (sourceIds.contains(BiometricsSafetySource.SAFETY_SOURCE_ID)) { + if (sourceIds.contains(BiometricsSafetySource.SAFETY_SOURCE_ID) + && !Flags.biometricsOnboardingEducation()) { BiometricsSafetySource.setSafetySourceData(context, safetyEvent); } if (sourceIds.contains(PrivateSpaceSafetySource.SAFETY_SOURCE_ID)) { PrivateSpaceSafetySource.setSafetySourceData(context, safetyEvent); } + if (sourceIds.contains(FaceSafetySource.SAFETY_SOURCE_ID) + && Flags.biometricsOnboardingEducation()) { + FaceSafetySource.setSafetySourceData(context, safetyEvent); + } + if (sourceIds.contains(FingerprintSafetySource.SAFETY_SOURCE_ID) + && Flags.biometricsOnboardingEducation()) { + FingerprintSafetySource.setSafetySourceData(context, safetyEvent); + } } private static void refreshAllSafetySources(Context context, SafetyEvent safetyEvent) { - LockScreenSafetySource.setSafetySourceData(context, - new ScreenLockPreferenceDetailsUtils(context), safetyEvent); - BiometricsSafetySource.setSafetySourceData(context, safetyEvent); + LockScreenSafetySource.setSafetySourceData( + context, new ScreenLockPreferenceDetailsUtils(context), safetyEvent); + if (!Flags.biometricsOnboardingEducation()) { + BiometricsSafetySource.setSafetySourceData(context, safetyEvent); + } PrivateSpaceSafetySource.setSafetySourceData(context, safetyEvent); + if (Flags.biometricsOnboardingEducation()) { + FaceSafetySource.setSafetySourceData(context, safetyEvent); + FingerprintSafetySource.setSafetySourceData(context, safetyEvent); + } } } diff --git a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java index 71899fbb730..574b4a7a77f 100644 --- a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java @@ -39,6 +39,9 @@ import android.hardware.face.FaceManager; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.safetycenter.SafetyEvent; import android.safetycenter.SafetySourceData; import android.safetycenter.SafetySourceStatus; @@ -50,6 +53,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.settings.Settings; import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal; import com.android.settings.biometrics.fingerprint.FingerprintSettings; +import com.android.settings.flags.Flags; import com.android.settings.testutils.ActiveUnlockTestUtils; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.ResourcesUtils; @@ -57,6 +61,7 @@ import com.android.settingslib.utils.StringUtil; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -67,6 +72,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +@RequiresFlagsDisabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) @RunWith(AndroidJUnit4.class) public class BiometricsSafetySourceTest { @@ -75,6 +81,9 @@ public class BiometricsSafetySourceTest { private static final SafetyEvent EVENT_SOURCE_STATE_CHANGED = new SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private Context mApplicationContext; @Mock private PackageManager mPackageManager; @@ -196,7 +205,7 @@ public class BiometricsSafetySourceTest { @Test public void setSafetySourceData_withFingerprintsEnrolled_whenDisabledByAdmin_setsData() { - final int enrolledFingerprintsCount = 2; + int enrolledFingerprintsCount = 2; when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(false); @@ -216,7 +225,7 @@ public class BiometricsSafetySourceTest { @Test public void setSafetySourceData_withFingerprintsEnrolled_whenNotDisabledByAdmin_setsData() { - final int enrolledFingerprintsCount = 2; + int enrolledFingerprintsCount = 2; when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(false); @@ -364,7 +373,7 @@ public class BiometricsSafetySourceTest { @Test public void setSafetySourceData_faceAndFingerprint_whenFaceEnrolled_withMpFingers_setsData() { - final int enrolledFingerprintsCount = 2; + int enrolledFingerprintsCount = 2; when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(true); @@ -382,7 +391,7 @@ public class BiometricsSafetySourceTest { @Test public void setSafetySourceData_faceAndFingerprint_whenFaceEnrolled_withOneFinger_setsData() { - final int enrolledFingerprintsCount = 1; + int enrolledFingerprintsCount = 1; when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(true); @@ -417,7 +426,7 @@ public class BiometricsSafetySourceTest { @Test public void setSafetySourceData_activeUnlockEnabled_withFingerprintOnly_setsData() { - final int enrolledFingerprintsCount = 1; + int enrolledFingerprintsCount = 1; when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(false); @@ -453,7 +462,7 @@ public class BiometricsSafetySourceTest { @Test public void setSafetySourceData_activeUnlockEnabled_withFaceAndFingerprint_setsData() { - final int enrolledFingerprintsCount = 1; + int enrolledFingerprintsCount = 1; when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(true); @@ -472,7 +481,7 @@ public class BiometricsSafetySourceTest { @Test public void setSafetySourceData_faceAndFingerprint_whenNoFaceEnrolled_withFingers_setsData() { - final int enrolledFingerprintsCount = 1; + int enrolledFingerprintsCount = 1; when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(true); @@ -660,7 +669,7 @@ public class BiometricsSafetySourceTest { String expectedTitleResName, String expectedSummaryResName, int expectedSummaryQuantity) { - final int stringResId = + int stringResId = ResourcesUtils.getResourcesId( ApplicationProvider.getApplicationContext(), "string", @@ -676,7 +685,7 @@ public class BiometricsSafetySourceTest { String expectedSummaryResName, int expectedSummaryQuantity, String expectedSettingsClassName) { - final int stringResId = + int stringResId = ResourcesUtils.getResourcesId( ApplicationProvider.getApplicationContext(), "string", @@ -705,7 +714,7 @@ public class BiometricsSafetySourceTest { assertThat(safetySourceStatus.getSeverityLevel()) .isEqualTo(SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED); - final Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent(); + Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent(); assertThat(clickIntent).isNotNull(); assertThat(clickIntent.getAction()).isEqualTo(ACTION_SHOW_ADMIN_SUPPORT_DETAILS); } @@ -725,14 +734,14 @@ public class BiometricsSafetySourceTest { assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle); assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary); assertThat(safetySourceStatus.isEnabled()).isTrue(); - final Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent(); + Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent(); assertThat(clickIntent).isNotNull(); assertThat(clickIntent.getComponent().getPackageName()).isEqualTo("com.android.settings"); assertThat(clickIntent.getComponent().getClassName()).isEqualTo(expectedSettingsClassName); } private List createFingerprintList(int size) { - final List fingerprintList = new ArrayList<>(size); + List fingerprintList = new ArrayList<>(size); for (int i = 0; i < size; i++) { fingerprintList.add(new Fingerprint("fingerprint" + i, 0, 0)); } diff --git a/tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java new file mode 100644 index 00000000000..e42a4d1fd9d --- /dev/null +++ b/tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.safetycenter; + +import static android.provider.Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS; +import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.face.FaceManager; +import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.safetycenter.SafetyEvent; +import android.safetycenter.SafetySourceData; +import android.safetycenter.SafetySourceStatus; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.Settings; +import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal; +import com.android.settings.flags.Flags; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.ResourcesUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) +@RunWith(AndroidJUnit4.class) +public class FaceSafetySourceTest { + + private static final ComponentName COMPONENT_NAME = new ComponentName("package", "class"); + private static final UserHandle USER_HANDLE = new UserHandle(UserHandle.myUserId()); + private static final SafetyEvent EVENT_SOURCE_STATE_CHANGED = + new SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build(); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private Context mApplicationContext; + + @Mock private PackageManager mPackageManager; + @Mock private DevicePolicyManager mDevicePolicyManager; + @Mock private FaceManager mFaceManager; + @Mock private LockPatternUtils mLockPatternUtils; + @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mApplicationContext = spy(ApplicationProvider.getApplicationContext()); + when(mApplicationContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true); + when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(USER_HANDLE)) + .thenReturn(COMPONENT_NAME); + when(mApplicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE)) + .thenReturn(mDevicePolicyManager); + when(mApplicationContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager); + FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); + when(featureFactory.securityFeatureProvider.getLockPatternUtils(mApplicationContext)) + .thenReturn(mLockPatternUtils); + doReturn(true).when(mLockPatternUtils).isSecure(anyInt()); + SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper; + } + + @After + public void tearDown() { + SafetyCenterManagerWrapper.sInstance = null; + } + + @Test + public void setSafetyData_whenSafetyCenterIsDisabled_doesNotSetData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(false); + + FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + verify(mSafetyCenterManagerWrapper, never()) + .setSafetySourceData(any(), any(), any(), any()); + } + + @Test + public void setSafetySourceData_whenSafetyCenterIsEnabled_withoutFaceHardware_setsNullData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(false); + + FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData(any(), eq(FaceSafetySource.SAFETY_SOURCE_ID), eq(null), any()); + } + + @Test + public void setSafetySourceData_setsDataForFaceSource() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData(any(), eq(FaceSafetySource.SAFETY_SOURCE_ID), any(), any()); + } + + @Test + public void setSafetySourceData_setsDataWithCorrectSafetyEvent() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData(any(), any(), any(), eq(EVENT_SOURCE_STATE_CHANGED)); + } + + @Test + public void setSafetySourceData_withFaceNotEnrolled_whenDisabledByAdmin_setsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE); + + FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + assertSafetySourceDisabledDataSetWithSingularSummary( + "security_settings_face_preference_title_new", + "security_settings_face_preference_summary_none"); + } + + @Test + public void setSafetySourceData_withFaceNotEnrolled_whenNotDisabledByAdmin_setsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + assertSafetySourceEnabledDataSetWithSingularSummary( + "security_settings_face_preference_title_new", + "security_settings_face_preference_summary_none", + FaceEnrollIntroductionInternal.class.getName()); + } + + @Test + public void setSafetySourceData_withFaceEnrolled_whenDisabledByAdmin_setsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE); + + FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + assertSafetySourceDisabledDataSetWithSingularSummary( + "security_settings_face_preference_title_new", + "security_settings_face_preference_summary"); + } + + @Test + public void setSafetySourceData_withFaceEnrolled_whenNotDisabledByAdmin_setsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + assertSafetySourceEnabledDataSetWithSingularSummary( + "security_settings_face_preference_title_new", + "security_settings_face_preference_summary", + Settings.FaceSettingsInternalActivity.class.getName()); + } + + @Test + public void setSafetySourceData_face_whenEnrolled_setsInfoSeverity() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + + FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), eq(FaceSafetySource.SAFETY_SOURCE_ID), captor.capture(), any()); + SafetySourceStatus safetySourceStatus = captor.getValue().getStatus(); + assertThat(safetySourceStatus.getSeverityLevel()) + .isEqualTo(SafetySourceData.SEVERITY_LEVEL_INFORMATION); + } + + @Test + public void setSafetySourceData_face_whenNotEnrolled_setsUnspSeverity() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false); + + FaceSafetySource.setSafetySourceData(mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), eq(FaceSafetySource.SAFETY_SOURCE_ID), captor.capture(), any()); + SafetySourceStatus safetySourceStatus = captor.getValue().getStatus(); + assertThat(safetySourceStatus.getSeverityLevel()) + .isEqualTo(SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED); + } + + private void assertSafetySourceDisabledDataSetWithSingularSummary( + String expectedTitleResName, String expectedSummaryResName) { + assertSafetySourceDisabledDataSet( + ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), + ResourcesUtils.getResourcesString(mApplicationContext, expectedSummaryResName)); + } + + private void assertSafetySourceEnabledDataSetWithSingularSummary( + String expectedTitleResName, + String expectedSummaryResName, + String expectedSettingsClassName) { + assertSafetySourceEnabledDataSet( + ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), + ResourcesUtils.getResourcesString(mApplicationContext, expectedSummaryResName), + expectedSettingsClassName); + } + + private void assertSafetySourceDisabledDataSet(String expectedTitle, String expectedSummary) { + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), eq(FaceSafetySource.SAFETY_SOURCE_ID), captor.capture(), any()); + SafetySourceData safetySourceData = captor.getValue(); + SafetySourceStatus safetySourceStatus = safetySourceData.getStatus(); + + assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle); + assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary); + assertThat(safetySourceStatus.isEnabled()).isFalse(); + assertThat(safetySourceStatus.getSeverityLevel()) + .isEqualTo(SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED); + + Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent(); + assertThat(clickIntent).isNotNull(); + assertThat(clickIntent.getAction()).isEqualTo(ACTION_SHOW_ADMIN_SUPPORT_DETAILS); + } + + private void assertSafetySourceEnabledDataSet( + String expectedTitle, String expectedSummary, String expectedSettingsClassName) { + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), eq(FaceSafetySource.SAFETY_SOURCE_ID), captor.capture(), any()); + SafetySourceData safetySourceData = captor.getValue(); + SafetySourceStatus safetySourceStatus = safetySourceData.getStatus(); + + assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle); + assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary); + assertThat(safetySourceStatus.isEnabled()).isTrue(); + Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent(); + assertThat(clickIntent).isNotNull(); + assertThat(clickIntent.getComponent().getPackageName()).isEqualTo("com.android.settings"); + assertThat(clickIntent.getComponent().getClassName()).isEqualTo(expectedSettingsClassName); + } +} diff --git a/tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java new file mode 100644 index 00000000000..8a34aaa48d3 --- /dev/null +++ b/tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.safetycenter; + +import static android.provider.Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS; +import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintManager; +import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.safetycenter.SafetyEvent; +import android.safetycenter.SafetySourceData; +import android.safetycenter.SafetySourceStatus; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.biometrics.fingerprint.FingerprintSettings; +import com.android.settings.flags.Flags; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.ResourcesUtils; +import com.android.settingslib.utils.StringUtil; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) +@RunWith(AndroidJUnit4.class) +public class FingerprintSafetySourceTest { + + private static final ComponentName COMPONENT_NAME = new ComponentName("package", "class"); + private static final UserHandle USER_HANDLE = new UserHandle(UserHandle.myUserId()); + private static final SafetyEvent EVENT_SOURCE_STATE_CHANGED = + new SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build(); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private Context mApplicationContext; + + @Mock private PackageManager mPackageManager; + @Mock private DevicePolicyManager mDevicePolicyManager; + @Mock private FingerprintManager mFingerprintManager; + @Mock private LockPatternUtils mLockPatternUtils; + @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mApplicationContext = spy(ApplicationProvider.getApplicationContext()); + when(mApplicationContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true); + when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(USER_HANDLE)) + .thenReturn(COMPONENT_NAME); + when(mApplicationContext.getSystemService(Context.FINGERPRINT_SERVICE)) + .thenReturn(mFingerprintManager); + when(mApplicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE)) + .thenReturn(mDevicePolicyManager); + FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); + when(featureFactory.securityFeatureProvider.getLockPatternUtils(mApplicationContext)) + .thenReturn(mLockPatternUtils); + doReturn(true).when(mLockPatternUtils).isSecure(anyInt()); + SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper; + } + + @After + public void tearDown() { + SafetyCenterManagerWrapper.sInstance = null; + } + + @Test + public void setSafetyData_whenSafetyCenterIsDisabled_doesNotSetData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(false); + + FingerprintSafetySource.setSafetySourceData( + mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + verify(mSafetyCenterManagerWrapper, never()) + .setSafetySourceData(any(), any(), any(), any()); + } + + @Test + public void setSafetySourceData_whenSafetyCenterIsEnabled_withoutFingerprint_setsNullData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(false); + + FingerprintSafetySource.setSafetySourceData( + mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), eq(FingerprintSafetySource.SAFETY_SOURCE_ID), eq(null), any()); + } + + @Test + public void setSafetySourceData_setsDataForFingerprintSource() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + FingerprintSafetySource.setSafetySourceData( + mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), eq(FingerprintSafetySource.SAFETY_SOURCE_ID), any(), any()); + } + + @Test + public void setSafetySourceData_setsDataWithCorrectSafetyEvent() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + FingerprintSafetySource.setSafetySourceData( + mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData(any(), any(), any(), eq(EVENT_SOURCE_STATE_CHANGED)); + } + + @Test + public void setSafetySourceData_withFingerprintNotEnrolled_whenDisabledByAdmin_setsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); + + FingerprintSafetySource.setSafetySourceData( + mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + assertSafetySourceDisabledDataSetWithSingularSummary( + "security_settings_fingerprint", + "security_settings_fingerprint_preference_summary_none"); + } + + @Test + public void setSafetySourceData_withFingerprintNotEnrolled_whenNotDisabledByAdmin_setsData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + FingerprintSafetySource.setSafetySourceData( + mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + assertSafetySourceEnabledDataSetWithSingularSummary( + "security_settings_fingerprint", + "security_settings_fingerprint_preference_summary_none", + FingerprintSettings.class.getName()); + } + + @Test + public void setSafetySourceData_withFingerprintsEnrolled_whenDisabledByAdmin_setsData() { + int enrolledFingerprintsCount = 2; + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true); + when(mFingerprintManager.getEnrolledFingerprints(anyInt())) + .thenReturn(createFingerprintList(enrolledFingerprintsCount)); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); + + FingerprintSafetySource.setSafetySourceData( + mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + assertSafetySourceDisabledDataSetWithPluralSummary( + "security_settings_fingerprint", + "security_settings_fingerprint_preference_summary", + enrolledFingerprintsCount); + } + + @Test + public void setSafetySourceData_withFingerprintsEnrolled_whenNotDisabledByAdmin_setsData() { + int enrolledFingerprintsCount = 2; + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true); + when(mFingerprintManager.getEnrolledFingerprints(anyInt())) + .thenReturn(createFingerprintList(enrolledFingerprintsCount)); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0); + + FingerprintSafetySource.setSafetySourceData( + mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + assertSafetySourceEnabledDataSetWithPluralSummary( + "security_settings_fingerprint", + "security_settings_fingerprint_preference_summary", + enrolledFingerprintsCount, + FingerprintSettings.class.getName()); + } + + @Test + public void setSafetySourceData_fingerprint_whenEnrolled_setsInfoSeverity() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true); + + FingerprintSafetySource.setSafetySourceData( + mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), + eq(FingerprintSafetySource.SAFETY_SOURCE_ID), + captor.capture(), + any()); + SafetySourceStatus safetySourceStatus = captor.getValue().getStatus(); + assertThat(safetySourceStatus.getSeverityLevel()) + .isEqualTo(SafetySourceData.SEVERITY_LEVEL_INFORMATION); + } + + @Test + public void setSafetySourceData_fingerprint_whenNotEnrolled_setsUnspSeverity() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); + + FingerprintSafetySource.setSafetySourceData( + mApplicationContext, EVENT_SOURCE_STATE_CHANGED); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), + eq(FingerprintSafetySource.SAFETY_SOURCE_ID), + captor.capture(), + any()); + SafetySourceStatus safetySourceStatus = captor.getValue().getStatus(); + assertThat(safetySourceStatus.getSeverityLevel()) + .isEqualTo(SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED); + } + + private void assertSafetySourceDisabledDataSetWithSingularSummary( + String expectedTitleResName, String expectedSummaryResName) { + assertSafetySourceDisabledDataSet( + ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), + ResourcesUtils.getResourcesString(mApplicationContext, expectedSummaryResName)); + } + + private void assertSafetySourceEnabledDataSetWithSingularSummary( + String expectedTitleResName, + String expectedSummaryResName, + String expectedSettingsClassName) { + assertSafetySourceEnabledDataSet( + ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), + ResourcesUtils.getResourcesString(mApplicationContext, expectedSummaryResName), + expectedSettingsClassName); + } + + private void assertSafetySourceDisabledDataSetWithPluralSummary( + String expectedTitleResName, + String expectedSummaryResName, + int expectedSummaryQuantity) { + int stringResId = + ResourcesUtils.getResourcesId( + ApplicationProvider.getApplicationContext(), + "string", + expectedSummaryResName); + assertSafetySourceDisabledDataSet( + ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), + StringUtil.getIcuPluralsString( + mApplicationContext, expectedSummaryQuantity, stringResId)); + } + + private void assertSafetySourceEnabledDataSetWithPluralSummary( + String expectedTitleResName, + String expectedSummaryResName, + int expectedSummaryQuantity, + String expectedSettingsClassName) { + int stringResId = + ResourcesUtils.getResourcesId( + ApplicationProvider.getApplicationContext(), + "string", + expectedSummaryResName); + assertSafetySourceEnabledDataSet( + ResourcesUtils.getResourcesString(mApplicationContext, expectedTitleResName), + StringUtil.getIcuPluralsString( + mApplicationContext, expectedSummaryQuantity, stringResId), + expectedSettingsClassName); + } + + private void assertSafetySourceDisabledDataSet(String expectedTitle, String expectedSummary) { + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), + eq(FingerprintSafetySource.SAFETY_SOURCE_ID), + captor.capture(), + any()); + SafetySourceData safetySourceData = captor.getValue(); + SafetySourceStatus safetySourceStatus = safetySourceData.getStatus(); + + assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle); + assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary); + assertThat(safetySourceStatus.isEnabled()).isFalse(); + assertThat(safetySourceStatus.getSeverityLevel()) + .isEqualTo(SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED); + + Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent(); + assertThat(clickIntent).isNotNull(); + assertThat(clickIntent.getAction()).isEqualTo(ACTION_SHOW_ADMIN_SUPPORT_DETAILS); + } + + private void assertSafetySourceEnabledDataSet( + String expectedTitle, String expectedSummary, String expectedSettingsClassName) { + ArgumentCaptor captor = ArgumentCaptor.forClass(SafetySourceData.class); + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), + eq(FingerprintSafetySource.SAFETY_SOURCE_ID), + captor.capture(), + any()); + SafetySourceData safetySourceData = captor.getValue(); + SafetySourceStatus safetySourceStatus = safetySourceData.getStatus(); + + assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle); + assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary); + assertThat(safetySourceStatus.isEnabled()).isTrue(); + Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent(); + assertThat(clickIntent).isNotNull(); + assertThat(clickIntent.getComponent().getPackageName()).isEqualTo("com.android.settings"); + assertThat(clickIntent.getComponent().getClassName()).isEqualTo(expectedSettingsClassName); + } + + private List createFingerprintList(int size) { + List fingerprintList = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + fingerprintList.add(new Fingerprint("fingerprint" + i, 0, 0)); + } + return fingerprintList; + } +} diff --git a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java index e528c4e790e..f16113ab13d 100644 --- a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java @@ -29,6 +29,10 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.safetycenter.SafetyEvent; import android.safetycenter.SafetySourceData; import android.safetycenter.SafetySourceIssue; @@ -39,12 +43,14 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.flags.Flags; import com.android.settings.security.ScreenLockPreferenceDetailsUtils; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.ResourcesUtils; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -62,6 +68,9 @@ public class LockScreenSafetySourceTest { private static final SafetyEvent EVENT_SOURCE_STATE_CHANGED = new SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private Context mApplicationContext; @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; @@ -486,7 +495,9 @@ public class LockScreenSafetySourceTest { } @Test - public void onLockScreenChange_whenSafetyCenterEnabled_setsLockscreenAndBiometricData() { + @RequiresFlagsDisabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) + public void + onLockScreenChange_whenSafetyCenterEnabled_flagOff_setsLockscreenAndBiometricData() { whenScreenLockIsEnabled(); when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); @@ -500,6 +511,24 @@ public class LockScreenSafetySourceTest { any(), eq(BiometricsSafetySource.SAFETY_SOURCE_ID), any(), any()); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) + public void onLockScreenChange_whenSafetyCenterEnabled_flagOn_setsLockscreenAndBiometricData() { + whenScreenLockIsEnabled(); + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + + LockScreenSafetySource.onLockScreenChange(mApplicationContext); + + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), any(), any()); + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData(any(), eq(FaceSafetySource.SAFETY_SOURCE_ID), any(), any()); + verify(mSafetyCenterManagerWrapper) + .setSafetySourceData( + any(), eq(FingerprintSafetySource.SAFETY_SOURCE_ID), any(), any()); + } + @Test public void onLockScreenChange_whenSafetyCenterDisabled_doesNotSetData() { whenScreenLockIsEnabled(); diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java index cd4c3c64fe7..a0a532714df 100644 --- a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java @@ -21,8 +21,11 @@ import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOUR import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS; import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED; import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED; + import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -30,7 +33,10 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; -import android.os.Flags; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.safetycenter.SafetyEvent; import android.safetycenter.SafetySourceData; @@ -39,6 +45,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.flags.Flags; import com.android.settings.privatespace.PrivateSpaceSafetySource; import com.android.settings.testutils.FakeFeatureFactory; @@ -51,7 +58,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Arrays; import java.util.List; @RunWith(AndroidJUnit4.class) @@ -61,6 +67,9 @@ public class SafetySourceBroadcastReceiverTest { private Context mApplicationContext; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper; @Mock private LockPatternUtils mLockPatternUtils; @@ -202,6 +211,7 @@ public class SafetySourceBroadcastReceiverTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) public void onReceive_onRefresh_withBiometricsSourceId_setsBiometricData() { when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); Intent intent = @@ -220,9 +230,49 @@ public class SafetySourceBroadcastReceiverTest { assertThat(captor.getValue()).isEqualTo(BiometricsSafetySource.SAFETY_SOURCE_ID); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) + public void onReceive_onRefresh_withFaceUnlockSourceId_setsFaceUnlockData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + Intent intent = + new Intent() + .setAction(ACTION_REFRESH_SAFETY_SOURCES) + .putExtra( + EXTRA_REFRESH_SAFETY_SOURCE_IDS, + new String[] {FaceSafetySource.SAFETY_SOURCE_ID}) + .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID); + + new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(mSafetyCenterManagerWrapper, times(1)) + .setSafetySourceData(any(), captor.capture(), any(), any()); + + assertThat(captor.getValue()).isEqualTo(FaceSafetySource.SAFETY_SOURCE_ID); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) + public void onReceive_onRefresh_withFingerprintUnlockSourceId_setsFingerprintUnlockData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + Intent intent = + new Intent() + .setAction(ACTION_REFRESH_SAFETY_SOURCES) + .putExtra( + EXTRA_REFRESH_SAFETY_SOURCE_IDS, + new String[] {FingerprintSafetySource.SAFETY_SOURCE_ID}) + .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID); + + new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(mSafetyCenterManagerWrapper, times(1)) + .setSafetySourceData(any(), captor.capture(), any(), any()); + + assertThat(captor.getValue()).isEqualTo(FingerprintSafetySource.SAFETY_SOURCE_ID); + } + /** - * Tests that on receiving the refresh broadcast request with the PS source id, the PS data - * is set. + * Tests that on receiving the refresh broadcast request with the PS source id, the PS data is + * set. */ @Test public void onReceive_onRefresh_withPrivateSpaceSourceId_setsPrivateSpaceData() { @@ -247,7 +297,8 @@ public class SafetySourceBroadcastReceiverTest { @Test public void onReceive_onRefresh_withPrivateSpaceFeatureDisabled_setsNullData() { when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); - mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE, + mSetFlagsRule.disableFlags( + android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); Intent intent = @@ -273,16 +324,48 @@ public class SafetySourceBroadcastReceiverTest { new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); ArgumentCaptor captor = ArgumentCaptor.forClass(SafetyEvent.class); - verify(mSafetyCenterManagerWrapper, times(3)) + verify(mSafetyCenterManagerWrapper, atLeastOnce()) .setSafetySourceData(any(), any(), any(), captor.capture()); SafetyEvent bootEvent = new SafetyEvent.Builder(SAFETY_EVENT_TYPE_DEVICE_REBOOTED).build(); - assertThat(captor.getAllValues()) - .containsExactlyElementsIn(Arrays.asList(bootEvent, bootEvent, bootEvent)); + assertThat(captor.getAllValues()).contains(bootEvent); } @Test - public void onReceive_onBootCompleted_sendsAllSafetySourcesData() { + @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) + public void onReceive_onBootCompleted_flagOn_sendsAllSafetySourcesData() { + when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); + Intent intent = new Intent().setAction(Intent.ACTION_BOOT_COMPLETED); + + new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(mSafetyCenterManagerWrapper, times(4)) + .setSafetySourceData(any(), captor.capture(), any(), any()); + List safetySourceIdList = captor.getAllValues(); + + assertThat( + safetySourceIdList.stream() + .anyMatch(id -> id.equals(LockScreenSafetySource.SAFETY_SOURCE_ID))) + .isTrue(); + assertThat( + safetySourceIdList.stream() + .anyMatch(id -> id.equals(FaceSafetySource.SAFETY_SOURCE_ID))) + .isTrue(); + assertThat( + safetySourceIdList.stream() + .anyMatch( + id -> id.equals(FingerprintSafetySource.SAFETY_SOURCE_ID))) + .isTrue(); + assertThat( + safetySourceIdList.stream() + .anyMatch( + id -> id.equals(PrivateSpaceSafetySource.SAFETY_SOURCE_ID))) + .isTrue(); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION) + public void onReceive_onBootCompleted_flagOff_sendsAllSafetySourcesData() { when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true); Intent intent = new Intent().setAction(Intent.ACTION_BOOT_COMPLETED); @@ -292,11 +375,18 @@ public class SafetySourceBroadcastReceiverTest { .setSafetySourceData(any(), captor.capture(), any(), any()); List safetySourceIdList = captor.getAllValues(); - assertThat(safetySourceIdList.stream().anyMatch( - id -> id.equals(LockScreenSafetySource.SAFETY_SOURCE_ID))).isTrue(); - assertThat(safetySourceIdList.stream().anyMatch( - id -> id.equals(BiometricsSafetySource.SAFETY_SOURCE_ID))).isTrue(); - assertThat(safetySourceIdList.stream().anyMatch( - id -> id.equals(PrivateSpaceSafetySource.SAFETY_SOURCE_ID))).isTrue(); + assertThat( + safetySourceIdList.stream() + .anyMatch(id -> id.equals(LockScreenSafetySource.SAFETY_SOURCE_ID))) + .isTrue(); + assertThat( + safetySourceIdList.stream() + .anyMatch(id -> id.equals(BiometricsSafetySource.SAFETY_SOURCE_ID))) + .isTrue(); + assertThat( + safetySourceIdList.stream() + .anyMatch( + id -> id.equals(PrivateSpaceSafetySource.SAFETY_SOURCE_ID))) + .isTrue(); } } From 63b02907cc8a4523eecbf777f91ed2768fd24d78 Mon Sep 17 00:00:00 2001 From: Lorenzo Lucena Maguire Date: Fri, 10 Jan 2025 19:51:09 +0000 Subject: [PATCH 6/8] Update Strings for Double Press Power Button Gesture Android Settings Feature Request: b/380287172 Test: manually verified strings updated Flag: EXEMPT xml only Bug: 389122652 Change-Id: I0e683f87364145690229c1c73dc56f6c8b119c62 --- res/values/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index c3fb2f94ead..0415b397b70 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -11285,19 +11285,19 @@ To quickly open camera, press the power button twice. Works from any screen. - Double tap power button + Double press power button %1$s / %2$s - Use double tap + Use double press - Double Tap Power Button + Double press power button to open - Open Camera + Camera Access Camera - Open Wallet + Wallet Access Wallet From f761a300403b27fcd555e5b90b702428279d1589 Mon Sep 17 00:00:00 2001 From: Haijie Hong Date: Mon, 13 Jan 2025 16:17:09 +0800 Subject: [PATCH 7/8] Fix exception in key missing dialog when rotating screen BUG: 387915075 Test: atest BluetoothKeyMissingDialogTest Flag: com.android.settings.flags.enable_bluetooth_key_missing_dialog Change-Id: I966954f27d074a5ca0dc329cb142c1ab66b3b013 --- .../bluetooth/BluetoothKeyMissingDialog.java | 3 ++- .../BluetoothKeyMissingDialogFragment.java | 17 ++++++++++++++--- .../BluetoothKeyMissingDialogTest.java | 16 ++++++++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothKeyMissingDialog.java b/src/com/android/settings/bluetooth/BluetoothKeyMissingDialog.java index 46975f77726..fa43c24ad14 100644 --- a/src/com/android/settings/bluetooth/BluetoothKeyMissingDialog.java +++ b/src/com/android/settings/bluetooth/BluetoothKeyMissingDialog.java @@ -40,7 +40,8 @@ public class BluetoothKeyMissingDialog extends FragmentActivity { finish(); return; } - BluetoothKeyMissingDialogFragment fragment = new BluetoothKeyMissingDialogFragment(device); + BluetoothKeyMissingDialogFragment fragment = + BluetoothKeyMissingDialogFragment.newInstance(device); fragment.show(getSupportFragmentManager(), FRAGMENT_TAG); closeSystemDialogs(); } diff --git a/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogFragment.java index a8e3aae175a..342af34338e 100644 --- a/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogFragment.java @@ -31,6 +31,7 @@ import androidx.appcompat.app.AlertDialog; import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settingslib.bluetooth.LocalBluetoothManager; /** * A dialogFragment used by {@link BluetoothKeyMissingDialog} to create a dialog for the @@ -40,16 +41,26 @@ public class BluetoothKeyMissingDialogFragment extends InstrumentedDialogFragmen implements OnClickListener { private static final String TAG = "BTKeyMissingDialogFragment"; + private static final String KEY_CACHED_DEVICE_ADDRESS = "cached_device"; private BluetoothDevice mBluetoothDevice; - public BluetoothKeyMissingDialogFragment(@NonNull BluetoothDevice bluetoothDevice) { - mBluetoothDevice = bluetoothDevice; + /** Creates a new instant of the fragment. */ + public static BluetoothKeyMissingDialogFragment newInstance(BluetoothDevice device) { + Bundle args = new Bundle(1); + args.putString(KEY_CACHED_DEVICE_ADDRESS, device.getAddress()); + BluetoothKeyMissingDialogFragment fragment = new BluetoothKeyMissingDialogFragment(); + fragment.setArguments(args); + return fragment; } @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + String deviceAddress = getArguments().getString(KEY_CACHED_DEVICE_ADDRESS); + LocalBluetoothManager manager = Utils.getLocalBtManager(getContext()); + mBluetoothDevice = manager.getBluetoothAdapter().getRemoteDevice(deviceAddress); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); View view = getActivity().getLayoutInflater().inflate(R.layout.bluetooth_key_missing, null); TextView keyMissingTitle = view.findViewById(R.id.bluetooth_key_missing_title); @@ -66,7 +77,7 @@ public class BluetoothKeyMissingDialogFragment extends InstrumentedDialogFragmen @Override public void onDestroy() { super.onDestroy(); - if (!getActivity().isFinishing()) { + if (!getActivity().isChangingConfigurations() && !getActivity().isFinishing()) { getActivity().finish(); } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogTest.java index a47101e7b79..14e263c0fb2 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; import android.bluetooth.BluetoothDevice; @@ -27,10 +28,13 @@ import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; @@ -38,18 +42,26 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowAlertDialogCompat.class) +@Config(shadows = {ShadowAlertDialogCompat.class, ShadowBluetoothUtils.class}) public class BluetoothKeyMissingDialogTest { @Mock private BluetoothDevice mBluetoothDevice; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private LocalBluetoothManager mLocalBtManager; private BluetoothKeyMissingDialogFragment mFragment = null; private FragmentActivity mActivity = null; + private static final String MAC_ADDRESS = "12:34:56:78:90:12"; + @Before public void setUp() { MockitoAnnotations.initMocks(this); + when(mBluetoothDevice.getAddress()).thenReturn(MAC_ADDRESS); + when(mLocalBtManager.getBluetoothAdapter().getRemoteDevice(MAC_ADDRESS)) + .thenReturn(mBluetoothDevice); + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager; mActivity = Robolectric.setupActivity(FragmentActivity.class); - mFragment = new BluetoothKeyMissingDialogFragment(mBluetoothDevice); + mFragment = BluetoothKeyMissingDialogFragment.newInstance(mBluetoothDevice); mActivity .getSupportFragmentManager() .beginTransaction() From d683d1813275ce86d5b64367bd42744c179dbafc Mon Sep 17 00:00:00 2001 From: Wenyu Zhang Date: Mon, 13 Jan 2025 21:31:03 +0000 Subject: [PATCH 8/8] a11y: Add "Ignore minor cursor movement" autoclick setting When "Ignore minor cursor movement" is on, the small cursor movement within the cursor area ring will not interrupt the autoclick timer. Screenshot: http://b/388845718#comment2 Change-Id: Ib6a8ca8463358a3e2de5ae6be8afd5113a5826ba Test: ToggleAutoclickIgnoreMinorCursorMovementControllerTest Bug: b/388845718 Flag: com.android.server.accessibility.enable_autoclick_indicator --- res/values/strings.xml | 3 + res/xml/accessibility_autoclick_settings.xml | 5 ++ ...ckIgnoreMinorCursorMovementController.java | 58 ++++++++++++++++ ...noreMinorCursorMovementControllerTest.java | 69 +++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 src/com/android/settings/accessibility/ToggleAutoclickIgnoreMinorCursorMovementController.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickIgnoreMinorCursorMovementControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 236959d2530..dc9d0c44f56 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5594,6 +5594,9 @@ Adjust the autoclick ring indicator area size + + + Ignore minor cursor movement Vibration & haptics diff --git a/res/xml/accessibility_autoclick_settings.xml b/res/xml/accessibility_autoclick_settings.xml index d3a951479ad..edcc29bdf9c 100644 --- a/res/xml/accessibility_autoclick_settings.xml +++ b/res/xml/accessibility_autoclick_settings.xml @@ -84,6 +84,11 @@ settings:searchable="false" settings:controller="com.android.settings.accessibility.ToggleAutoclickCursorAreaSizeController"/> + +