From 1dd4f54d587c67b3f665b423f3157223bd8e06f7 Mon Sep 17 00:00:00 2001 From: Joshua McCloskey Date: Mon, 18 Apr 2022 19:16:10 +0000 Subject: [PATCH 1/7] SUW enrolls FP before Face Test: During SUW verified Fingerprint enrollment comes before face. Test: During SUW enrolled multiple fingerprints than 1 face. Test: Skipped and cancelled on every possible screen to ensure behavior was correct. Bug: 228607474 Change-Id: I4c50763a804fe4eb9d62451eb2f957545857723e --- .../biometrics/BiometricEnrollActivity.java | 7 --- .../biometrics/BiometricEnrollBase.java | 1 + .../BiometricEnrollIntroduction.java | 42 +++++++++++--- .../settings/biometrics/BiometricUtils.java | 56 +++++++++++++++---- .../MultiBiometricEnrollHelper.java | 34 +++++------ .../face/FaceEnrollIntroduction.java | 24 ++++++++ .../fingerprint/FingerprintEnrollFinish.java | 7 +-- .../FingerprintEnrollIntroduction.java | 31 +++++----- .../SetupFingerprintEnrollFindSensor.java | 2 + .../SetupFingerprintEnrollIntroduction.java | 22 +++++--- 10 files changed, 158 insertions(+), 68 deletions(-) diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java index c74e85e54c2..dda3997c28a 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java +++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java @@ -32,7 +32,6 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.BiometricManager.BiometricError; -import android.hardware.biometrics.SensorProperties; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; @@ -211,12 +210,6 @@ public class BiometricEnrollActivity extends InstrumentedActivity { // required check if setup has completed instead. final boolean isSettingUp = isSetupWizard || (mParentalOptionsRequired && !WizardManagerHelper.isUserSetupComplete(this)); - if (isSettingUp && isMultiSensor && mIsFaceEnrollable) { - if (props.sensorStrength == SensorProperties.STRENGTH_CONVENIENCE) { - Log.i(TAG, "Excluding face from SuW enrollment (STRENGTH_CONVENIENCE)"); - mIsFaceEnrollable = false; - } - } } } if (mHasFeatureFingerprint) { diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java index 6e7d04f26b5..98cface2bc1 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollBase.java +++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java @@ -254,6 +254,7 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity { intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary); intent.putExtra(EXTRA_KEY_CHALLENGE, mChallenge); intent.putExtra(EXTRA_KEY_SENSOR_ID, mSensorId); + BiometricUtils.copyMultiBiometricExtras(getIntent(), intent); if (mUserId != UserHandle.USER_NULL) { intent.putExtra(Intent.EXTRA_USER_ID, mUserId); } diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java index e3607601840..61783c5164c 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java @@ -24,7 +24,6 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; -import android.util.Log; import android.view.View; import android.widget.TextView; @@ -303,9 +302,17 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + final boolean cameFromMultiBioFpAuthAddAnother = + requestCode == BiometricUtils.REQUEST_ADD_ANOTHER + && BiometricUtils.isMultiBiometricFingerprintEnrollmentFlow(this); if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST) { - if (isResultSkipOrFinished(resultCode)) { + if (isResultFinished(resultCode)) { handleBiometricResultSkipOrFinished(resultCode, data); + } else if (isResultSkipped(resultCode)) { + if (!BiometricUtils.tryStartingNextBiometricEnroll(this, + ENROLL_NEXT_BIOMETRIC_REQUEST, "BIOMETRIC_FIND_SENSOR_SKIPPED")) { + handleBiometricResultSkipOrFinished(resultCode, data); + } } else if (resultCode == RESULT_TIMEOUT) { setResult(resultCode, data); finish(); @@ -353,10 +360,22 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase } } else if (requestCode == LEARN_MORE_REQUEST) { overridePendingTransition(R.anim.sud_slide_back_in, R.anim.sud_slide_back_out); - } else if (requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST) { - Log.d(TAG, "ENROLL_NEXT_BIOMETRIC_REQUEST, result: " + resultCode); - if (isResultSkipOrFinished(resultCode)) { + } else if (requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST + || cameFromMultiBioFpAuthAddAnother) { + if (isResultFinished(resultCode)) { handleBiometricResultSkipOrFinished(resultCode, data); + } else if (isResultSkipped(resultCode)) { + if (requestCode == BiometricUtils.REQUEST_ADD_ANOTHER) { + // If we came from an add another request, it still might + // be possible to add another biometric. Check if we can. + if (checkMaxEnrolled() != 0) { + // If we can't enroll any more biometrics, than skip + // this one. + handleBiometricResultSkipOrFinished(resultCode, data); + } + } else { + handleBiometricResultSkipOrFinished(resultCode, data); + } } else if (resultCode != RESULT_CANCELED) { setResult(resultCode, data); finish(); @@ -365,9 +384,17 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase super.onActivityResult(requestCode, resultCode, data); } + private static boolean isResultSkipped(int resultCode) { + return resultCode == RESULT_SKIP + || resultCode == SetupSkipDialog.RESULT_SKIP; + } + + private static boolean isResultFinished(int resultCode) { + return resultCode == RESULT_FINISHED; + } + private static boolean isResultSkipOrFinished(int resultCode) { - return resultCode == RESULT_SKIP || resultCode == SetupSkipDialog.RESULT_SKIP - || resultCode == RESULT_FINISHED; + return isResultSkipped(resultCode) || isResultFinished(resultCode); } private void handleBiometricResultSkipOrFinished(int resultCode, @Nullable Intent data) { @@ -375,6 +402,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase && data.getBooleanExtra( MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, false)) { getIntent().removeExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE); + getIntent().removeExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT); } if (resultCode == RESULT_SKIP) { diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java index febe3c6a93e..be233ed466b 100644 --- a/src/com/android/settings/biometrics/BiometricUtils.java +++ b/src/com/android/settings/biometrics/BiometricUtils.java @@ -51,6 +51,12 @@ import com.google.android.setupcompat.util.WizardManagerHelper; */ public class BiometricUtils { private static final String TAG = "BiometricUtils"; + + /** + * Request was sent for starting another enrollment of a previously + * enrolled biometric of the same type. + */ + public static int REQUEST_ADD_ANOTHER = 7; /** * Given the result from confirming or choosing a credential, request Gatekeeper to generate * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge. @@ -223,38 +229,66 @@ public class BiometricUtils { } /** + * Used for checking if a multi-biometric enrollment flow starts with Face and + * ends with Fingerprint. + * * @param activity Activity that we want to check - * @return True if the activity is going through a multi-biometric enrollment flow. + * @return True if the activity is going through a multi-biometric enrollment flow, that starts + * with Face. */ - public static boolean isMultiBiometricEnrollmentFlow(@NonNull Activity activity) { + public static boolean isMultiBiometricFaceEnrollmentFlow(@NonNull Activity activity) { return activity.getIntent().hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE); } + + /** + * Used for checking if a multi-biometric enrollment flowstarts with Fingerprint + * and ends with Face. + * + * @param activity Activity that we want to check + * @return True if the activity is going through a multi-biometric enrollment flow, that starts + * with Fingerprint. + */ + public static boolean isMultiBiometricFingerprintEnrollmentFlow(@NonNull Activity activity) { + return activity.getIntent().hasExtra( + MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT); + } + public static void copyMultiBiometricExtras(@NonNull Intent fromIntent, @NonNull Intent toIntent) { - final PendingIntent pendingIntent = (PendingIntent) fromIntent.getExtra( + PendingIntent pendingIntent = (PendingIntent) fromIntent.getExtra( MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, null); if (pendingIntent != null) { - toIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, pendingIntent); + toIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, + pendingIntent); + } + + pendingIntent = (PendingIntent) fromIntent.getExtra( + MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT, null); + if (pendingIntent != null) { + toIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT, + pendingIntent); } } /** - * If the current biometric enrollment (e.g. face) should be followed by another one (e.g. - * fingerprint) (see {@link #isMultiBiometricEnrollmentFlow(Activity)}), retrieves the - * PendingIntent pointing to the next enrollment and starts it. The caller will receive the - * result in onActivityResult. + * If the current biometric enrollment (e.g. face/fingerprint) should be followed by another + * one (e.g. fingerprint/face) retrieves the PendingIntent pointing to the next enrollment + * and starts it. The caller will receive the result in onActivityResult. * @return true if the next enrollment was started */ public static boolean tryStartingNextBiometricEnroll(@NonNull Activity activity, int requestCode, String debugReason) { - Log.d(TAG, "tryStartingNextBiometricEnroll, debugReason: " + debugReason); - final PendingIntent pendingIntent = (PendingIntent) activity.getIntent() + PendingIntent pendingIntent = (PendingIntent) activity.getIntent() .getExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE); + if (pendingIntent == null) { + pendingIntent = (PendingIntent) activity.getIntent() + .getExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT); + } + if (pendingIntent != null) { try { - Log.d(TAG, "Starting pendingIntent: " + pendingIntent); IntentSender intentSender = pendingIntent.getIntentSender(); activity.startIntentSenderForResult(intentSender, requestCode, null /* fillInIntent */, 0 /* flagMask */, 0 /* flagValues */, diff --git a/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java b/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java index 5cc45b1b34e..94fbb76ac60 100644 --- a/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java +++ b/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java @@ -37,6 +37,7 @@ public class MultiBiometricEnrollHelper { private static final int REQUEST_FINGERPRINT_ENROLL = 3001; public static final String EXTRA_ENROLL_AFTER_FACE = "enroll_after_face"; + public static final String EXTRA_ENROLL_AFTER_FINGERPRINT = "enroll_after_finger"; public static final String EXTRA_SKIP_PENDING_ENROLL = "skip_pending_enroll"; @NonNull private final FragmentActivity mActivity; @@ -55,10 +56,10 @@ public class MultiBiometricEnrollHelper { } void startNextStep() { - if (mRequestEnrollFace) { - launchFaceEnroll(); - } else if (mRequestEnrollFingerprint) { + if (mRequestEnrollFingerprint) { launchFingerprintEnroll(); + } else if (mRequestEnrollFace) { + launchFaceEnroll(); } else { mActivity.setResult(BiometricEnrollIntroduction.RESULT_SKIP); mActivity.finish(); @@ -74,20 +75,6 @@ public class MultiBiometricEnrollHelper { mActivity.getIntent()); faceIntent.putExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId); faceIntent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge); - - if (mRequestEnrollFingerprint) { - // Give FaceEnroll a pendingIntent pointing to fingerprint enrollment, so that it - // can be started when user skips or finishes face enrollment. FLAG_UPDATE_CURRENT - // ensures it is launched with the most recent values. - final Intent fpIntent = BiometricUtils.getFingerprintIntroIntent(mActivity, - mActivity.getIntent()); - fpIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle); - final PendingIntent fpAfterFaceIntent = PendingIntent.getActivity(mActivity, - 0 /* requestCode */, fpIntent, - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); - faceIntent.putExtra(EXTRA_ENROLL_AFTER_FACE, fpAfterFaceIntent); - } - BiometricUtils.launchEnrollForResult(mActivity, faceIntent, REQUEST_FACE_ENROLL, hardwareAuthToken, mGkPwHandle, mUserId); }); @@ -103,6 +90,19 @@ public class MultiBiometricEnrollHelper { mActivity.getIntent()); intent.putExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId); intent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge); + if (mRequestEnrollFace) { + // Give FingerprintEnroll a pendingIntent pointing to face enrollment, so that it + // can be started when user skips or finishes fingerprint enrollment. + // FLAG_UPDATE_CURRENT ensures it is launched with the most recent values. + final Intent faceIntent = BiometricUtils.getFaceIntroIntent(mActivity, + mActivity.getIntent()); + faceIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle); + final PendingIntent faceAfterFp = PendingIntent.getActivity(mActivity, + 0 /* requestCode */, faceIntent, + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); + intent.putExtra(EXTRA_ENROLL_AFTER_FINGERPRINT, faceAfterFp); + } + BiometricUtils.launchEnrollForResult(mActivity, intent, REQUEST_FINGERPRINT_ENROLL, hardwareAuthToken, mGkPwHandle, mUserId); })); diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java index efb200fa8ac..f8f34f91092 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java @@ -41,8 +41,10 @@ import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollActivity; import com.android.settings.biometrics.BiometricEnrollIntroduction; import com.android.settings.biometrics.BiometricUtils; +import com.android.settings.biometrics.MultiBiometricEnrollHelper; import com.android.settings.overlay.FeatureFactory; import com.android.settings.password.ChooseLockSettingsHelper; +import com.android.settings.password.SetupSkipDialog; import com.android.settings.utils.SensorPrivacyManagerHelper; import com.android.settingslib.RestrictedLockUtilsInternal; @@ -167,6 +169,19 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { Log.v(TAG, "cameraPrivacyEnabled : " + cameraPrivacyEnabled); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // If user has skipped or finished enrolling, don't restart enrollment. + final boolean isEnrollRequest = requestCode == BIOMETRIC_FIND_SENSOR_REQUEST + || requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST; + final boolean isResultSkipOrFinished = resultCode == RESULT_SKIP + || resultCode == SetupSkipDialog.RESULT_SKIP || resultCode == RESULT_FINISHED; + if (isEnrollRequest && isResultSkipOrFinished) { + data = setSkipPendingEnroll(data); + } + super.onActivityResult(requestCode, resultCode, data); + } + protected boolean generateChallengeOnCreate() { return true; } @@ -387,4 +402,13 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { protected int getMoreButtonTextRes() { return R.string.security_settings_face_enroll_introduction_more; } + + @NonNull + protected static Intent setSkipPendingEnroll(@Nullable Intent data) { + if (data == null) { + data = new Intent(); + } + data.putExtra(MultiBiometricEnrollHelper.EXTRA_SKIP_PENDING_ENROLL, true); + return data; + } } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java index 74e844abfda..784c5438a38 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java @@ -31,6 +31,7 @@ import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollBase; +import com.android.settings.biometrics.BiometricUtils; import com.android.settings.password.ChooseLockSettingsHelper; import com.google.android.setupcompat.template.FooterBarMixin; @@ -47,8 +48,6 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase { private static final String ACTION_FINGERPRINT_SETTINGS = "android.settings.FINGERPRINT_SETTINGS"; @VisibleForTesting - static final int REQUEST_ADD_ANOTHER = 1; - @VisibleForTesting static final String FINGERPRINT_SUGGESTION_ACTIVITY = "com.android.settings.SetupFingerprintSuggestionActivity"; @@ -160,13 +159,13 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase { } private void onAddAnotherButtonClick(View view) { - startActivityForResult(getFingerprintEnrollingIntent(), REQUEST_ADD_ANOTHER); + startActivityForResult(getFingerprintEnrollingIntent(), BiometricUtils.REQUEST_ADD_ANOTHER); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { updateFingerprintSuggestionEnableState(); - if (requestCode == REQUEST_ADD_ANOTHER && resultCode != RESULT_CANCELED) { + if (requestCode == BiometricUtils.REQUEST_ADD_ANOTHER && resultCode != RESULT_CANCELED) { setResult(resultCode, data); finish(); } else { diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java index 6fe14e6a9cd..b9e9dcc5149 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java @@ -42,7 +42,6 @@ import com.android.settings.biometrics.BiometricEnrollIntroduction; import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.MultiBiometricEnrollHelper; import com.android.settings.password.ChooseLockSettingsHelper; -import com.android.settings.password.SetupSkipDialog; import com.android.settingslib.HelpUtils; import com.android.settingslib.RestrictedLockUtilsInternal; @@ -106,28 +105,31 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - // If user has skipped or finished enrolling, don't restart enrollment. - final boolean isEnrollRequest = requestCode == BIOMETRIC_FIND_SENSOR_REQUEST - || requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST; - final boolean isResultSkipOrFinished = resultCode == RESULT_SKIP - || resultCode == SetupSkipDialog.RESULT_SKIP || resultCode == RESULT_FINISHED; - if (isEnrollRequest && isResultSkipOrFinished) { - data = setSkipPendingEnroll(data); - } super.onActivityResult(requestCode, resultCode, data); } @Override protected void onCancelButtonClick(View view) { - // User has explicitly canceled enroll. Don't restart it automatically. - Intent data = setSkipPendingEnroll(new Intent()); - setResult(RESULT_SKIP, data); - finish(); + if (!BiometricUtils.tryStartingNextBiometricEnroll( + this, ENROLL_NEXT_BIOMETRIC_REQUEST, "cancel")) { + super.onCancelButtonClick(view); + } } @Override protected void onSkipButtonClick(View view) { - onCancelButtonClick(view); + if (!BiometricUtils.tryStartingNextBiometricEnroll( + this, ENROLL_NEXT_BIOMETRIC_REQUEST, "skipped")) { + super.onSkipButtonClick(view); + } + } + + @Override + protected void onFinishedEnrolling(@Nullable Intent data) { + if (!BiometricUtils.tryStartingNextBiometricEnroll( + this, ENROLL_NEXT_BIOMETRIC_REQUEST, "finished")) { + super.onFinishedEnrolling(data); + } } @StringRes @@ -269,6 +271,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { @Override protected Intent getEnrollingIntent() { final Intent intent = new Intent(this, FingerprintEnrollFindSensor.class); + BiometricUtils.copyMultiBiometricExtras(getIntent(), intent); if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, BiometricUtils.getGatekeeperPasswordHandle(getIntent())); diff --git a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java index 5f2fbb553db..7256511d2e6 100644 --- a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollFindSensor.java @@ -31,6 +31,7 @@ import androidx.fragment.app.FragmentManager; import com.android.settings.R; import com.android.settings.SetupWizardUtils; +import com.android.settings.biometrics.BiometricUtils; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.password.ChooseLockSettingsHelper; @@ -45,6 +46,7 @@ public class SetupFingerprintEnrollFindSensor extends FingerprintEnrollFindSenso if (mUserId != UserHandle.USER_NULL) { intent.putExtra(Intent.EXTRA_USER_ID, mUserId); } + BiometricUtils.copyMultiBiometricExtras(getIntent(), intent); SetupWizardUtils.copySetupExtras(getIntent(), intent); return intent; } diff --git a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java index 7d8d12e8cb5..e88e50d9425 100644 --- a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java @@ -62,8 +62,10 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu @Override protected Intent getEnrollingIntent() { final Intent intent = new Intent(this, SetupFingerprintEnrollFindSensor.class); + BiometricUtils.copyMultiBiometricExtras(getIntent(), intent); if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { - intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, + intent.putExtra( + ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, BiometricUtils.getGatekeeperPasswordHandle(getIntent())); } SetupWizardUtils.copySetupExtras(getIntent(), intent); @@ -118,18 +120,22 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu if (isKeyguardSecure()) { // If the keyguard is already set up securely (maybe the user added a backup screen // lock and skipped fingerprint), return RESULT_SKIP directly. - resultCode = RESULT_SKIP; - data = mAlreadyHadLockScreenSetup ? null : getMetricIntent(null); + if (!BiometricUtils.tryStartingNextBiometricEnroll( + this, ENROLL_NEXT_BIOMETRIC_REQUEST, "cancel")) { + resultCode = RESULT_SKIP; + data = mAlreadyHadLockScreenSetup ? null : getMetricIntent(null); + setResult(resultCode, data); + finish(); + return; + } } else { resultCode = SetupSkipDialog.RESULT_SKIP; - data = null; + data = setSkipPendingEnroll(null); + setResult(resultCode, data); + finish(); } // User has explicitly canceled enroll. Don't restart it automatically. - data = setSkipPendingEnroll(data); - - setResult(resultCode, data); - finish(); } /** From 7ae0b641ddd8cff4fd51a698be9fc9cef4445320 Mon Sep 17 00:00:00 2001 From: menghanli Date: Tue, 19 Apr 2022 08:11:34 +0800 Subject: [PATCH 2/7] Fix transparency seekbar is enabled while fade feature is disabled. Root cause: Show default status until setting key changed. It does not show latest status when created. Solution: Update the status when created. Bug: 229566130 Test: make RunSettingsRoboTests ROBOTEST_FILTER=FloatingMenuTransparencyPreferenceControllerTest Change-Id: Ia25fe5cec0444c1771e8ce31aae2a4cb3b0405a1 --- ...gMenuTransparencyPreferenceController.java | 5 +- ...uTransparencyPreferenceControllerTest.java | 68 ++++++++++++++++++- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/accessibility/FloatingMenuTransparencyPreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuTransparencyPreferenceController.java index 894d3aea2d4..9f698f20874 100644 --- a/src/com/android/settings/accessibility/FloatingMenuTransparencyPreferenceController.java +++ b/src/com/android/settings/accessibility/FloatingMenuTransparencyPreferenceController.java @@ -51,9 +51,7 @@ public class FloatingMenuTransparencyPreferenceController extends SliderPreferen private final ContentResolver mContentResolver; @VisibleForTesting final ContentObserver mContentObserver; - - @VisibleForTesting - SeekBarPreference mPreference; + private SeekBarPreference mPreference; public FloatingMenuTransparencyPreferenceController(Context context, String preferenceKey) { @@ -83,6 +81,7 @@ public class FloatingMenuTransparencyPreferenceController extends SliderPreferen mPreference.setMin(getMin()); mPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS); + updateAvailabilityStatus(); updateState(mPreference); } diff --git a/tests/robotests/src/com/android/settings/accessibility/FloatingMenuTransparencyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/FloatingMenuTransparencyPreferenceControllerTest.java index 55637cd137d..eceb185151c 100644 --- a/tests/robotests/src/com/android/settings/accessibility/FloatingMenuTransparencyPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/FloatingMenuTransparencyPreferenceControllerTest.java @@ -27,6 +27,7 @@ import static com.android.settings.core.BasePreferenceController.DISABLED_DEPEND import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -34,6 +35,7 @@ import android.content.ContentResolver; import android.content.Context; import android.provider.Settings; +import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; import com.android.settings.widget.SeekBarPreference; @@ -60,11 +62,18 @@ public class FloatingMenuTransparencyPreferenceControllerTest { @Mock private ContentResolver mContentResolver; private FloatingMenuTransparencyPreferenceController mController; + private SeekBarPreference mSeekBarPreference; + + @Mock + private PreferenceScreen mScreen; @Before public void setUp() { when(mContext.getContentResolver()).thenReturn(mContentResolver); mController = new FloatingMenuTransparencyPreferenceController(mContext, "test_key"); + + mSeekBarPreference = new SeekBarPreference(mContext); + doReturn(mSeekBarPreference).when(mScreen).findPreference("test_key"); } @Test @@ -84,14 +93,67 @@ public class FloatingMenuTransparencyPreferenceControllerTest { } @Test - public void onChange_a11yBtnModeChangeToNavigationBar_preferenceDisabled() { - mController.mPreference = new SeekBarPreference(mContext); + public void displayPreference_floatingMenuMode_fadeEnabled_preferenceEnabled() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + Settings.Secure.putInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, /* ON */ 1); + + mController.displayPreference(mScreen); + + assertThat(mSeekBarPreference.isEnabled()).isTrue(); + } + + @Test + public void displayPreference_floatingMenuMode_fadeDisabled_preferenceDisabled() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + Settings.Secure.putInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, /* OFF */ 0); + + mController.displayPreference(mScreen); + + assertThat(mSeekBarPreference.isEnabled()).isFalse(); + } + + @Test + public void displayPreference_navigationBarMode_preferenceDisabled() { Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + mController.displayPreference(mScreen); + + assertThat(mSeekBarPreference.isEnabled()).isFalse(); + } + + @Test + public void onChange_floatingMenuModeChangeToNavigationBar_preferenceDisabled() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + Settings.Secure.putInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, /* ON */ 1); + mController.displayPreference(mScreen); + + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); mController.mContentObserver.onChange(false); - assertThat(mController.mPreference.isEnabled()).isFalse(); + assertThat(mSeekBarPreference.isEnabled()).isFalse(); + } + + @Test + public void onChange_navigationBarModeChangeToFloatingMenu_preferenceDisabled() { + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR); + Settings.Secure.putInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, /* ON */ 1); + mController.displayPreference(mScreen); + + Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU); + mController.mContentObserver.onChange(false); + + assertThat(mSeekBarPreference.isEnabled()).isTrue(); } @Test From a4f30404f0402ff8467c5ceee39c499030cb55d4 Mon Sep 17 00:00:00 2001 From: Milton Wu Date: Mon, 18 Apr 2022 17:18:57 +0800 Subject: [PATCH 3/7] Not relaunch BiometricEnrollActivity durng SUW Biometric fingerprint flow is abnormal because BiometricEnrollActivity is relaunched during SUW because diplay or orientation is changed. Bug: 205059704 Bug: 228812876 Test: atest BiometricEnrollActivityTest Test: Before clicking "No thanks" manully unfold or rotate device on the fingerprint setup screen of SUW Change-Id: If0abe785c74c7fb40da779af166d79eeba6ad57f --- AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0ed126f611e..697d2c49557 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2226,6 +2226,7 @@ From 61022c6419c5ebd71c2b32cb6d40d377bace234f Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Mon, 18 Apr 2022 15:35:52 +0800 Subject: [PATCH 4/7] Fix UiBlcoker regression We determine if the visibility should be updated by mIsFirstLaunch, and mIsFirstLaunch is set to false in onStop(). This breaks the updatability if there's a change before onStop() gets called. So we move the value assignment to the end of checkUiBlocker() because at that time, all blocker work should be either finished or timeout. Also remove mIsFirstLaunch since mUiBlockerFinished can cover the determination with the new design. Fixes: 229565193 Test: Toggle Use Location in the location page and see the Recent used section react accordingly. Change-Id: Id6005e5b8b29cb8a6309068d0a2177489a3fb2f4 --- .../settings/core/BasePreferenceController.java | 11 +---------- .../settings/dashboard/DashboardFragment.java | 12 ++++++------ 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/com/android/settings/core/BasePreferenceController.java b/src/com/android/settings/core/BasePreferenceController.java index 6cc09e23369..5763d3b93ef 100644 --- a/src/com/android/settings/core/BasePreferenceController.java +++ b/src/com/android/settings/core/BasePreferenceController.java @@ -126,7 +126,6 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl @Nullable private UserHandle mWorkProfileUser; private int mMetricsCategory; - private boolean mIsFirstLaunch; private boolean mPrefVisibility; /** @@ -198,7 +197,6 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl public BasePreferenceController(Context context, String preferenceKey) { super(context); mPreferenceKey = preferenceKey; - mIsFirstLaunch = true; mPrefVisibility = true; if (TextUtils.isEmpty(mPreferenceKey)) { throw new IllegalArgumentException("Preference key must be set"); @@ -331,13 +329,6 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl } } - /** - * Set back the value of whether this is the first launch. - */ - public void revokeFirstLaunch() { - mIsFirstLaunch = false; - } - /** * Launches the specified fragment for the work profile user if the associated * {@link Preference} is clicked. Otherwise just forward it to the super class. @@ -454,7 +445,7 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl * preference visibility. */ protected void updatePreferenceVisibilityDelegate(Preference preference, boolean isVisible) { - if (mUiBlockerFinished || !mIsFirstLaunch) { + if (mUiBlockerFinished) { preference.setVisible(isVisible); return; } diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index 256a6201cb7..aaa9b3d3bca 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -132,17 +132,22 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment @VisibleForTesting void checkUiBlocker(List controllers) { final List keys = new ArrayList<>(); + final List baseControllers = new ArrayList<>(); controllers.forEach(controller -> { if (controller instanceof BasePreferenceController.UiBlocker && controller.isAvailable()) { ((BasePreferenceController) controller).setUiBlockListener(this); keys.add(controller.getPreferenceKey()); + baseControllers.add((BasePreferenceController) controller); } }); if (!keys.isEmpty()) { mBlockerController = new UiBlockerController(keys); - mBlockerController.start(() -> updatePreferenceVisibility(mPreferenceControllers)); + mBlockerController.start(() -> { + updatePreferenceVisibility(mPreferenceControllers); + baseControllers.forEach(controller -> controller.setUiBlockerFinished(true)); + }); } } @@ -250,11 +255,6 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment } mListeningToCategoryChange = false; } - mControllers.forEach(controller -> { - if (controller instanceof BasePreferenceController.UiBlocker) { - ((BasePreferenceController) controller).revokeFirstLaunch(); - } - }); } @Override From 821608c5be06ebbda5857c771a483b8276ccfbbd Mon Sep 17 00:00:00 2001 From: Tsung-Mao Fang Date: Mon, 11 Apr 2022 18:35:25 +0800 Subject: [PATCH 5/7] Do not expose wifi slice when no permission Prior to this cl, slice provider always exposes wifi slice to calling package without confirming any wifi permissions. For current solution, we will check calling package's permission state and decide whether slice provider should expose wifi slice or not. Because settings search is a part of settings app, this permission checker won't be applied to settings intelligence. Test: manual, robotest, cts Also run manul Bug: 178014725 Change-Id: I2770b5b43366a5aa65c7519efc4243d350a21b26 --- .../settings/wifi/slice/WifiSlice.java | 40 ++++++++++++-- .../testutils/shadow/ShadowWifiSlice.java | 39 ++++++++++++++ .../wifi/slice/ContextualWifiSliceTest.java | 14 ++++- .../settings/wifi/slice/WifiSliceTest.java | 52 ++++++++++++++----- 4 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiSlice.java diff --git a/src/com/android/settings/wifi/slice/WifiSlice.java b/src/com/android/settings/wifi/slice/WifiSlice.java index 743c7f9815a..f59dc60d6e0 100644 --- a/src/com/android/settings/wifi/slice/WifiSlice.java +++ b/src/com/android/settings/wifi/slice/WifiSlice.java @@ -26,13 +26,16 @@ import android.app.PendingIntent; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.net.wifi.WifiManager; +import android.os.Binder; import android.os.Bundle; import android.text.TextUtils; +import android.util.Log; import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; @@ -49,8 +52,8 @@ import com.android.settings.network.WifiSwitchPreferenceController; import com.android.settings.slices.CustomSliceable; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settings.slices.SliceBuilderUtils; +import com.android.settings.wifi.AppStateChangeWifiStateBridge; import com.android.settings.wifi.WifiDialogActivity; -import com.android.settings.wifi.WifiSettings; import com.android.settings.wifi.WifiUtils; import com.android.settings.wifi.details.WifiNetworkDetailsFragment; import com.android.wifitrackerlib.WifiEntry; @@ -67,6 +70,7 @@ public class WifiSlice implements CustomSliceable { @VisibleForTesting static final int DEFAULT_EXPANDED_ROW_COUNT = 3; + private static final String TAG = "WifiSlice"; protected final Context mContext; protected final WifiManager mWifiManager; @@ -83,6 +87,12 @@ public class WifiSlice implements CustomSliceable { @Override public Slice getSlice() { + // If external calling package doesn't have Wi-Fi permission. + if (!Utils.isSettingsIntelligence(mContext) && !isPermissionGranted(mContext)) { + Log.i(TAG, "No wifi permissions to control wifi slice."); + return null; + } + final boolean isWifiEnabled = isWifiEnabled(); ListBuilder listBuilder = getListBuilder(isWifiEnabled, null /* wifiSliceItem */); if (!isWifiEnabled) { @@ -120,6 +130,30 @@ public class WifiSlice implements CustomSliceable { return listBuilder.build(); } + private static boolean isPermissionGranted(Context settingsContext) { + final int callingUid = Binder.getCallingUid(); + final String callingPackage = settingsContext.getPackageManager() + .getPackagesForUid(callingUid)[0]; + + Context packageContext; + try { + packageContext = settingsContext.createPackageContext(callingPackage, 0); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Cannot create Context for package: " + callingPackage); + return false; + } + + // If app doesn't have related Wi-Fi permission, they shouldn't show Wi-Fi slice. + final boolean hasPermission = packageContext.checkPermission( + android.Manifest.permission.CHANGE_WIFI_STATE, Binder.getCallingPid(), + callingUid) == PackageManager.PERMISSION_GRANTED; + AppStateChangeWifiStateBridge.WifiSettingsState state = + new AppStateChangeWifiStateBridge(settingsContext, null, null) + .getWifiSettingsInfo(callingPackage, callingUid); + + return hasPermission && state.isPermissible(); + } + protected boolean isApRowCollapsed() { return false; } @@ -175,7 +209,7 @@ public class WifiSlice implements CustomSliceable { tint = Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal); } else { tint = Utils.getDisabled(mContext, Utils.getColorAttrDefaultColor(mContext, - android.R.attr.colorControlNormal)); + android.R.attr.colorControlNormal)); } final Drawable drawable = mContext.getDrawable( @@ -275,7 +309,7 @@ public class WifiSlice implements CustomSliceable { final String key = WifiSwitchPreferenceController.KEY; final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, className, - key, screenTitle, SettingsEnums.DIALOG_WIFI_AP_EDIT, this) + key, screenTitle, SettingsEnums.DIALOG_WIFI_AP_EDIT, this) .setClassName(mContext.getPackageName(), SubSettings.class.getName()) .setData(contentUri); diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiSlice.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiSlice.java new file mode 100644 index 00000000000..ed583a4f0fe --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiSlice.java @@ -0,0 +1,39 @@ +/* + * 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.testutils.shadow; + +import android.content.Context; + +import com.android.settings.wifi.slice.WifiSlice; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(WifiSlice.class) +public class ShadowWifiSlice { + + private static boolean sIsWifiPermissible; + + @Implementation + protected static boolean isPermissionGranted(Context settingsContext) { + return sIsWifiPermissible; + } + + public static void setWifiPermissible(boolean isWifiPermissible) { + sIsWifiPermissible = isWifiPermissible; + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java index f31c216d260..52dcb5282da 100644 --- a/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java @@ -24,9 +24,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.wifi.WifiInfo; @@ -44,6 +46,7 @@ import com.android.settings.slices.CustomSliceRegistry; import com.android.settings.slices.SlicesFeatureProviderImpl; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowConnectivityManager; +import com.android.settings.testutils.shadow.ShadowWifiSlice; import org.junit.Before; import org.junit.Test; @@ -53,17 +56,20 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowBinder; import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowConnectivityManager.class) +@Config(shadows = {ShadowConnectivityManager.class, ShadowWifiSlice.class}) public class ContextualWifiSliceTest { private static final String SSID = "123"; @Mock private WifiManager mWifiManager; @Mock + private PackageManager mPackageManager; + @Mock private WifiInfo mWifiInfo; @Mock private Network mNetwork; @@ -88,10 +94,16 @@ public class ContextualWifiSliceTest { doReturn(mWifiInfo).when(mWifiManager).getConnectionInfo(); doReturn(SSID).when(mWifiInfo).getSSID(); doReturn(mNetwork).when(mWifiManager).getCurrentNetwork(); + when(mContext.getPackageManager()).thenReturn(mPackageManager); // Set-up specs for SliceMetadata. SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + final String siPackageName = + mContext.getString(R.string.config_settingsintelligence_package_name); + ShadowBinder.setCallingUid(1); + when(mPackageManager.getPackagesForUid(1)).thenReturn(new String[]{siPackageName}); + ShadowWifiSlice.setWifiPermissible(true); mWifiSlice = new ContextualWifiSlice(mContext); } diff --git a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java index b4a8b05f648..5b7a7d6c33f 100644 --- a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java @@ -31,21 +31,20 @@ import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; import android.net.wifi.WifiManager; -import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.SliceItem; -import androidx.slice.SliceMetadata; import androidx.slice.SliceProvider; -import androidx.slice.core.SliceAction; import androidx.slice.core.SliceQuery; import androidx.slice.widget.SliceLiveData; import com.android.settings.R; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settings.testutils.SliceTester; +import com.android.settings.testutils.shadow.ShadowWifiSlice; import com.android.wifitrackerlib.WifiEntry; import com.android.wifitrackerlib.WifiEntry.ConnectedState; @@ -59,24 +58,32 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowBinder; import java.util.ArrayList; import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(shadows = WifiSliceTest.ShadowSliceBackgroundWorker.class) +@Config(shadows = { + WifiSliceTest.ShadowSliceBackgroundWorker.class, + ShadowWifiSlice.class}) public class WifiSliceTest { private static final String AP1_NAME = "ap1"; private static final String AP2_NAME = "ap2"; private static final String AP3_NAME = "ap3"; + private static final int USER_ID = 1; @Mock private WifiManager mWifiManager; + @Mock + private PackageManager mPackageManager; + private Context mContext; private ContentResolver mResolver; private WifiSlice mWifiSlice; + private String mSIPackageName; @Before public void setUp() { @@ -86,27 +93,46 @@ public class WifiSliceTest { doReturn(mResolver).when(mContext).getContentResolver(); doReturn(mWifiManager).when(mContext).getSystemService(WifiManager.class); doReturn(WifiManager.WIFI_STATE_ENABLED).when(mWifiManager).getWifiState(); + when(mContext.getPackageManager()).thenReturn(mPackageManager); // Set-up specs for SliceMetadata. SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + mSIPackageName = mContext.getString(R.string.config_settingsintelligence_package_name); + ShadowBinder.setCallingUid(USER_ID); + when(mPackageManager.getPackagesForUid(USER_ID)).thenReturn(new String[]{mSIPackageName}); + ShadowWifiSlice.setWifiPermissible(true); mWifiSlice = new WifiSlice(mContext); } @Test - public void getWifiSlice_shouldHaveTitleAndToggle() { + public void getWifiSlice_fromSIPackage_shouldHaveTitleAndToggle() { + when(mPackageManager.getPackagesForUid(USER_ID)).thenReturn(new String[]{mSIPackageName}); + ShadowWifiSlice.setWifiPermissible(false); + final Slice wifiSlice = mWifiSlice.getSlice(); - final SliceMetadata metadata = SliceMetadata.from(mContext, wifiSlice); - assertThat(metadata.getTitle()).isEqualTo(mContext.getString(R.string.wifi_settings)); + assertThat(wifiSlice).isNotNull(); + } - final List toggles = metadata.getToggles(); - assertThat(toggles).hasSize(1); + @Test + public void getWifiSlice_notFromSIPackageAndWithWifiPermission_shouldHaveTitleAndToggle() { + when(mPackageManager.getPackagesForUid(USER_ID)).thenReturn(new String[]{"com.test"}); + ShadowWifiSlice.setWifiPermissible(true); - final SliceAction primaryAction = metadata.getPrimaryAction(); - final IconCompat expectedToggleIcon = IconCompat.createWithResource(mContext, - R.drawable.ic_settings_wireless); - assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedToggleIcon.toString()); + final Slice wifiSlice = mWifiSlice.getSlice(); + + assertThat(wifiSlice).isNotNull(); + } + + @Test + public void getWifiSlice_notFromSIPackageAndWithoutWifiPermission_shouldNoSlice() { + when(mPackageManager.getPackagesForUid(USER_ID)).thenReturn(new String[]{"com.test"}); + ShadowWifiSlice.setWifiPermissible(false); + + final Slice wifiSlice = mWifiSlice.getSlice(); + + assertThat(wifiSlice).isNull(); } @Test From 87e2471ab6df2847aad81f03ab2bbb4bf8052bfd Mon Sep 17 00:00:00 2001 From: Niklas Lindgren Date: Thu, 11 Oct 2018 13:02:10 +0200 Subject: [PATCH 6/7] Remove config_showOperatorNameInStatusBar config_showOperatorNameInStatusBar has moved to CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL Bug: 116847905 Test: Build Product Change-Id: Ia5e7b5e498ce33ffe58ff922ab3799a1b263a88b --- res/values/config.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/res/values/config.xml b/res/values/config.xml index 6b42baab6bc..c7ef595e3ae 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -71,9 +71,6 @@ false - - false - - Use Auto-rotate + Use auto-rotate - Face Detection uses the front-facing camera to improve Auto-rotate accuracy. Images are never stored or sent to Google. + Face Detection uses the front-facing camera to improve auto-rotate accuracy. Images are never stored or sent to Google. Sample text @@ -2926,7 +2926,11 @@ On - Face-based - Enable Face Detection + Face Detection + + Automatically adjust the screen orientation when you move your phone between portrait and landscape + + Learn more about auto-rotate Screen resolution @@ -12453,6 +12457,9 @@ + + + diff --git a/res/xml/auto_rotate_settings.xml b/res/xml/auto_rotate_settings.xml index 0c120d68fff..38aa5f0047e 100644 --- a/res/xml/auto_rotate_settings.xml +++ b/res/xml/auto_rotate_settings.xml @@ -20,6 +20,14 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/accelerometer_title" > + + + + + settings:searchable="false"/> diff --git a/src/com/android/settings/display/AutoRotateSwitchBarController.java b/src/com/android/settings/display/AutoRotateSwitchBarController.java index 48dedfd1fa3..d76104aef7f 100644 --- a/src/com/android/settings/display/AutoRotateSwitchBarController.java +++ b/src/com/android/settings/display/AutoRotateSwitchBarController.java @@ -18,86 +18,78 @@ package com.android.settings.display; import android.app.settings.SettingsEnums; import android.content.Context; -import android.widget.Switch; import com.android.internal.view.RotationPolicy; +import com.android.settings.R; import com.android.settings.overlay.FeatureFactory; -import com.android.settings.widget.SettingsMainSwitchBar; +import com.android.settings.widget.SettingsMainSwitchPreferenceController; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; -import com.android.settingslib.widget.OnMainSwitchChangeListener; /** - * The switch controller for auto-rotate. + * The main switch controller for auto-rotate. */ -public class AutoRotateSwitchBarController implements OnMainSwitchChangeListener, +public class AutoRotateSwitchBarController extends SettingsMainSwitchPreferenceController implements LifecycleObserver, OnStart, OnStop { - private final SettingsMainSwitchBar mSwitchBar; - private final Context mContext; - private boolean mValidListener; private final MetricsFeatureProvider mMetricsFeatureProvider; + private RotationPolicy.RotationPolicyListener mRotationPolicyListener; - public AutoRotateSwitchBarController(Context context, SettingsMainSwitchBar switchBar, - Lifecycle lifecycle) { - mSwitchBar = switchBar; - mContext = context; + public AutoRotateSwitchBarController(Context context, String key) { + super(context, key); mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); - if (lifecycle != null) { - lifecycle.addObserver(this); - } + } + + @Override + public int getAvailabilityStatus() { + return RotationPolicy.isRotationLockToggleVisible(mContext) + && !DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(mContext) + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @Override public void onStart() { - if (!mValidListener) { - mSwitchBar.addOnSwitchChangeListener(this); - mValidListener = true; + if (mRotationPolicyListener == null) { + mRotationPolicyListener = new RotationPolicy.RotationPolicyListener() { + @Override + public void onChange() { + if (mSwitchPreference != null) { + updateState(mSwitchPreference); + } + } + }; } - onChange(); + RotationPolicy.registerRotationPolicyListener(mContext, + mRotationPolicyListener); } @Override public void onStop() { - if (mValidListener) { - mSwitchBar.removeOnSwitchChangeListener(this); - mValidListener = false; + if (mRotationPolicyListener != null) { + RotationPolicy.unregisterRotationPolicyListener(mContext, mRotationPolicyListener); } } - /** - * Listens to the state change of the rotation primary switch. - */ @Override - public void onSwitchChanged(Switch switchView, boolean isChecked) { - setRotationLock(isChecked); + public boolean isChecked() { + return !RotationPolicy.isRotationLocked(mContext); } - - protected void onChange() { - final boolean isEnabled = !RotationPolicy.isRotationLocked(mContext); - if (isEnabled != mSwitchBar.isChecked()) { - // set listener to null so that that code below doesn't trigger onCheckedChanged() - if (mValidListener) { - mSwitchBar.removeOnSwitchChangeListener(this); - } - mSwitchBar.setChecked(isEnabled); - if (mValidListener) { - mSwitchBar.addOnSwitchChangeListener(this); - } - } - } - - private boolean setRotationLock(boolean isChecked) { + @Override + public boolean setChecked(boolean isChecked) { final boolean isLocked = !isChecked; mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_ROTATE_ROTATE_MASTER_TOGGLE, - isChecked); + isLocked); RotationPolicy.setRotationLock(mContext, isLocked); return true; } + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_display; + } + } diff --git a/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java b/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java index 9fda03c4d5f..1b4e998db87 100644 --- a/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java +++ b/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java @@ -20,7 +20,7 @@ import static com.android.settings.display.SmartAutoRotateController.isRotationR import android.app.settings.SettingsEnums; import android.content.Context; import android.os.Bundle; -import android.text.Html; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -28,12 +28,11 @@ import android.view.ViewGroup; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; -import com.android.internal.view.RotationPolicy; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settings.widget.SettingsMainSwitchBar; +import com.android.settingslib.HelpUtils; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.Indexable; import com.android.settingslib.search.SearchIndexable; @@ -51,9 +50,11 @@ public class SmartAutoRotatePreferenceFragment extends DashboardFragment { private static final String TAG = "SmartAutoRotatePreferenceFragment"; - private RotationPolicy.RotationPolicyListener mRotationPolicyListener; - private AutoRotateSwitchBarController mSwitchBarController; - @VisibleForTesting static final String AUTO_ROTATE_SWITCH_PREFERENCE_ID = "auto_rotate_switch"; + @VisibleForTesting + static final String AUTO_ROTATE_MAIN_SWITCH_PREFERENCE_KEY = "auto_rotate_main_switch"; + @VisibleForTesting + static final String AUTO_ROTATE_SWITCH_PREFERENCE_KEY = "auto_rotate_switch"; + private static final String KEY_FOOTER_PREFERENCE = "auto_rotate_footer_preference"; @Override protected int getPreferenceScreenResId() { @@ -81,11 +82,10 @@ public class SmartAutoRotatePreferenceFragment extends DashboardFragment { final View view = super.onCreateView(inflater, container, savedInstanceState); final SettingsActivity activity = (SettingsActivity) getActivity(); createHeader(activity); - final Preference footerPreference = findPreference(FooterPreference.KEY_FOOTER); + final Preference footerPreference = findPreference(KEY_FOOTER_PREFERENCE); if (footerPreference != null) { - footerPreference.setTitle(Html.fromHtml(getString(R.string.smart_rotate_text_headline), - Html.FROM_HTML_MODE_COMPACT)); footerPreference.setVisible(isRotationResolverServiceAvailable(activity)); + setupFooter(); } return view; } @@ -95,39 +95,9 @@ public class SmartAutoRotatePreferenceFragment extends DashboardFragment { boolean deviceStateRotationEnabled = DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(activity); if (isRotationResolverServiceAvailable(activity) && !deviceStateRotationEnabled) { - final SettingsMainSwitchBar switchBar = activity.getSwitchBar(); - switchBar.setTitle( - getContext().getString(R.string.auto_rotate_settings_primary_switch_title)); - switchBar.show(); - mSwitchBarController = new AutoRotateSwitchBarController(activity, switchBar, - getSettingsLifecycle()); - findPreference(AUTO_ROTATE_SWITCH_PREFERENCE_ID).setVisible(false); - } - } - - @Override - public void onResume() { - super.onResume(); - if (mRotationPolicyListener == null) { - mRotationPolicyListener = new RotationPolicy.RotationPolicyListener() { - @Override - public void onChange() { - if (mSwitchBarController != null) { - mSwitchBarController.onChange(); - } - } - }; - } - RotationPolicy.registerRotationPolicyListener(getPrefContext(), - mRotationPolicyListener); - } - - @Override - public void onPause() { - super.onPause(); - if (mRotationPolicyListener != null) { - RotationPolicy.unregisterRotationPolicyListener(getPrefContext(), - mRotationPolicyListener); + findPreference(AUTO_ROTATE_SWITCH_PREFERENCE_KEY).setVisible(false); + } else { + findPreference(AUTO_ROTATE_MAIN_SWITCH_PREFERENCE_KEY).setVisible(false); } } @@ -141,6 +111,34 @@ public class SmartAutoRotatePreferenceFragment extends DashboardFragment { return TAG; } + @Override + public int getHelpResource() { + return R.string.help_url_auto_rotate_settings; + } + + // Updates the footer for this page. + @VisibleForTesting + void setupFooter() { + final String mHelpUri = getString(getHelpResource()); + if (!TextUtils.isEmpty(mHelpUri)) { + addHelpLink(); + } + } + + // Changes the text to include a learn more link if the link is defined. + @VisibleForTesting + void addHelpLink() { + final FooterPreference pref = findPreference(KEY_FOOTER_PREFERENCE); + if (pref != null) { + pref.setLearnMoreAction(v -> { + startActivityForResult(HelpUtils.getHelpIntent(getContext(), + getString(getHelpResource()), + /*backupContext=*/ ""), /*requestCode=*/ 0); + }); + pref.setLearnMoreContentDescription(getString(R.string.auto_rotate_link_a11y)); + } + } + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider(R.xml.auto_rotate_settings) { diff --git a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java index 942fed6f619..e5374196681 100644 --- a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java @@ -16,7 +16,8 @@ package com.android.settings.display; -import static com.android.settings.display.SmartAutoRotatePreferenceFragment.AUTO_ROTATE_SWITCH_PREFERENCE_ID; +import static com.android.settings.display.SmartAutoRotatePreferenceFragment.AUTO_ROTATE_MAIN_SWITCH_PREFERENCE_KEY; +import static com.android.settings.display.SmartAutoRotatePreferenceFragment.AUTO_ROTATE_SWITCH_PREFERENCE_KEY; import static com.google.common.truth.Truth.assertThat; @@ -45,7 +46,6 @@ import com.android.settings.SettingsActivity; import com.android.settings.testutils.ResolveInfoBuilder; import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager; import com.android.settings.testutils.shadow.ShadowRotationPolicy; -import com.android.settings.widget.SettingsMainSwitchBar; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; @@ -71,8 +71,6 @@ public class SmartAutoRotatePreferenceFragmentTest { private SmartAutoRotatePreferenceFragment mFragment; - private SettingsMainSwitchBar mSwitchBar; - @Mock private PackageManager mPackageManager; @@ -87,6 +85,9 @@ public class SmartAutoRotatePreferenceFragmentTest { private Resources mResources; private Context mContext; + @Mock + private Preference mRotateMainSwitchPreference; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -112,21 +113,21 @@ public class SmartAutoRotatePreferenceFragmentTest { when(mFragment.getContext()).thenReturn(mContext); doReturn(mView).when(mFragment).getView(); - when(mFragment.findPreference(AUTO_ROTATE_SWITCH_PREFERENCE_ID)).thenReturn( + when(mFragment.findPreference(AUTO_ROTATE_SWITCH_PREFERENCE_KEY)).thenReturn( mRotateSwitchPreference); - mSwitchBar = spy(new SettingsMainSwitchBar(mContext)); - when(mActivity.getSwitchBar()).thenReturn(mSwitchBar); - doReturn(mSwitchBar).when(mView).findViewById(R.id.switch_bar); - ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(false); + + when(mFragment.findPreference(AUTO_ROTATE_MAIN_SWITCH_PREFERENCE_KEY)).thenReturn( + mRotateMainSwitchPreference); } @Test public void createHeader_faceDetectionSupported_switchBarIsEnabled() { + ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(false); mFragment.createHeader(mActivity); - verify(mSwitchBar, times(1)).show(); + verify(mRotateMainSwitchPreference, never()).setVisible(false); verify(mRotateSwitchPreference, times(1)).setVisible(false); } @@ -137,7 +138,7 @@ public class SmartAutoRotatePreferenceFragmentTest { mFragment.createHeader(mActivity); - verify(mSwitchBar, never()).show(); + verify(mRotateMainSwitchPreference, times(1)).setVisible(false); verify(mRotateSwitchPreference, never()).setVisible(false); } @@ -147,7 +148,7 @@ public class SmartAutoRotatePreferenceFragmentTest { mFragment.createHeader(mActivity); - verify(mSwitchBar, never()).show(); + verify(mRotateMainSwitchPreference, times(1)).setVisible(false); verify(mRotateSwitchPreference, never()).setVisible(false); } @@ -176,6 +177,19 @@ public class SmartAutoRotatePreferenceFragmentTest { DeviceStateAutoRotateSettingController.class); } + @Test + public void setupFooter_linkAddedWhenAppropriate() { + doReturn("").when(mFragment).getText(anyInt()); + doReturn("").when(mFragment).getString(anyInt()); + mFragment.setupFooter(); + verify(mFragment, never()).addHelpLink(); + + doReturn("testString").when(mFragment).getText(anyInt()); + doReturn("testString").when(mFragment).getString(anyInt()); + mFragment.setupFooter(); + verify(mFragment, times(1)).addHelpLink(); + } + private void enableDeviceStateSettableRotationStates(String[] settableStates, String[] settableStatesDescriptions) { when(mResources.getStringArray(