Merge "Refactor FingerprintEnrollEnrolling to fragment" into udc-dev am: 98d2e83c3d

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/21508164

Change-Id: I4f7b42e2cadbed273b94e91207d1dfa4f8b89464
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
index f7f138c..70fdbf0 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
@@ -142,7 +142,10 @@
         setRetainInstance(true);
     }
 
-    void onEnrollmentProgress(int totalSteps, int remaining) {
+    /**
+     * Called when a enroll progress update
+     */
+    public void onEnrollmentProgress(int totalSteps, int remaining) {
         if (mTotalSteps == -1) {
             mTotalSteps = totalSteps;
         }
@@ -161,25 +164,37 @@
         }
     }
 
-    void onEnrollmentHelp() {
+    /**
+     * Called when a receive error has been encountered during enrollment.
+     */
+    public void onEnrollmentHelp() {
         if (mListener != null) {
             mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps);
         }
     }
 
-    void onAcquired(boolean isAcquiredGood) {
+    /**
+     * Called when a fingerprint image has been acquired, but wasn't processed yet.
+     */
+    public void onAcquired(boolean isAcquiredGood) {
         if (mListener != null && mTotalSteps != -1) {
             mListener.onAcquired(isAcquiredGood && animateIfLastStep());
         }
     }
 
-    void onPointerDown(int sensorId) {
+    /**
+     * Called when pointer down
+     */
+    public void onPointerDown(int sensorId) {
         if (mListener != null) {
             mListener.onPointerDown(sensorId);
         }
     }
 
-    void onPointerUp(int sensorId) {
+    /**
+     * Called when pointer up
+     */
+    public void onPointerUp(int sensorId) {
         if (mListener != null) {
             mListener.onPointerUp(sensorId);
         }
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java
index 96b49aa..6e42059 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java
@@ -108,7 +108,10 @@
         return mOverlayParams;
     }
 
-    void setOverlayParams(UdfpsOverlayParams params) {
+    /**
+     * Set UdfpsOverlayParams
+     */
+    public void setOverlayParams(UdfpsOverlayParams params) {
         mOverlayParams = params;
 
         post(() -> {
@@ -121,7 +124,10 @@
         });
     }
 
-    void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
+    /**
+     * Set UdfpsEnrollHelper
+     */
+    public void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
         mFingerprintDrawable.setEnrollHelper(enrollHelper);
         enrollHelper.setListener(this);
     }
@@ -193,6 +199,8 @@
         params.height = rotatedBounds.height() + 2 * getPaddingX();
         params.width = rotatedBounds.width() + 2 * getPaddingY();
         setLayoutParams(params);
+
+
     }
 
     private void onFingerDown() {
diff --git a/src/com/android/settings/biometrics2/data/repository/AccessibilityRepository.java b/src/com/android/settings/biometrics2/data/repository/AccessibilityRepository.java
index 8777b4f..20d7f1f 100644
--- a/src/com/android/settings/biometrics2/data/repository/AccessibilityRepository.java
+++ b/src/com/android/settings/biometrics2/data/repository/AccessibilityRepository.java
@@ -65,4 +65,13 @@
     public void sendAccessibilityEvent(@NonNull AccessibilityEvent event) {
         mAccessibilityManager.sendAccessibilityEvent(event);
     }
+
+    /**
+     * Returns if the touch exploration in the system is enabled.
+     *
+     * @return True if touch exploration is enabled, false otherwise.
+     */
+    public boolean isTouchExplorationEnabled() {
+        return mAccessibilityManager.isTouchExplorationEnabled();
+    }
 }
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java
index a91e206..be18b9c 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java
@@ -35,7 +35,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
@@ -329,12 +328,7 @@
     }
 
     private void announceEnrollmentProgress(CharSequence announcement) {
-        AccessibilityEvent event = new AccessibilityEvent();
-        event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
-        event.setClassName(getClass().getName());
-        event.setPackageName(getClass().getPackageName());
-        event.getText().add(announcement);
-        mEnrollingViewModel.sendAccessibilityEvent(event);
+        mEnrollingViewModel.sendAccessibilityEvent(announcement);
     }
 
     private void onEnrollmentProgressChange(@NonNull EnrollmentProgress progress) {
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.java
index ad6abf1..4cf3573 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.java
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.java
@@ -22,18 +22,23 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.Animatable2;
-import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.DisplayInfo;
 import android.view.LayoutInflater;
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -47,15 +52,20 @@
 
 import com.android.settings.R;
 import com.android.settings.biometrics.BiometricUtils;
+import com.android.settings.biometrics.fingerprint.UdfpsEnrollHelper;
+import com.android.settings.biometrics.fingerprint.UdfpsEnrollView;
 import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
 import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage;
 import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel;
 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
 import com.android.settingslib.display.DisplayDensityUtils;
+import com.android.settingslib.udfps.UdfpsOverlayParams;
+import com.android.settingslib.udfps.UdfpsUtils;
 
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieCompositionFactory;
+import com.google.android.setupcompat.template.FooterActionButton;
 import com.google.android.setupcompat.template.FooterBarMixin;
 import com.google.android.setupcompat.template.FooterButton;
 import com.google.android.setupdesign.GlifLayout;
@@ -70,9 +80,6 @@
     private static final String TAG = FingerprintEnrollEnrollingUdfpsFragment.class.getSimpleName();
 
     private static final int PROGRESS_BAR_MAX = 10000;
-    private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500;
-    private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3;
-    private static final int HINT_TIMEOUT_DURATION = 2500;
 
     private static final int STAGE_UNKNOWN = -1;
     private static final int STAGE_CENTER = 0;
@@ -100,35 +107,52 @@
     private GlifLayout mView;
     private TextView mErrorText;
     private FooterBarMixin mFooterBarMixin;
-    private AnimatedVectorDrawable mIconAnimationDrawable;
-    private AnimatedVectorDrawable mIconBackgroundBlinksDrawable;
 
     private boolean mShouldShowLottie;
     private boolean mIsAccessibilityEnabled;
 
     private final View.OnClickListener mOnSkipClickListener =
             (v) -> mEnrollingViewModel.onCancelledDueToOnSkipPressed();
-    private final Observer<EnrollmentProgress> mProgressObserver = progress -> {
+
+    private Observer<EnrollmentProgress> mProgressObserver = progress -> {
+        if (progress != null) {
+            onEnrollmentProgressChange(progress);
+        }
+    };
+    private Observer<EnrollmentStatusMessage> mHelpMessageObserver = helpMessage -> {
+        if (helpMessage != null) {
+            onEnrollmentHelp(helpMessage.getMsgId(), helpMessage.getStr());
+        }
+    };
+    private Observer<EnrollmentStatusMessage> mErrorMessageObserver = errorMessage -> {
         // TODO
     };
-    private final Observer<EnrollmentStatusMessage> mHelpMessageObserver = helpMessage -> {
-        // TODO
-    };
-    private final Observer<EnrollmentStatusMessage> mErrorMessageObserver = errorMessage -> {
-        // TODO
-    };
-    private final Observer<Boolean> mAcquireObserver = isAcquiredGood -> {
-        // TODO
+    private Observer<Boolean> mAcquireObserver = isAcquiredGood -> {
+        if (isAcquiredGood != null) {
+            onAcquired(isAcquiredGood);
+        }
     };
     private final Observer<Integer> mPointerDownObserver = sensorId -> {
-        // TODO
+        if (sensorId != null) {
+            onPointerDown(sensorId);
+        }
     };
     private final Observer<Integer> mPointerUpObserver = sensorId -> {
-        // TODO
+        if (sensorId != null) {
+            onPointerUp(sensorId);
+        }
     };
 
     private int mIconTouchCount;
 
+    private UdfpsUtils mUdfpsUtils;
+    private float mScaleFactor = 1.0f;
+    //TODO UdfpsEnrollHelper should not be a Fragment, we should tell enrollview & progress
+    // drawable enough information EnrollView & ProgressDrawable should draw themselves without
+    // UdfpsEnrollHelper
+    private UdfpsEnrollHelper mUdfpsEnrollHelper;
+
+
     @Override
     public void onAttach(@NonNull Context context) {
         final FragmentActivity activity = getActivity();
@@ -176,6 +200,10 @@
         super.onCreate(savedInstanceState);
         mEnrollingViewModel.restoreSavedState(savedInstanceState);
         mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled();
+        mUdfpsUtils = new UdfpsUtils();
+        mUdfpsEnrollHelper = new UdfpsEnrollHelper(getActivity(), getActivity().getSystemService(
+                FingerprintManager.class
+        ));
     }
 
     @Override
@@ -188,13 +216,15 @@
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         mView = initUdfpsLayout(inflater, container);
+
         return mView;
     }
 
     private GlifLayout initUdfpsLayout(LayoutInflater inflater, ViewGroup container) {
         final GlifLayout containView = (GlifLayout) inflater.inflate(
                 R.layout.udfps_enroll_enrolling, container, false);
-
+        final UdfpsEnrollView udfpsEnrollView = addUdfpsEnrollView(inflater,
+                mEnrollingViewModel.getFirstFingerprintSensorPropertiesInternal());
         final int rotation = mRotationViewModel.getLiveData().getValue();
         if (rotation == Surface.ROTATION_90) {
             final boolean isLayoutRtl = (TextUtils.getLayoutDirectionFromLocale(
@@ -207,19 +237,36 @@
                     isLayoutRtl ? 0 : (int) getResources().getDimension(
                             R.dimen.rotation_90_enroll_padding_end),
                     0);
+
             final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                     LinearLayout.LayoutParams.MATCH_PARENT,
                     LinearLayout.LayoutParams.MATCH_PARENT);
             lp.setMarginEnd((int) getResources().getDimension(
                     R.dimen.rotation_90_enroll_margin_end));
             layoutContainer.setLayoutParams(lp);
+            containView.addView(udfpsEnrollView);
+            containView.setClipChildren(false);
+            containView.setClipToPadding(false);
             containView.setLayoutParams(lp);
+            setOnHoverListener(true, containView, udfpsEnrollView);
+        } else if (rotation == Surface.ROTATION_270) {
+            containView.addView(udfpsEnrollView);
+            containView.setClipChildren(false);
+            containView.setClipToPadding(false);
+            setOnHoverListener(true, containView, udfpsEnrollView);
+        } else {
+            final FrameLayout portraitLayoutContainer = containView.findViewById(
+                    R.id.layout_container);
+            portraitLayoutContainer.addView(udfpsEnrollView);
+            ViewGroup parent = ((ViewGroup) portraitLayoutContainer.getParent());
+            parent.setClipChildren(false);
+            parent.setClipToPadding(false);
+            setOnHoverListener(false, containView, udfpsEnrollView);
         }
 
         final Activity activity = getActivity();
         new GlifLayoutHelper(activity, containView).setDescriptionText(
                 getString(R.string.security_settings_udfps_enroll_start_message));
-        updateTitleAndDescription();
 
         mShouldShowLottie = shouldShowLottie();
         boolean isLandscape = BiometricUtils.isReverseLandscape(activity)
@@ -245,6 +292,19 @@
         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
                 activity, android.R.interpolator.fast_out_linear_in);
 
+        final LinearLayout buttonContainer = mFooterBarMixin.getButtonContainer();
+        View spaceView = null;
+        for (int i = 0; i < buttonContainer.getChildCount(); i++) {
+            if (!(buttonContainer.getChildAt(i) instanceof FooterActionButton)) {
+                spaceView = buttonContainer.getChildAt(i);
+                break;
+            }
+        }
+        if (spaceView != null) {
+            spaceView.setVisibility(View.GONE);
+            buttonContainer.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
+        }
+
         return containView;
     }
 
@@ -298,6 +358,9 @@
 
         final int progress = getProgress(enrollmentProgress);
 
+        mUdfpsEnrollHelper.onEnrollmentProgress(enrollmentProgress.getSteps(),
+                enrollmentProgress.getRemaining());
+
         if (animate) {
             animateProgress(progress);
         } else if (progress >= PROGRESS_BAR_MAX) {
@@ -319,6 +382,38 @@
         super.onDestroy();
     }
 
+    private UdfpsEnrollView addUdfpsEnrollView(LayoutInflater inflater,
+            FingerprintSensorPropertiesInternal udfpsProps) {
+
+        UdfpsEnrollView enrollView = (UdfpsEnrollView) inflater.inflate(R.layout.udfps_enroll_view,
+                null, false);
+        DisplayInfo displayInfo = new DisplayInfo();
+        getActivity().getDisplay().getDisplayInfo(displayInfo);
+        mScaleFactor = mUdfpsUtils.getScaleFactor(displayInfo);
+        Rect udfpsBounds = udfpsProps.getLocation().getRect();
+        udfpsBounds.scale(mScaleFactor);
+
+        final Rect overlayBounds = new Rect(
+                0, /* left */
+                displayInfo.getNaturalHeight() / 2, /* top */
+                displayInfo.getNaturalWidth(), /* right */
+                displayInfo.getNaturalHeight() /* botom */);
+
+        UdfpsOverlayParams params = new UdfpsOverlayParams(
+                udfpsBounds,
+                overlayBounds,
+                displayInfo.getNaturalWidth(),
+                displayInfo.getNaturalHeight(),
+                mScaleFactor,
+                displayInfo.rotation);
+
+        enrollView.setOverlayParams(params);
+
+        enrollView.setEnrollHelper(mUdfpsEnrollHelper);
+
+        return enrollView;
+    }
+
     private void animateProgress(int progress) {
         // UDFPS animations are owned by SystemUI
         if (progress >= PROGRESS_BAR_MAX) {
@@ -462,16 +557,10 @@
     }
 
     private void startIconAnimation() {
-        if (mIconAnimationDrawable != null) {
-            mIconAnimationDrawable.start();
-        }
     }
 
     private void stopIconAnimation() {
         mAnimationCancelled = true;
-        if (mIconAnimationDrawable != null) {
-            mIconAnimationDrawable.stop();
-        }
     }
 
     private int getCurrentStage() {
@@ -545,6 +634,79 @@
                 });
     }
 
+    private void setOnHoverListener(boolean isLandscape, GlifLayout enrollLayout,
+            UdfpsEnrollView udfpsEnrollView) {
+        if (!mIsAccessibilityEnabled) return;
+
+        final Context context = getActivity();
+        final View.OnHoverListener onHoverListener = (v, event) -> {
+            // Map the touch to portrait mode if the device is in
+            // landscape mode.
+            final Point scaledTouch =
+                    mUdfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0),
+                            event, udfpsEnrollView.getOverlayParams());
+
+            final String theStr = mUdfpsUtils.onTouchOutsideOfSensorArea(
+                    mEnrollingViewModel.isTouchExplorationEnabled(), context,
+                    scaledTouch.x, scaledTouch.y, udfpsEnrollView.getOverlayParams());
+            if (theStr != null) {
+                v.announceForAccessibility(theStr);
+            }
+            return false;
+        };
+
+        enrollLayout.findManagedViewById(isLandscape ? R.id.sud_landscape_content_area
+                : R.id.sud_layout_content).setOnHoverListener(onHoverListener);
+    }
+
+    private void onEnrollmentProgressChange(@NonNull EnrollmentProgress progress) {
+        updateProgress(true /* animate */, progress);
+
+        updateTitleAndDescription();
+
+        if (mIsAccessibilityEnabled) {
+            final int steps = progress.getSteps();
+            final int remaining = progress.getRemaining();
+            final int percent = (int) (((float) (steps - remaining) / (float) steps) * 100);
+            CharSequence announcement = getActivity().getString(
+                    R.string.security_settings_udfps_enroll_progress_a11y_message, percent);
+            mEnrollingViewModel.sendAccessibilityEvent(announcement);
+        }
+
+    }
+
+    private void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
+        if (!TextUtils.isEmpty(helpString)) {
+            showError(helpString);
+            mUdfpsEnrollHelper.onEnrollmentHelp();
+        }
+    }
+
+    private void onAcquired(boolean isAcquiredGood) {
+        if (mUdfpsEnrollHelper != null) {
+            mUdfpsEnrollHelper.onAcquired(isAcquiredGood);
+        }
+    }
+
+    private void onPointerDown(int sensorId) {
+        if (mUdfpsEnrollHelper != null) {
+            mUdfpsEnrollHelper.onPointerDown(sensorId);
+        }
+    }
+
+    private void onPointerUp(int sensorId) {
+        if (mUdfpsEnrollHelper != null) {
+            mUdfpsEnrollHelper.onPointerUp(sensorId);
+        }
+    }
+
+    private void showError(CharSequence error) {
+        mView.setHeaderText(error);
+        mView.getHeaderTextView().setContentDescription(error);
+        new GlifLayoutHelper(getActivity(), mView).setDescriptionText("");
+    }
+
+
     private final Runnable mShowDialogRunnable = new Runnable() {
         @Override
         public void run() {
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java
index 6a9adb7..6fde44c 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java
@@ -23,7 +23,6 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.util.Log;
-import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
@@ -282,19 +281,23 @@
 
     /**
      * Sends an {@link AccessibilityEvent}.
-     *
-     * @param event The event to send.
-     *
-     * @throws IllegalStateException if accessibility is not enabled.
-     *
-     * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
-     * events is through calling
-     * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
-     * instead of this method to allow predecessors to augment/filter events sent by
-     * their descendants.
      */
-    public void sendAccessibilityEvent(@NonNull AccessibilityEvent event) {
-        mAccessibilityRepository.sendAccessibilityEvent(event);
+    public void sendAccessibilityEvent(CharSequence announcement) {
+        AccessibilityEvent e = AccessibilityEvent.obtain();
+        e.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
+        e.setClassName(getClass().getName());
+        e.setPackageName(getApplication().getPackageName());
+        e.getText().add(announcement);
+        mAccessibilityRepository.sendAccessibilityEvent(e);
+    }
+
+     /**
+     * Returns if the touch exploration in the system is enabled.
+     *
+     * @return True if touch exploration is enabled, false otherwise.
+     */
+    public boolean isTouchExplorationEnabled() {
+        return mAccessibilityRepository.isTouchExplorationEnabled();
     }
 
     /**