Cancel UDFPS enrollment on overlay focus loss

Integrate onWindowFocusChanged() to FingerprintEnrollEnrolling
When the activity window lost focus, we just cancel this enroll
session, and create a dialog to notify user.

* Keep legacy rotation behavior, do not cancel enrollment if user
  rotate device.
* This change will cancel enrollment when user interrupted by
  1) Swipe down Notification Shade
  2) Heads up notification expand
  3) Bubble Expanded
  4) Google Assist
  5) PIP expand
  6) Recents
  7) Launch Power Menu(Global Action)

Test: manual test above scenario
Test: m RunSettingsRoboTests -j30 ROBOTEST_FILTER=FingerprintEnrollEnrollingTest
Bug: 228261883
Bug: 242478941
Bug: 227905887
Change-Id: Id097e6b15928646cd5ab91c2a1f6eb1bb9bf190b
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index 5f9a74f..67848e3 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.biometrics.fingerprint;
 
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED;
+
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.annotation.IntDef;
@@ -51,6 +53,7 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import androidx.annotation.IdRes;
 import androidx.appcompat.app.AlertDialog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -80,6 +83,7 @@
     private static final String TAG = "FingerprintEnrollEnrolling";
     static final String TAG_SIDECAR = "sidecar";
     static final String KEY_STATE_CANCELED = "is_canceled";
+    static final String KEY_STATE_PREVIOUS_ROTATION = "previous_rotation";
 
     private static final int PROGRESS_BAR_MAX = 10000;
 
@@ -134,6 +138,7 @@
     private boolean mRestoring;
     private Vibrator mVibrator;
     private boolean mIsSetupWizard;
+    private boolean mIsOrientationChanged;
     private boolean mIsCanceled;
     private AccessibilityManager mAccessibilityManager;
     private boolean mIsAccessibilityEnabled;
@@ -156,6 +161,23 @@
     }
 
     @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        if (hasFocus) {
+            return;
+        }
+
+        // By UX design, we should ensure seamless enrollment CUJ even though user rotate device.
+        // Do NOT cancel enrollment progress after rotating, adding mIsOrientationChanged
+        // to judge if the focus changed was triggered by rotation, current WMS has triple callbacks
+        // (true > false > true), we need to reset mIsOrientationChanged when !hasFocus callback.
+        if (!mIsOrientationChanged) {
+            onCancelEnrollment(FINGERPRINT_ERROR_USER_CANCELED);
+        } else {
+            mIsOrientationChanged = false;
+        }
+    }
+
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
@@ -295,11 +317,15 @@
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         outState.putBoolean(KEY_STATE_CANCELED, mIsCanceled);
+        outState.putInt(KEY_STATE_PREVIOUS_ROTATION, mPreviousRotation);
     }
 
     private void restoreSavedState(Bundle savedInstanceState) {
         mRestoring = true;
         mIsCanceled = savedInstanceState.getBoolean(KEY_STATE_CANCELED, false);
+        mPreviousRotation = savedInstanceState.getInt(KEY_STATE_PREVIOUS_ROTATION,
+                getDisplay().getRotation());
+        mIsOrientationChanged = mPreviousRotation != getDisplay().getRotation();
     }
 
     @Override
@@ -337,6 +363,19 @@
         }
     }
 
+    @VisibleForTesting
+    void onCancelEnrollment(@IdRes int errorMsgId) {
+        FingerprintErrorDialog.showErrorDialog(this, errorMsgId);
+        mIsCanceled = true;
+        mIsOrientationChanged = false;
+        cancelEnrollment();
+        stopIconAnimation();
+        stopListenOrientationEvent();
+        if (!mCanAssumeUdfps) {
+            mErrorText.removeCallbacks(mTouchAgainRunnable);
+        }
+    }
+
     @Override
     protected void onStop() {
         super.onStop();
@@ -541,14 +580,7 @@
 
     @Override
     public void onEnrollmentError(int errMsgId, CharSequence errString) {
-        FingerprintErrorDialog.showErrorDialog(this, errMsgId);
-        mIsCanceled = true;
-        cancelEnrollment();
-        stopIconAnimation();
-        stopListenOrientationEvent();
-        if (!mCanAssumeUdfps) {
-            mErrorText.removeCallbacks(mTouchAgainRunnable);
-        }
+        onCancelEnrollment(errMsgId);
     }
 
     @Override
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java
index 757a304..7395694 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.biometrics.fingerprint;
 
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.KEY_STATE_PREVIOUS_ROTATION;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -28,14 +30,18 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Vibrator;
+import android.view.Display;
+import android.view.Surface;
 import android.widget.TextView;
 
 import com.android.settings.R;
@@ -49,6 +55,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.android.controller.ActivityController;
 
 import java.util.ArrayList;
@@ -61,7 +68,10 @@
 
     @Mock private Vibrator mVibrator;
 
+    @Mock private Display mMockDisplay;
+
     private FingerprintEnrollEnrolling mActivity;
+    private Context mContext;
 
     @Before
     public void setUp() {
@@ -100,6 +110,26 @@
         verify(mVibrator, never()).vibrate(anyInt(), anyString(), any(), anyString(), any());
     }
 
+    @Test
+    public void fingerprintUdfpsOverlayEnrollment_gainFocus_shouldNotCancel() {
+        initializeActivityFor(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
+
+        mActivity.onEnrollmentProgressChange(1, 1);
+        mActivity.onWindowFocusChanged(true);
+
+        verify(mActivity, never()).onCancelEnrollment(anyInt());
+    }
+
+    @Test
+    public void fingerprintUdfpsOverlayEnrollment_loseFocus_shouldCancel() {
+        initializeActivityFor(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
+
+        mActivity.onEnrollmentProgressChange(1, 1);
+        mActivity.onWindowFocusChanged(false);
+
+        verify(mActivity, never()).onCancelEnrollment(anyInt());
+    }
+
     private void initializeActivityFor(int sensorType) {
         final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
         final FingerprintSensorPropertiesInternal prop =
@@ -111,15 +141,21 @@
                         sensorType,
                         true /* resetLockoutRequiresHardwareAuthToken */);
         final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+        final Bundle savedInstanceState = new Bundle();
+        savedInstanceState.putInt(KEY_STATE_PREVIOUS_ROTATION, Surface.ROTATION_90);
         props.add(prop);
-        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
-
+        mContext = spy(RuntimeEnvironment.application);
         mActivity = spy(FingerprintEnrollEnrolling.class);
+
+        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
+        when(mContext.getDisplay()).thenReturn(mMockDisplay);
+        when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0);
+
         doReturn(true).when(mActivity).shouldShowLottie();
         doReturn(mFingerprintManager).when(mActivity).getSystemService(FingerprintManager.class);
         doReturn(mVibrator).when(mActivity).getSystemService(Vibrator.class);
 
-        ActivityController.of(mActivity).create();
+        ActivityController.of(mActivity).create(savedInstanceState);
     }
 
     private EnrollmentCallback verifyAndCaptureEnrollmentCallback() {