Merge "Move the same-name-different-product configs from res folder to res-product folder" into udc-dev
diff --git a/src/com/android/settings/biometrics2/data/repository/AccessibilityRepository.java b/src/com/android/settings/biometrics2/data/repository/AccessibilityRepository.java
index 5353f89..8777b4f 100644
--- a/src/com/android/settings/biometrics2/data/repository/AccessibilityRepository.java
+++ b/src/com/android/settings/biometrics2/data/repository/AccessibilityRepository.java
@@ -16,8 +16,12 @@
 
 package com.android.settings.biometrics2.data.repository;
 
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.NonNull;
+
 /**
  * This repository is used to call all APIs in {@link AccessibilityManager}
  */
@@ -44,4 +48,21 @@
     public boolean isEnabled() {
         return mAccessibilityManager.isEnabled();
     }
+
+    /**
+     * 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) {
+        mAccessibilityManager.sendAccessibilityEvent(event);
+    }
 }
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.java
index 842bf42..74ae6fb 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.java
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.java
@@ -410,12 +410,6 @@
         }
     }
 
-    @Override
-    public void onDestroy() {
-        // TODO stopListenOrientationEvent();
-        super.onDestroy();
-    }
-
     private void animateProgress(int progress) {
         if (mProgressAnim != null) {
             mProgressAnim.cancel();
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java
index 7d2ef9f..a91e206 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.java
@@ -27,35 +27,34 @@
 import android.content.res.Configuration;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
-import android.graphics.drawable.Animatable2;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.Bundle;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
 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;
 import android.widget.ProgressBar;
-import android.widget.TextView;
+import android.widget.RelativeLayout;
 
+import androidx.activity.OnBackPressedCallback;
 import androidx.annotation.NonNull;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 import androidx.lifecycle.Observer;
 import androidx.lifecycle.ViewModelProvider;
-import androidx.transition.Transition;
-import androidx.transition.TransitionSet;
 
 import com.android.settings.R;
+import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog;
 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.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieCompositionFactory;
@@ -73,8 +72,10 @@
 public class FingerprintEnrollEnrollingSfpsFragment extends Fragment {
 
     private static final String TAG = FingerprintEnrollEnrollingSfpsFragment.class.getSimpleName();
+    private static final boolean DEBUG = false;
 
     private static final int PROGRESS_BAR_MAX = 10000;
+    private static final long ANIMATION_DURATION = 250L;
     private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500;
     private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3;
 
@@ -86,101 +87,75 @@
     private static final int SFPS_STAGE_RIGHT_EDGE = 4;
 
     private FingerprintEnrollEnrollingViewModel mEnrollingViewModel;
-    private DeviceRotationViewModel mRotationViewModel;
     private FingerprintEnrollProgressViewModel mProgressViewModel;
 
     private Interpolator mFastOutSlowInInterpolator;
-    private Interpolator mLinearOutSlowInInterpolator;
-    private Interpolator mFastOutLinearInInterpolator;
-    private boolean mAnimationCancelled;
 
     private GlifLayout mView;
     private ProgressBar mProgressBar;
     private ObjectAnimator mProgressAnim;
-    private TextView mErrorText;
-    private FooterBarMixin mFooterBarMixin;
-    private AnimatedVectorDrawable mIconAnimationDrawable;
-    private AnimatedVectorDrawable mIconBackgroundBlinksDrawable;
 
     private LottieAnimationView mIllustrationLottie;
-    private boolean mShouldShowLottie;
-    private boolean mIsAccessibilityEnabled;
 
     private boolean mHaveShownSfpsNoAnimationLottie;
     private boolean mHaveShownSfpsCenterLottie;
     private boolean mHaveShownSfpsTipLottie;
     private boolean mHaveShownSfpsLeftEdgeLottie;
     private boolean mHaveShownSfpsRightEdgeLottie;
+    private ObjectAnimator mHelpAnimation;
+    private int mIconTouchCount;
 
     private final View.OnClickListener mOnSkipClickListener =
             (v) -> mEnrollingViewModel.onCancelledDueToOnSkipPressed();
+
     private final Observer<EnrollmentProgress> mProgressObserver = progress -> {
-        // TODO
-    };
-    private final Observer<EnrollmentStatusMessage> mHelpMessageObserver = helpMessage -> {
-        // TODO
-    };
-    private final Observer<EnrollmentStatusMessage> mErrorMessageObserver = errorMessage -> {
-        // TODO
-    };
-    private final Observer<Boolean> mAcquireObserver = isAcquiredGood -> {
-        // TODO
-    };
-    private final Observer<Integer> mPointerDownObserver = sensorId -> {
-        // TODO
-    };
-    private final Observer<Integer> mPointerUpObserver = sensorId -> {
-        // TODO
+        if (DEBUG) {
+            Log.d(TAG, "mProgressObserver(" + progress + ")");
+        }
+        if (progress != null && progress.getSteps() >= 0) {
+            onEnrollmentProgressChange(progress);
+        }
     };
 
-    private int mIconTouchCount;
+    private final Observer<EnrollmentStatusMessage> mHelpMessageObserver = helpMessage -> {
+        if (DEBUG) {
+            Log.d(TAG, "mHelpMessageObserver(" + helpMessage + ")");
+        }
+        if (helpMessage != null) {
+            onEnrollmentHelp(helpMessage);
+        }
+    };
+
+    private final Observer<EnrollmentStatusMessage> mErrorMessageObserver = errorMessage -> {
+        if (DEBUG) {
+            Log.d(TAG, "mErrorMessageObserver(" + errorMessage + ")");
+        }
+        if (errorMessage != null) {
+            onEnrollmentError(errorMessage);
+        }
+    };
 
     @Override
     public void onAttach(@NonNull Context context) {
         final FragmentActivity activity = getActivity();
         final ViewModelProvider provider = new ViewModelProvider(activity);
         mEnrollingViewModel = provider.get(FingerprintEnrollEnrollingViewModel.class);
-        mRotationViewModel = provider.get(DeviceRotationViewModel.class);
         mProgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class);
         super.onAttach(context);
-        final TransitionSet transitionSet = (TransitionSet) getSharedElementEnterTransition();
-        if (transitionSet != null) {
-            transitionSet.addListener(new Transition.TransitionListener() {
-                @Override
-                public void onTransitionStart(@NonNull Transition transition) {
-
-                }
-
-                @Override
-                public void onTransitionEnd(@NonNull Transition transition) {
-                    transition.removeListener(this);
-                    mAnimationCancelled = false;
-                    startIconAnimation();
-                }
-
-                @Override
-                public void onTransitionCancel(@NonNull Transition transition) {
-
-                }
-
-                @Override
-                public void onTransitionPause(@NonNull Transition transition) {
-
-                }
-
-                @Override
-                public void onTransitionResume(@NonNull Transition transition) {
-
-                }
-            });
-        }
+        requireActivity().getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
+            @Override
+            public void handleOnBackPressed() {
+                setEnabled(false);
+                mEnrollingViewModel.setOnBackPressed();
+                cancelEnrollment();
+            }
+        });
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mEnrollingViewModel.restoreSavedState(savedInstanceState);
-        mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled();
     }
 
     @Override
@@ -193,6 +168,7 @@
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         mView = initSfpsLayout(inflater, container);
+        maybeHideSfpsText(getActivity().getResources().getConfiguration());
         return mView;
     }
 
@@ -204,13 +180,22 @@
         new GlifLayoutHelper(activity, containView).setDescriptionText(
                 getString(R.string.security_settings_fingerprint_enroll_start_message));
 
-        mShouldShowLottie = shouldShowLottie(); // Move shouldShowLottie into updateOrientation()?
+        // setHelpAnimation()
+        final float translationX = 40;
+        final int duration = 550;
+        final RelativeLayout progressLottieLayout = containView.findViewById(R.id.progress_lottie);
+        mHelpAnimation = ObjectAnimator.ofFloat(progressLottieLayout,
+                "translationX" /* propertyName */,
+                0, translationX, -1 * translationX, translationX, 0f);
+        mHelpAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
+        mHelpAnimation.setDuration(duration);
+        mHelpAnimation.setAutoCancel(false);
+
         mIllustrationLottie = containView.findViewById(R.id.illustration_lottie);
 
-        mErrorText = containView.findViewById(R.id.error_text);
         mProgressBar = containView.findViewById(R.id.fingerprint_progress_bar);
-        mFooterBarMixin = containView.getMixin(FooterBarMixin.class);
-        mFooterBarMixin.setSecondaryButton(
+        final FooterBarMixin footerBarMixin = containView.getMixin(FooterBarMixin.class);
+        footerBarMixin.setSecondaryButton(
                 new FooterButton.Builder(activity)
                         .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
                         .setListener(mOnSkipClickListener)
@@ -221,10 +206,6 @@
 
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
                 activity, android.R.interpolator.fast_out_slow_in);
-        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
-                activity, android.R.interpolator.linear_out_slow_in);
-        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
-                activity, android.R.interpolator.fast_out_linear_in);
 
         mProgressBar.setProgressBackgroundTintMode(PorterDuff.Mode.SRC);
         mProgressBar.setOnTouchListener((v, event) -> {
@@ -243,7 +224,6 @@
             return true;
         });
 
-        maybeHideSfpsText(activity.getResources().getConfiguration());
         return containView;
     }
 
@@ -251,46 +231,135 @@
     public void onStart() {
         super.onStart();
         startEnrollment();
-        updateProgress(false /* animate */);
-        updateTitleAndDescription(new GlifLayoutHelper(getActivity(), mView));
-        if (true /* TODO mRestoring */) {
-            startIconAnimation();
-        }
+        updateProgress(false /* animate */, mProgressViewModel.getProgressLiveData().getValue());
+        updateTitleAndDescription();
     }
 
     @Override
     public void onStop() {
-        stopIconAnimation();
-        mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver);
-        mProgressViewModel.getHelpMessageLiveData().removeObserver(mHelpMessageObserver);
-        mProgressViewModel.getErrorMessageLiveData().removeObserver(mErrorMessageObserver);
-        mProgressViewModel.getAcquireLiveData().removeObserver(mAcquireObserver);
-        mProgressViewModel.getPointerDownLiveData().removeObserver(mPointerDownObserver);
-        mProgressViewModel.getPointerUpLiveData().removeObserver(mPointerUpObserver);
-        if (!getActivity().isChangingConfigurations()) {
+        removeEnrollmentObservers();
+        if (!getActivity().isChangingConfigurations() && mProgressViewModel.isEnrolling()) {
             mProgressViewModel.cancelEnrollment();
         }
         super.onStop();
     }
 
+    private void removeEnrollmentObservers() {
+        preRemoveEnrollmentObservers();
+        mProgressViewModel.getErrorMessageLiveData().removeObserver(mErrorMessageObserver);
+    }
+
+    private void preRemoveEnrollmentObservers() {
+        mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver);
+        mProgressViewModel.getHelpMessageLiveData().removeObserver(mHelpMessageObserver);
+    }
+
+    private void cancelEnrollment() {
+        preRemoveEnrollmentObservers();
+        mProgressViewModel.cancelEnrollment();
+    }
+
     private void startEnrollment() {
+        final boolean startResult = mProgressViewModel.startEnrollment(ENROLL_ENROLL);
+        if (!startResult) {
+            Log.e(TAG, "startEnrollment(), failed");
+        }
         mProgressViewModel.getProgressLiveData().observe(this, mProgressObserver);
         mProgressViewModel.getHelpMessageLiveData().observe(this, mHelpMessageObserver);
         mProgressViewModel.getErrorMessageLiveData().observe(this, mErrorMessageObserver);
-        mProgressViewModel.getAcquireLiveData().observe(this, mAcquireObserver);
-        mProgressViewModel.getPointerDownLiveData().observe(this, mPointerDownObserver);
-        mProgressViewModel.getPointerUpLiveData().observe(this, mPointerUpObserver);
-        mProgressViewModel.startEnrollment(ENROLL_ENROLL);
     }
 
-    private void updateProgress(boolean animate) {
+    private void configureEnrollmentStage(CharSequence description, @RawRes int lottie) {
+        new GlifLayoutHelper(getActivity(), mView).setDescriptionText(description);
+        LottieCompositionFactory.fromRawRes(getActivity(), lottie)
+                .addListener((c) -> {
+                    mIllustrationLottie.setComposition(c);
+                    mIllustrationLottie.setVisibility(View.VISIBLE);
+                    mIllustrationLottie.playAnimation();
+                });
+    }
+
+    private int getCurrentSfpsStage() {
+        EnrollmentProgress progressLiveData = mProgressViewModel.getProgressLiveData().getValue();
+
+        if (progressLiveData == null) {
+            return STAGE_UNKNOWN;
+        }
+
+        final int progressSteps = progressLiveData.getSteps() - progressLiveData.getRemaining();
+        if (progressSteps < getStageThresholdSteps(0)) {
+            return SFPS_STAGE_NO_ANIMATION;
+        } else if (progressSteps < getStageThresholdSteps(1)) {
+            return SFPS_STAGE_CENTER;
+        } else if (progressSteps < getStageThresholdSteps(2)) {
+            return SFPS_STAGE_FINGERTIP;
+        } else if (progressSteps < getStageThresholdSteps(3)) {
+            return SFPS_STAGE_LEFT_EDGE;
+        } else {
+            return SFPS_STAGE_RIGHT_EDGE;
+        }
+    }
+
+    private void onEnrollmentHelp(@NonNull EnrollmentStatusMessage helpMessage) {
+        final CharSequence helpStr = helpMessage.getStr();
+        if (!TextUtils.isEmpty(helpStr)) {
+            showError(helpStr);
+        }
+    }
+
+    private void onEnrollmentError(@NonNull EnrollmentStatusMessage errorMessage) {
+        removeEnrollmentObservers();
+
+        if (mEnrollingViewModel.getOnBackPressed()
+                && errorMessage.getMsgId() == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
+            mEnrollingViewModel.onCancelledDueToOnBackPressed();
+        } else if (mEnrollingViewModel.getOnSkipPressed()
+                && errorMessage.getMsgId() == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
+            mEnrollingViewModel.onCancelledDueToOnSkipPressed();
+        } else {
+            final int errMsgId = errorMessage.getMsgId();
+            mEnrollingViewModel.showErrorDialog(
+                    new FingerprintEnrollEnrollingViewModel.ErrorDialogData(
+                            getString(FingerprintErrorDialog.getErrorMessage(errMsgId)),
+                            getString(FingerprintErrorDialog.getErrorTitle(errMsgId)),
+                            errMsgId
+                    ));
+            mProgressViewModel.cancelEnrollment();
+        }
+    }
+
+    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);
+    }
+
+    private void onEnrollmentProgressChange(@NonNull EnrollmentProgress progress) {
+        updateProgress(true /* animate */, progress);
+        if (mEnrollingViewModel.isAccessibilityEnabled()) {
+            final int percent = (int) (((float) (progress.getSteps() - progress.getRemaining())
+                    / (float) progress.getSteps()) * 100);
+
+            CharSequence announcement = getString(
+                    R.string.security_settings_sfps_enroll_progress_a11y_message, percent);
+            announceEnrollmentProgress(announcement);
+
+            mIllustrationLottie.setContentDescription(
+                    getString(R.string.security_settings_sfps_animation_a11y_label, percent)
+            );
+        }
+        updateTitleAndDescription();
+    }
+
+    private void updateProgress(boolean animate, @NonNull EnrollmentProgress enrollmentProgress) {
         if (!mProgressViewModel.isEnrolling()) {
             Log.d(TAG, "Enrollment not started yet");
             return;
         }
 
-        final EnrollmentProgress enrollmentProgress =
-                mProgressViewModel.getProgressLiveData().getValue();
         final int progress = getProgress(enrollmentProgress);
         // Only clear the error when progress has been made.
         // TODO (b/234772728) Add tests.
@@ -318,19 +387,43 @@
         return PROGRESS_BAR_MAX * displayProgress / (progress.getSteps() + 1);
     }
 
+    private void showError(CharSequence error) {
+        mView.setHeaderText(error);
+        mView.getHeaderTextView().setContentDescription(error);
+        new GlifLayoutHelper(getActivity(), mView).setDescriptionText("");
+        if (!mHelpAnimation.isRunning()) {
+            mHelpAnimation.start();
+        }
+        applySfpsErrorDynamicColors(true);
+        if (isResumed() && mEnrollingViewModel.isAccessibilityEnabled()) {
+            mEnrollingViewModel.vibrateError(getClass().getSimpleName() + "::showError");
+        }
+    }
+
     private void clearError() {
         applySfpsErrorDynamicColors(false);
     }
 
+    private void animateProgress(int progress) {
+        if (mProgressAnim != null) {
+            mProgressAnim.cancel();
+        }
+        ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress",
+                mProgressBar.getProgress(), progress);
+        anim.addListener(mProgressAnimationListener);
+        anim.setInterpolator(mFastOutSlowInInterpolator);
+        anim.setDuration(ANIMATION_DURATION);
+        anim.start();
+        mProgressAnim = anim;
+    }
+
     /**
      * Applies dynamic colors corresponding to showing or clearing errors on the progress bar
      * and finger lottie for SFPS
      */
     private void applySfpsErrorDynamicColors(boolean isError) {
         applyProgressBarDynamicColor(isError);
-        if (mIllustrationLottie != null) {
-            applyLottieDynamicColor(isError);
-        }
+        applyLottieDynamicColor(isError);
     }
 
     private void applyProgressBarDynamicColor(boolean isError) {
@@ -358,36 +451,39 @@
         mIllustrationLottie.invalidate();
     }
 
-    @Override
-    public void onDestroy() {
-        // TODO stopListenOrientationEvent();
-        super.onDestroy();
-    }
+    private int getStageThresholdSteps(int index) {
+        final EnrollmentProgress progressLiveData =
+                mProgressViewModel.getProgressLiveData().getValue();
 
-    private void animateProgress(int progress) {
-        if (mProgressAnim != null) {
-            mProgressAnim.cancel();
+        if (progressLiveData == null || progressLiveData.getSteps() == -1) {
+            Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet");
+            return 1;
         }
-        ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress",
-                mProgressBar.getProgress(), progress);
-        anim.addListener(mProgressAnimationListener);
-        anim.setInterpolator(mFastOutSlowInInterpolator);
-        anim.setDuration(250);
-        anim.start();
-        mProgressAnim = anim;
+        return Math.round(progressLiveData.getSteps()
+                * mEnrollingViewModel.getEnrollStageThreshold(index));
     }
 
-    private void updateTitleAndDescription(@NonNull GlifLayoutHelper glifLayoutHelper) {
-        if (mIsAccessibilityEnabled) {
+    private void updateTitleAndDescription() {
+        final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(getActivity(), mView);
+        if (mEnrollingViewModel.isAccessibilityEnabled()) {
             mEnrollingViewModel.clearTalkback();
             glifLayoutHelper.getGlifLayout().getDescriptionTextView().setAccessibilityLiveRegion(
                     View.ACCESSIBILITY_LIVE_REGION_POLITE);
         }
-        switch (getCurrentSfpsStage()) {
+        final int stage = getCurrentSfpsStage();
+        if (DEBUG) {
+            Log.d(TAG, "updateTitleAndDescription, stage:" + stage
+                    + ", noAnimation:" + mHaveShownSfpsNoAnimationLottie
+                    + ", center:" + mHaveShownSfpsCenterLottie
+                    + ", tip:" + mHaveShownSfpsTipLottie
+                    + ", leftEdge:" + mHaveShownSfpsLeftEdgeLottie
+                    + ", rightEdge:" + mHaveShownSfpsRightEdgeLottie);
+        }
+        switch (stage) {
             case SFPS_STAGE_NO_ANIMATION:
                 glifLayoutHelper.setHeaderText(
                         R.string.security_settings_fingerprint_enroll_repeat_title);
-                if (!mHaveShownSfpsNoAnimationLottie && mIllustrationLottie != null) {
+                if (!mHaveShownSfpsNoAnimationLottie) {
                     mHaveShownSfpsNoAnimationLottie = true;
                     mIllustrationLottie.setContentDescription(
                             getString(
@@ -405,7 +501,7 @@
             case SFPS_STAGE_CENTER:
                 glifLayoutHelper.setHeaderText(
                         R.string.security_settings_sfps_enroll_finger_center_title);
-                if (!mHaveShownSfpsCenterLottie && mIllustrationLottie != null) {
+                if (!mHaveShownSfpsCenterLottie) {
                     mHaveShownSfpsCenterLottie = true;
                     configureEnrollmentStage(
                             getString(R.string.security_settings_sfps_enroll_start_message),
@@ -417,7 +513,7 @@
             case SFPS_STAGE_FINGERTIP:
                 glifLayoutHelper.setHeaderText(
                         R.string.security_settings_sfps_enroll_fingertip_title);
-                if (!mHaveShownSfpsTipLottie && mIllustrationLottie != null) {
+                if (!mHaveShownSfpsTipLottie) {
                     mHaveShownSfpsTipLottie = true;
                     configureEnrollmentStage("", R.raw.sfps_lottie_tip);
                 }
@@ -426,7 +522,7 @@
             case SFPS_STAGE_LEFT_EDGE:
                 glifLayoutHelper.setHeaderText(
                         R.string.security_settings_sfps_enroll_left_edge_title);
-                if (!mHaveShownSfpsLeftEdgeLottie && mIllustrationLottie != null) {
+                if (!mHaveShownSfpsLeftEdgeLottie) {
                     mHaveShownSfpsLeftEdgeLottie = true;
                     configureEnrollmentStage("", R.raw.sfps_lottie_left_edge);
                 }
@@ -435,7 +531,7 @@
             case SFPS_STAGE_RIGHT_EDGE:
                 glifLayoutHelper.setHeaderText(
                         R.string.security_settings_sfps_enroll_right_edge_title);
-                if (!mHaveShownSfpsRightEdgeLottie && mIllustrationLottie != null) {
+                if (!mHaveShownSfpsRightEdgeLottie) {
                     mHaveShownSfpsRightEdgeLottie = true;
                     configureEnrollmentStage("", R.raw.sfps_lottie_right_edge);
                 }
@@ -460,7 +556,38 @@
         }
     }
 
-    private void maybeHideSfpsText(@android.annotation.NonNull Configuration newConfig) {
+    private void showIconTouchDialog() {
+        mIconTouchCount = 0;
+        //TODO EnrollingActivity should observe live data and add dialog fragment
+        mEnrollingViewModel.onIconTouchDialogShow();
+    }
+
+    private final Runnable mShowDialogRunnable = () -> showIconTouchDialog();
+
+    private final Animator.AnimatorListener mProgressAnimationListener =
+            new Animator.AnimatorListener() {
+
+                @Override
+                public void onAnimationStart(Animator animation) { }
+
+                @Override
+                public void onAnimationRepeat(Animator animation) { }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) {
+                        mProgressBar.postDelayed(mDelayedFinishRunnable, ANIMATION_DURATION);
+                    }
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) { }
+            };
+
+    // Give the user a chance to see progress completed before jumping to the next stage.
+    private final Runnable mDelayedFinishRunnable = () -> mEnrollingViewModel.onEnrollingDone();
+
+    private void maybeHideSfpsText(@NonNull Configuration newConfig) {
         final HeaderMixin headerMixin = ((GlifLayout) mView).getMixin(HeaderMixin.class);
         final DescriptionMixin descriptionMixin = ((GlifLayout) mView).getMixin(
                 DescriptionMixin.class);
@@ -480,137 +607,4 @@
         }
 
     }
-
-    private int getCurrentSfpsStage() {
-        EnrollmentProgress progressLiveData = mProgressViewModel.getProgressLiveData().getValue();
-
-        if (progressLiveData == null || progressLiveData.getSteps() == -1) {
-            return STAGE_UNKNOWN;
-        }
-
-        final int progressSteps = progressLiveData.getSteps() - progressLiveData.getRemaining();
-        if (progressSteps < getStageThresholdSteps(0)) {
-            return SFPS_STAGE_NO_ANIMATION;
-        } else if (progressSteps < getStageThresholdSteps(1)) {
-            return SFPS_STAGE_CENTER;
-        } else if (progressSteps < getStageThresholdSteps(2)) {
-            return SFPS_STAGE_FINGERTIP;
-        } else if (progressSteps < getStageThresholdSteps(3)) {
-            return SFPS_STAGE_LEFT_EDGE;
-        } else {
-            return SFPS_STAGE_RIGHT_EDGE;
-        }
-    }
-
-    private int getStageThresholdSteps(int index) {
-        final EnrollmentProgress progressLiveData =
-                mProgressViewModel.getProgressLiveData().getValue();
-
-        if (progressLiveData == null || progressLiveData.getSteps() == -1) {
-            Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet");
-            return 1;
-        }
-        return Math.round(progressLiveData.getSteps()
-                * mEnrollingViewModel.getEnrollStageThreshold(index));
-    }
-
-    private void updateOrientation() {
-        mIllustrationLottie = mView.findViewById(R.id.illustration_lottie);
-    }
-
-    private boolean shouldShowLottie() {
-        DisplayDensityUtils displayDensity = new DisplayDensityUtils(getContext());
-        int currentDensityIndex = displayDensity.getCurrentIndexForDefaultDisplay();
-        final int currentDensity = displayDensity.getDefaultDisplayDensityValues()
-                [currentDensityIndex];
-        final int defaultDensity = displayDensity.getDefaultDensityForDefaultDisplay();
-        return defaultDensity == currentDensity;
-    }
-
-
-    private void startIconAnimation() {
-        if (mIconAnimationDrawable != null) {
-            mIconAnimationDrawable.start();
-        }
-    }
-
-    private void stopIconAnimation() {
-        mAnimationCancelled = true;
-        if (mIconAnimationDrawable != null) {
-            mIconAnimationDrawable.stop();
-        }
-    }
-
-    private void showIconTouchDialog() {
-        mIconTouchCount = 0;
-        //TODO EnrollingActivity should observe live data and add dialog fragment
-        mEnrollingViewModel.onIconTouchDialogShow();
-    }
-
-    private void configureEnrollmentStage(CharSequence description, @RawRes int lottie) {
-        new GlifLayoutHelper(getActivity(), mView).setDescriptionText(description);
-        LottieCompositionFactory.fromRawRes(getActivity(), lottie)
-                .addListener((c) -> {
-                    mIllustrationLottie.setComposition(c);
-                    mIllustrationLottie.setVisibility(View.VISIBLE);
-                    mIllustrationLottie.playAnimation();
-                });
-    }
-
-    private final Runnable mShowDialogRunnable = new Runnable() {
-        @Override
-        public void run() {
-            showIconTouchDialog();
-        }
-    };
-
-    private final Animator.AnimatorListener mProgressAnimationListener =
-            new Animator.AnimatorListener() {
-
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    startIconAnimation();
-                }
-
-                @Override
-                public void onAnimationRepeat(Animator animation) { }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    stopIconAnimation();
-
-                    if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) {
-                        mProgressBar.postDelayed(mDelayedFinishRunnable, 250L);
-                    }
-                }
-
-                @Override
-                public void onAnimationCancel(Animator animation) { }
-            };
-
-    // Give the user a chance to see progress completed before jumping to the next stage.
-    private final Runnable mDelayedFinishRunnable = new Runnable() {
-        @Override
-        public void run() {
-            /* TODO launchFinish(); */
-        }
-    };
-
-    private final Animatable2.AnimationCallback mIconAnimationCallback =
-            new Animatable2.AnimationCallback() {
-                @Override
-                public void onAnimationEnd(Drawable d) {
-                    if (mAnimationCancelled) {
-                        return;
-                    }
-
-                    // Start animation after it has ended.
-                    mProgressBar.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            startIconAnimation();
-                        }
-                    });
-                }
-            };
 }
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.java
index 1ea26fa..61f8e82 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.java
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.java
@@ -25,6 +25,7 @@
 import android.os.Bundle;
 import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -32,6 +33,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.LiveData;
 import androidx.lifecycle.Observer;
 import androidx.lifecycle.ViewModelProvider;
 
@@ -39,6 +41,7 @@
 import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation;
 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.FingerprintEnrollFindSensorViewModel;
 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
 
@@ -67,12 +70,23 @@
 
     private FingerprintEnrollFindSensorViewModel mViewModel;
     private FingerprintEnrollProgressViewModel mProgressViewModel;
+    private DeviceRotationViewModel mRotationViewModel;
 
     private View mView;
     private GlifLayout mGlifLayout;
     private FooterBarMixin mFooterBarMixin;
     private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
     @Nullable private FingerprintFindSensorAnimation mAnimation;
+    @Surface.Rotation private int mLastRotation = -1;
+
+    private final Observer<Integer> mRotationObserver = rotation -> {
+        if (DEBUG) {
+            Log.d(TAG, "rotationObserver " + rotation);
+        }
+        if (rotation != null) {
+            onRotationChanged(rotation);
+        }
+    };
 
     private final Observer<EnrollmentProgress> mProgressObserver = progress -> {
         if (DEBUG) {
@@ -85,15 +99,10 @@
 
     private final Observer<EnrollmentStatusMessage> mLastCancelMessageObserver = errorMessage -> {
         if (DEBUG) {
-            Log.d(TAG, "mErrorMessageObserver(" + errorMessage + ")");
+            Log.d(TAG, "mLastCancelMessageObserver(" + errorMessage + ")");
         }
         if (errorMessage != null) {
-            if (errorMessage.getMsgId() == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
-                mProgressViewModel.clearProgressLiveData();
-                mViewModel.onStartButtonClick();
-            } else {
-                Log.e(TAG, "mErrorMessageObserver(" + errorMessage + ")");
-            }
+            onLastCancelMessage(errorMessage);
         }
     };
 
@@ -144,6 +153,10 @@
 
     @Override
     public void onResume() {
+        final LiveData<Integer> rotationLiveData = mRotationViewModel.getLiveData();
+        mLastRotation = rotationLiveData.getValue();
+        rotationLiveData.observe(this, mRotationObserver);
+
         if (mAnimation != null) {
             if (DEBUG) {
                 Log.d(TAG, "onResume(), start animation");
@@ -198,15 +211,39 @@
             return;
         }
 
+        if (waitForLastCancelErrMsg) {
+            mProgressViewModel.clearErrorMessageLiveData(); // Prevent got previous error message
+            mProgressViewModel.getErrorMessageLiveData().observe(this,
+                    mLastCancelMessageObserver);
+        }
+
         mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver);
         final boolean cancelResult = mProgressViewModel.cancelEnrollment();
         if (!cancelResult) {
             Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment");
         }
+    }
 
-        if (waitForLastCancelErrMsg) {
-            mProgressViewModel.getErrorMessageLiveData().observe(this,
-                    mLastCancelMessageObserver);
+    private void onRotationChanged(@Surface.Rotation int newRotation) {
+        if (DEBUG) {
+            Log.d(TAG, "onRotationChanged() from " + mLastRotation + " to " + newRotation);
+        }
+        if (newRotation % 2 != mLastRotation % 2) {
+            // Fragment is going to be recreated, just stopLookingForFingerprint() here.
+            stopLookingForFingerprint(true);
+        }
+    }
+
+    private void onLastCancelMessage(@NonNull EnrollmentStatusMessage errorMessage) {
+        if (errorMessage.getMsgId() == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
+            final EnrollmentProgress progress = mProgressViewModel.getProgressLiveData().getValue();
+            mProgressViewModel.clearProgressLiveData();
+            mProgressViewModel.getErrorMessageLiveData().removeObserver(mLastCancelMessageObserver);
+            if (progress != null && !progress.isInitialStep()) {
+                mViewModel.onStartButtonClick();
+            }
+        } else {
+            Log.e(TAG, "mErrorMessageObserver(" + errorMessage + ")");
         }
     }
 
@@ -227,6 +264,7 @@
         final ViewModelProvider provider = new ViewModelProvider(activity);
         mViewModel = provider.get(FingerprintEnrollFindSensorViewModel.class);
         mProgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class);
+        mRotationViewModel = provider.get(DeviceRotationViewModel.class);
         super.onAttach(context);
     }
 }
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java
index c363f04..75207ca 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java
@@ -20,6 +20,7 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -39,6 +40,7 @@
 
 import com.android.settings.R;
 import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
+import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage;
 import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel;
 import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
@@ -75,30 +77,36 @@
     private DeviceRotationViewModel mRotationViewModel;
     private DeviceFoldedViewModel mFoldedViewModel;
 
+    private GlifLayout mView;
+    private FooterBarMixin mFooterBarMixin;
+    private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
+    private LottieAnimationView mIllustrationLottie;
+    @Surface.Rotation private int mAnimationRotation = -1;
+
     private final Observer<Integer> mRotationObserver = rotation -> {
         if (DEBUG) {
             Log.d(TAG, "rotationObserver " + rotation);
         }
-        if (rotation == null) {
-            return;
+        if (rotation != null) {
+            onRotationChanged(rotation);
         }
-        onRotationChanged(rotation);
     };
 
-    @Surface.Rotation private int mAnimationRotation = -1;
-
-    private View mView;
-    private GlifLayout mGlifLayout;
-    private FooterBarMixin mFooterBarMixin;
-    private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
-    private LottieAnimationView mIllustrationLottie;
-
     private final Observer<EnrollmentProgress> mProgressObserver = progress -> {
         if (DEBUG) {
             Log.d(TAG, "mProgressObserver(" + progress + ")");
         }
         if (progress != null && !progress.isInitialStep()) {
-            mViewModel.onStartButtonClick();
+            stopLookingForFingerprint(true);
+        }
+    };
+
+    private final Observer<EnrollmentStatusMessage> mLastCancelMessageObserver = errorMessage -> {
+        if (DEBUG) {
+            Log.d(TAG, "mLastCancelMessageObserver(" + errorMessage + ")");
+        }
+        if (errorMessage != null) {
+            onLastCancelMessage(errorMessage);
         }
     };
 
@@ -107,10 +115,10 @@
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
             @Nullable Bundle savedInstanceState) {
         final Context context = inflater.getContext();
-        mView = inflater.inflate(R.layout.sfps_enroll_find_sensor_layout, container, false);
-        mGlifLayout = mView.findViewById(R.id.setup_wizard_layout);
+        mView = (GlifLayout) inflater.inflate(R.layout.sfps_enroll_find_sensor_layout, container,
+                false);
         mIllustrationLottie = mView.findViewById(R.id.illustration_lottie);
-        mFooterBarMixin = mGlifLayout.getMixin(FooterBarMixin.class);
+        mFooterBarMixin = mView.getMixin(FooterBarMixin.class);
         mFooterBarMixin.setSecondaryButton(
                 new FooterButton.Builder(context)
                         .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
@@ -125,7 +133,7 @@
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         final Activity activity = getActivity();
-        final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mGlifLayout);
+        final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mView);
         glifLayoutHelper.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title);
         glifLayoutHelper.setDescriptionText(
                 getText(R.string.security_settings_sfps_enroll_find_sensor_message));
@@ -136,10 +144,13 @@
     public void onStart() {
         super.onStart();
 
+        final boolean isEnrolling = mProgressViewModel.isEnrolling();
         if (DEBUG) {
-            Log.d(TAG, "onStart(), start looking for fingerprint");
+            Log.d(TAG, "onStart(), isEnrolling:" + isEnrolling);
         }
-        startLookingForFingerprint();
+        if (!isEnrolling) {
+            startLookingForFingerprint();
+        }
     }
 
     @Override
@@ -159,10 +170,13 @@
     @Override
     public void onStop() {
         super.onStop();
+        final boolean isEnrolling = mProgressViewModel.isEnrolling();
         if (DEBUG) {
-            Log.d(TAG, "onStop(), stop looking for fingerprint");
+            Log.d(TAG, "onStop(), isEnrolling:" + isEnrolling);
         }
-        stopLookingForFingerprint();
+        if (isEnrolling) {
+            stopLookingForFingerprint(false);
+        }
     }
 
     private void startLookingForFingerprint() {
@@ -180,13 +194,19 @@
         }
     }
 
-    private void stopLookingForFingerprint() {
+    private void stopLookingForFingerprint(boolean waitForLastCancelErrMsg) {
         if (!mProgressViewModel.isEnrolling()) {
             Log.d(TAG, "stopLookingForFingerprint(), failed because isEnrolling is false before"
                     + " stopping");
             return;
         }
 
+        if (waitForLastCancelErrMsg) {
+            mProgressViewModel.clearErrorMessageLiveData(); // Prevent got previous error message
+            mProgressViewModel.getErrorMessageLiveData().observe(this,
+                    mLastCancelMessageObserver);
+        }
+
         mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver);
         final boolean cancelResult = mProgressViewModel.cancelEnrollment();
         if (!cancelResult) {
@@ -199,10 +219,25 @@
             Log.d(TAG, "onRotationChanged() from " + mAnimationRotation + " to " + newRotation);
         }
         if ((newRotation + 2) % 4 == mAnimationRotation) {
+            // Fragment not changed, we just need to play correct rotation animation
             playLottieAnimation(newRotation);
+        } else if (newRotation % 2 != mAnimationRotation % 2) {
+            // Fragment is going to be recreated, just stopLookingForFingerprint() here.
+            stopLookingForFingerprint(true);
         }
-        // Fragment will be re-created if it's changed between landscape and portrait, so no need to
-        // handle other cases.
+    }
+
+    private void onLastCancelMessage(@NonNull EnrollmentStatusMessage errorMessage) {
+        if (errorMessage.getMsgId() == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
+            final EnrollmentProgress progress = mProgressViewModel.getProgressLiveData().getValue();
+            mProgressViewModel.clearProgressLiveData();
+            mProgressViewModel.getErrorMessageLiveData().removeObserver(mLastCancelMessageObserver);
+            if (progress != null && !progress.isInitialStep()) {
+                mViewModel.onStartButtonClick();
+            }
+        } else {
+            Log.e(TAG, "mErrorMessageObserver(" + errorMessage + ")");
+        }
     }
 
     private void playLottieAnimation(@Surface.Rotation int rotation) {
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java
index bf7b0ac..6a9adb7 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java
@@ -23,6 +23,8 @@
 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;
 
 import androidx.annotation.NonNull;
@@ -279,6 +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);
+    }
+
+    /**
      * Like {@link #vibrate(VibrationEffect, VibrationAttributes)}, but allows the
      * caller to specify the vibration is owned by someone else and set a reason for vibration.
      */
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
index eebbc42..2a918f5 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
@@ -92,6 +92,9 @@
 
         @Override
         public void onEnrollmentError(int errMsgId, CharSequence errString) {
+            if (DEBUG) {
+                Log.d(TAG, "onEnrollmentError(" + errMsgId + ", " + errString + ")");
+            }
             mErrorMessageLiveData.postValue(new EnrollmentStatusMessage(errMsgId, errString));
         }
 
@@ -146,6 +149,13 @@
         mErrorMessageLiveData.setValue(null);
     }
 
+    /**
+     * clear error message
+     */
+    public void clearErrorMessageLiveData() {
+        mErrorMessageLiveData.setValue(null);
+    }
+
     public LiveData<EnrollmentProgress> getProgressLiveData() {
         return mProgressLiveData;
     }
diff --git a/src/com/android/settings/datausage/BillingCycleSettings.java b/src/com/android/settings/datausage/BillingCycleSettings.java
index 395d1c7..3047d73 100644
--- a/src/com/android/settings/datausage/BillingCycleSettings.java
+++ b/src/com/android/settings/datausage/BillingCycleSettings.java
@@ -27,6 +27,7 @@
 import android.net.NetworkPolicy;
 import android.net.NetworkTemplate;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.text.method.NumberKeyListener;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -54,6 +55,7 @@
 
 import java.text.NumberFormat;
 import java.text.ParseException;
+import java.util.Optional;
 import java.util.TimeZone;
 
 @SearchIndexable
@@ -115,6 +117,18 @@
 
         Bundle args = getArguments();
         mNetworkTemplate = args.getParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE);
+        if (mNetworkTemplate == null && getIntent() != null) {
+            mNetworkTemplate = getIntent().getParcelableExtra(Settings.EXTRA_NETWORK_TEMPLATE);
+        }
+
+        if (mNetworkTemplate == null) {
+            Optional<NetworkTemplate> mobileNetworkTemplateFromSim =
+                    DataUsageUtils.getMobileNetworkTemplateFromSubId(context, getIntent());
+            if (mobileNetworkTemplateFromSim.isPresent()) {
+                mNetworkTemplate = mobileNetworkTemplateFromSim.get();
+            }
+        }
+
         if (mNetworkTemplate == null) {
             mNetworkTemplate = DataUsageUtils.getDefaultTemplate(context,
                 DataUsageUtils.getDefaultSubscriptionId(context));
diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java
index 1499ded..68751e5 100644
--- a/src/com/android/settings/datausage/DataUsageList.java
+++ b/src/com/android/settings/datausage/DataUsageList.java
@@ -69,6 +69,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Panel showing data usage history across various networks, including options
@@ -266,6 +267,14 @@
             mSubId = intent.getIntExtra(Settings.EXTRA_SUB_ID,
                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
             mTemplate = intent.getParcelableExtra(Settings.EXTRA_NETWORK_TEMPLATE);
+
+            if (mTemplate == null) {
+                Optional<NetworkTemplate> mobileNetworkTemplateFromSim =
+                        DataUsageUtils.getMobileNetworkTemplateFromSubId(getContext(), getIntent());
+                if (mobileNetworkTemplateFromSim.isPresent()) {
+                    mTemplate = mobileNetworkTemplateFromSim.get();
+                }
+            }
         }
     }
 
diff --git a/src/com/android/settings/datausage/DataUsageUtils.java b/src/com/android/settings/datausage/DataUsageUtils.java
index 9fec27e..cce7ca1 100644
--- a/src/com/android/settings/datausage/DataUsageUtils.java
+++ b/src/com/android/settings/datausage/DataUsageUtils.java
@@ -22,11 +22,13 @@
 import android.app.usage.NetworkStats.Bucket;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkTemplate;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -39,6 +41,7 @@
 import com.android.settings.network.ProxySubscriptionManager;
 
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Utility methods for data usage classes.
@@ -193,4 +196,22 @@
         }
     }
 
+    /**
+     * Returns a mobile NetworkTemplate if EXTRA_SUB_ID of the Intent is available and the subId
+     * is valid & hasMobileData. Otherwise, returns empty data.
+     */
+    public static Optional<NetworkTemplate> getMobileNetworkTemplateFromSubId(Context context,
+            Intent intent) {
+        if (intent == null || !intent.hasExtra(Settings.EXTRA_SUB_ID)) {
+            return Optional.empty();
+        }
+
+        int subId = intent.getIntExtra(Settings.EXTRA_SUB_ID,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        if (SubscriptionManager.isValidSubscriptionId(subId) && hasMobileData(context)) {
+            return Optional.of(DataUsageLib.getMobileTemplate(context, subId));
+        }
+
+        return  Optional.empty();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
index 695e86b..16d1483 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
@@ -56,9 +56,11 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.settings.R;
 import com.android.settings.testutils.XmlTestUtils;
+import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
 import com.android.settings.testutils.shadow.ShadowFragment;
 import com.android.settings.testutils.shadow.ShadowUserManager;
 import com.android.settingslib.RestrictedPreference;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.search.SearchIndexableRaw;
 
@@ -85,6 +87,7 @@
 
 /** Test for {@link AccessibilitySettings}. */
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothUtils.class})
 public class AccessibilitySettingsTest {
     private static final String PACKAGE_NAME = "com.android.test";
     private static final String CLASS_NAME = PACKAGE_NAME + ".test_a11y_service";
@@ -116,6 +119,8 @@
     private ShadowAccessibilityManager mShadowAccessibilityManager;
     @Mock
     private AppOpsManager mAppOpsManager;
+    @Mock
+    private LocalBluetoothManager mLocalBluetoothManager;
 
     private Lifecycle mLifecycle;
 
@@ -134,6 +139,7 @@
                 anyInt(), anyString())).thenReturn(AppOpsManager.MODE_ALLOWED);
         mLifecycle = new Lifecycle(() -> mLifecycle);
         when(mFragment.getSettingsLifecycle()).thenReturn(mLifecycle);
+        ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
     }
 
     @Test