diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 73de1834283..42d60eee989 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1664,9 +1664,4 @@ [CHAR LIMIT=NONE] --> - - - - diff --git a/res/values/config.xml b/res/values/config.xml index 2d04bc9d28f..e3b8618f482 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -645,4 +645,11 @@ true + + false + + false + + false + diff --git a/res/values/integers.xml b/res/values/integers.xml index d110de253e7..530f9874a31 100644 --- a/res/values/integers.xml +++ b/res/values/integers.xml @@ -26,4 +26,11 @@ 1 1 + + + 0 + + 0 + + 0 diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 897e2909fd1..2eadc33c85f 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -350,8 +350,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { @Override protected BiometricEnrollSidecar getSidecar() { - final FingerprintEnrollSidecar sidecar = new FingerprintEnrollSidecar(); - sidecar.setEnrollReason(FingerprintManager.ENROLL_ENROLL); + final FingerprintEnrollSidecar sidecar = new FingerprintEnrollSidecar(this, + FingerprintManager.ENROLL_ENROLL); return sidecar; } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java index 200b8c57ec3..778ee5cb0b9 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java @@ -293,8 +293,8 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements mSidecar = (FingerprintEnrollSidecar) getSupportFragmentManager().findFragmentByTag( FingerprintEnrollEnrolling.TAG_SIDECAR); if (mSidecar == null) { - mSidecar = new FingerprintEnrollSidecar(); - mSidecar.setEnrollReason(FingerprintManager.ENROLL_FIND_SENSOR); + mSidecar = new FingerprintEnrollSidecar(this, + FingerprintManager.ENROLL_FIND_SENSOR); getSupportFragmentManager().beginTransaction() .add(mSidecar, FingerprintEnrollEnrolling.TAG_SIDECAR) .commitAllowingStateLoss(); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java index d1e512e37b8..b3b99753cf7 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollSidecar.java @@ -16,19 +16,19 @@ package com.android.settings.biometrics.fingerprint; +import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL; + import android.app.Activity; import android.app.settings.SettingsEnums; +import android.content.Context; import android.hardware.fingerprint.FingerprintManager; +import android.os.SystemClock; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.biometrics.BiometricEnrollSidecar; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - /** * Sidecar fragment to handle the state around fingerprint enrollment. */ @@ -37,19 +37,41 @@ public class FingerprintEnrollSidecar extends BiometricEnrollSidecar { private FingerprintUpdater mFingerprintUpdater; private @FingerprintManager.EnrollReason int mEnrollReason; - private Set mHelpIgnore; + private final MessageDisplayController mMessageDisplayController; + private final boolean mMessageDisplayControllerFlag; + + /** + * Create a new FingerprintEnrollSidecar object. + * @param context associated context + * @param enrollReason reason for enrollment + */ + public FingerprintEnrollSidecar(Context context, + @FingerprintManager.EnrollReason int enrollReason) { + mEnrollReason = enrollReason; + + int helpMinimumDisplayTime = context.getResources().getInteger( + R.integer.enrollment_help_minimum_time_display); + int progressMinimumDisplayTime = context.getResources().getInteger( + R.integer.enrollment_progress_minimum_time_display); + boolean progressPriorityOverHelp = context.getResources().getBoolean( + R.bool.enrollment_progress_priority_over_help); + boolean prioritizeAcquireMessages = context.getResources().getBoolean( + R.bool.enrollment_prioritize_acquire_messages); + int collectTime = context.getResources().getInteger( + R.integer.enrollment_collect_time); + mMessageDisplayControllerFlag = context.getResources().getBoolean( + R.bool.enrollment_message_display_controller_flag); + + mMessageDisplayController = new MessageDisplayController(context.getMainThreadHandler(), + mEnrollmentCallback, SystemClock.elapsedRealtimeClock(), helpMinimumDisplayTime, + progressMinimumDisplayTime, progressPriorityOverHelp, prioritizeAcquireMessages, + collectTime); + } @Override public void onAttach(Activity activity) { super.onAttach(activity); mFingerprintUpdater = new FingerprintUpdater(activity); - final int[] ignoreAcquiredInfo = getResources().getIntArray( - R.array.fingerprint_acquired_ignore_list); - mHelpIgnore = new HashSet<>(); - for (int acquiredInfo: ignoreAcquiredInfo) { - mHelpIgnore.add(acquiredInfo); - } - mHelpIgnore = Collections.unmodifiableSet(mHelpIgnore); } @Override @@ -62,8 +84,16 @@ public class FingerprintEnrollSidecar extends BiometricEnrollSidecar { getString(R.string.fingerprint_intro_error_unknown)); return; } - mFingerprintUpdater.enroll(mToken, mEnrollmentCancel, mUserId, mEnrollmentCallback, - mEnrollReason); + + if (mEnrollReason == ENROLL_ENROLL && mMessageDisplayControllerFlag) { + //API calls need to be processed for {@link FingerprintEnrollEnrolling} + mFingerprintUpdater.enroll(mToken, mEnrollmentCancel, mUserId, + mMessageDisplayController, mEnrollReason); + } else { + //No processing required for {@link FingerprintEnrollFindSensor} + mFingerprintUpdater.enroll(mToken, mEnrollmentCancel, mUserId, mEnrollmentCallback, + mEnrollReason); + } } public void setEnrollReason(@FingerprintManager.EnrollReason int enrollReason) { @@ -80,9 +110,6 @@ public class FingerprintEnrollSidecar extends BiometricEnrollSidecar { @Override public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { - if (mHelpIgnore.contains(helpMsgId)) { - return; - } FingerprintEnrollSidecar.super.onEnrollmentHelp(helpMsgId, helpString); } diff --git a/src/com/android/settings/biometrics/fingerprint/MessageDisplayController.java b/src/com/android/settings/biometrics/fingerprint/MessageDisplayController.java new file mode 100644 index 00000000000..11f3ee39062 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint/MessageDisplayController.java @@ -0,0 +1,264 @@ +/* + * 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.biometrics.fingerprint; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Handler; + +import java.time.Clock; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; + +/** + * Processes message provided from the enrollment callback and filters them based + * on the below configurable flags. This is primarily used to reduce the rate + * at which messages come through, which in turns eliminates UI flicker. + */ +public class MessageDisplayController extends FingerprintManager.EnrollmentCallback { + + private final int mHelpMinimumDisplayTime; + private final int mProgressMinimumDisplayTime; + private final boolean mProgressPriorityOverHelp; + private final boolean mPrioritizeAcquireMessages; + private final int mCollectTime; + @NonNull + private final Deque mHelpMessageList; + @NonNull + private final Deque mProgressMessageList; + @NonNull + private final Handler mHandler; + @NonNull + private final Clock mClock; + @NonNull + private final Runnable mDisplayMessageRunnable; + + @Nullable + private ProgressMessage mLastProgressMessageDisplayed; + private boolean mMustDisplayProgress; + private boolean mWaitingForMessage; + @NonNull FingerprintManager.EnrollmentCallback mEnrollmentCallback; + + private abstract static class Message { + long mTimeStamp = 0; + abstract void display(); + } + + private class HelpMessage extends Message { + private final int mHelpMsgId; + private final CharSequence mHelpString; + + HelpMessage(int helpMsgId, CharSequence helpString) { + mHelpMsgId = helpMsgId; + mHelpString = helpString; + mTimeStamp = mClock.millis(); + } + + @Override + void display() { + mEnrollmentCallback.onEnrollmentHelp(mHelpMsgId, mHelpString); + mHandler.postDelayed(mDisplayMessageRunnable, mHelpMinimumDisplayTime); + } + } + + private class ProgressMessage extends Message { + private final int mRemaining; + + ProgressMessage(int remaining) { + mRemaining = remaining; + mTimeStamp = mClock.millis(); + } + + @Override + void display() { + mEnrollmentCallback.onEnrollmentProgress(mRemaining); + mLastProgressMessageDisplayed = this; + mHandler.postDelayed(mDisplayMessageRunnable, mProgressMinimumDisplayTime); + } + } + + /** + * Creating a MessageDisplayController object. + * @param handler main handler to run message queue + * @param enrollmentCallback callback to display messages + * @param clock real time system clock + * @param helpMinimumDisplayTime the minimum duration (in millis) that +* a help message needs to be displayed for + * @param progressMinimumDisplayTime the minimum duration (in millis) that +* a progress message needs to be displayed for + * @param progressPriorityOverHelp if true, then progress message is displayed +* when both help and progress message APIs have been called + * @param prioritizeAcquireMessages if true, then displays the help message +* which has occurred the most after the last display message + * @param collectTime the waiting time (in millis) to collect messages when it is idle + */ + public MessageDisplayController(@NonNull Handler handler, + FingerprintManager.EnrollmentCallback enrollmentCallback, + @NonNull Clock clock, int helpMinimumDisplayTime, int progressMinimumDisplayTime, + boolean progressPriorityOverHelp, boolean prioritizeAcquireMessages, + int collectTime) { + mClock = clock; + mWaitingForMessage = false; + mHelpMessageList = new ArrayDeque<>(); + mProgressMessageList = new ArrayDeque<>(); + mHandler = handler; + mEnrollmentCallback = enrollmentCallback; + + mHelpMinimumDisplayTime = helpMinimumDisplayTime; + mProgressMinimumDisplayTime = progressMinimumDisplayTime; + mProgressPriorityOverHelp = progressPriorityOverHelp; + mPrioritizeAcquireMessages = prioritizeAcquireMessages; + mCollectTime = collectTime; + + mDisplayMessageRunnable = () -> { + long timeStamp = mClock.millis(); + Message messageToDisplay = getMessageToDisplay(timeStamp); + + if (messageToDisplay != null) { + messageToDisplay.display(); + } else { + mWaitingForMessage = true; + } + }; + + mHandler.postDelayed(mDisplayMessageRunnable, 0); + } + + /** + * Adds help message to the queue to be processed later. + * + * @param helpMsgId message Id associated with the help message + * @param helpString string associated with the help message + */ + @Override + public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { + mHelpMessageList.add(new HelpMessage(helpMsgId, helpString)); + + if (mWaitingForMessage) { + mWaitingForMessage = false; + mHandler.postDelayed(mDisplayMessageRunnable, mCollectTime); + } + } + + /** + * Adds progress change message to the queue to be processed later. + * + * @param remaining remaining number of steps to complete enrollment + */ + @Override + public void onEnrollmentProgress(int remaining) { + mProgressMessageList.add(new ProgressMessage(remaining)); + + if (mWaitingForMessage) { + mWaitingForMessage = false; + mHandler.postDelayed(mDisplayMessageRunnable, mCollectTime); + } + } + + @Override + public void onEnrollmentError(int errMsgId, CharSequence errString) { + mEnrollmentCallback.onEnrollmentError(errMsgId, errString); + } + + private Message getMessageToDisplay(long timeStamp) { + ProgressMessage progressMessageToDisplay = getProgressMessageToDisplay(timeStamp); + if (mMustDisplayProgress) { + mMustDisplayProgress = false; + if (progressMessageToDisplay != null) { + return progressMessageToDisplay; + } + if (mLastProgressMessageDisplayed != null) { + return mLastProgressMessageDisplayed; + } + } + + Message helpMessageToDisplay = getHelpMessageToDisplay(timeStamp); + if (helpMessageToDisplay != null || progressMessageToDisplay != null) { + if (mProgressPriorityOverHelp && progressMessageToDisplay != null) { + return progressMessageToDisplay; + } else if (helpMessageToDisplay != null) { + if (progressMessageToDisplay != null) { + mMustDisplayProgress = true; + mLastProgressMessageDisplayed = progressMessageToDisplay; + } + return helpMessageToDisplay; + } else { + return progressMessageToDisplay; + } + } else { + return null; + } + } + + private ProgressMessage getProgressMessageToDisplay(long timeStamp) { + ProgressMessage finalProgressMessage = null; + while (mProgressMessageList != null && !mProgressMessageList.isEmpty()) { + Message message = mProgressMessageList.peekFirst(); + if (message.mTimeStamp <= timeStamp) { + ProgressMessage progressMessage = mProgressMessageList.pollFirst(); + if (mLastProgressMessageDisplayed != null + && mLastProgressMessageDisplayed.mRemaining == progressMessage.mRemaining) { + continue; + } + finalProgressMessage = progressMessage; + } else { + break; + } + } + + return finalProgressMessage; + } + + private HelpMessage getHelpMessageToDisplay(long timeStamp) { + HashMap messageCount = new HashMap<>(); + HelpMessage finalHelpMessage = null; + + while (mHelpMessageList != null && !mHelpMessageList.isEmpty()) { + Message message = mHelpMessageList.peekFirst(); + if (message.mTimeStamp <= timeStamp) { + finalHelpMessage = mHelpMessageList.pollFirst(); + CharSequence errString = finalHelpMessage.mHelpString; + messageCount.put(errString, messageCount.getOrDefault(errString, 0) + 1); + } else { + break; + } + } + if (mPrioritizeAcquireMessages) { + finalHelpMessage = prioritizeHelpMessageByCount(messageCount); + } + + return finalHelpMessage; + } + + private HelpMessage prioritizeHelpMessageByCount(HashMap messageCount) { + int maxCount = 0; + CharSequence maxCountMessage = null; + + for (CharSequence key : + messageCount.keySet()) { + if (maxCount < messageCount.get(key)) { + maxCountMessage = key; + maxCount = messageCount.get(key); + } + } + + return maxCountMessage != null ? new HelpMessage(0 /* errMsgId */, + maxCountMessage) : null; + } +} 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 b049b7bfb14..b8992068a8e 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java @@ -18,7 +18,6 @@ package com.android.settings.biometrics.fingerprint; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL; -import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UNKNOWN; import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.KEY_STATE_PREVIOUS_ROTATION; import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_NO_ANIMATION; @@ -36,8 +35,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.mock; - import android.content.Context; import android.content.res.ColorStateList; @@ -53,9 +50,6 @@ import android.os.CancellationSignal; import android.os.Vibrator; import android.view.Display; import android.view.Surface; -import android.widget.TextView; - -import androidx.annotation.Nullable; import com.android.settings.R; import com.android.settings.testutils.FakeFeatureFactory; @@ -103,42 +97,6 @@ public class FingerprintEnrollEnrollingTest { FakeFeatureFactory.setupForTest(); } - @Test - public void fingerprintEnrollHelp_shouldShowHelpText() { - initializeActivityFor(TYPE_UNKNOWN); - TestFingerprintEnrollSidecar sidecar = new TestFingerprintEnrollSidecar(); - Resources resources = mock(Resources.class); - doReturn(resources).when(mContext).getResources(); - when(resources.getIntArray(R.array.fingerprint_acquired_ignore_list)) - .thenReturn(new int[]{3}); - - sidecar.setListener(mActivity); - sidecar.onAttach(mActivity); - sidecar.mEnrollmentCallback.onEnrollmentHelp(5, - "Help message should be displayed."); - - TextView errorText = mActivity.findViewById(R.id.error_text); - assertThat(errorText.getText()).isEqualTo("Help message should be displayed."); - } - - @Test - public void fingerprintEnrollHelp_shouldNotShowHelpText() { - initializeActivityFor(TYPE_UNKNOWN); - TestFingerprintEnrollSidecar sidecar = new TestFingerprintEnrollSidecar(); - Resources resources = mock(Resources.class); - doReturn(resources).when(mContext).getResources(); - when(resources.getIntArray(R.array.fingerprint_acquired_ignore_list)) - .thenReturn(new int[]{3}); - - sidecar.setListener(mActivity); - sidecar.onAttach(mActivity); - sidecar.mEnrollmentCallback.onEnrollmentHelp(3, - "Help message should not be displayed."); - - TextView errorText = mActivity.findViewById(R.id.error_text); - assertThat(errorText.getText()).isEqualTo(""); - } - @Test public void fingerprintUdfpsEnrollSuccessProgress_shouldNotVibrate() { initializeActivityFor(TYPE_UDFPS_OPTICAL); @@ -346,12 +304,4 @@ public class FingerprintEnrollEnrollingTest { return callbackCaptor.getValue(); } - - private class TestFingerprintEnrollSidecar extends FingerprintEnrollSidecar { - @Nullable - @Override - public Context getContext() { - return mContext; - } - } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/MessageDisplayControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/MessageDisplayControllerTest.java new file mode 100644 index 00000000000..0fa0918d90a --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/MessageDisplayControllerTest.java @@ -0,0 +1,219 @@ +/* + * 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.biometrics.fingerprint; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.hardware.fingerprint.FingerprintManager; +import android.os.Handler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowLooper; + +import java.time.Clock; +import java.util.concurrent.TimeUnit; + +@RunWith(RobolectricTestRunner.class) +public class MessageDisplayControllerTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private static final long START_TIME = 0L; + private static final int HELP_ID = 0; + private static final String HELP_MESSAGE = "Default Help Message"; + private static final int REMAINING = 5; + private static final int HELP_MINIMUM_DISPLAY_TIME = 300; + private static final int PROGRESS_MINIMUM_DISPLAY_TIME = 250; + private static final int COLLECT_TIME = 100; + + private MessageDisplayController mMessageDisplayController; + @Mock + private FingerprintManager.EnrollmentCallback mEnrollmentCallback; + @Mock + private Clock mClock; + + @Before + public void setup() { + mMessageDisplayController = new MessageDisplayController(new Handler(), mEnrollmentCallback, + mClock, + HELP_MINIMUM_DISPLAY_TIME, /* progressPriorityOverHelp */ + PROGRESS_MINIMUM_DISPLAY_TIME, /* prioritizeAcquireMessages */ + false, false, COLLECT_TIME); + } + + private void setMessageDisplayController(boolean progressPriorityOverHelp, + boolean prioritizeAcquireMessages) { + mMessageDisplayController = new MessageDisplayController(new Handler(), mEnrollmentCallback, + mClock, HELP_MINIMUM_DISPLAY_TIME, PROGRESS_MINIMUM_DISPLAY_TIME, + progressPriorityOverHelp, prioritizeAcquireMessages, COLLECT_TIME); + } + + @Test + public void showsHelpMessageAfterCollectTime() { + when(mClock.millis()).thenReturn(START_TIME); + + mMessageDisplayController.onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + when(mClock.millis()).thenReturn((long) COLLECT_TIME); + ShadowLooper.idleMainLooper(COLLECT_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + verifyNoMoreInteractions(mEnrollmentCallback); + } + + @Test + public void showsProgressMessageAfterCollectTime() { + when(mClock.millis()).thenReturn(START_TIME); + + mMessageDisplayController.onEnrollmentProgress(REMAINING); + when(mClock.millis()).thenReturn((long) COLLECT_TIME); + ShadowLooper.idleMainLooper(COLLECT_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentProgress(REMAINING); + verifyNoMoreInteractions(mEnrollmentCallback); + } + + @Test + public void helpDisplayedForMinimumDisplayTime() { + when(mClock.millis()).thenReturn(START_TIME); + + mMessageDisplayController.onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + when(mClock.millis()).thenReturn((long) COLLECT_TIME); + ShadowLooper.idleMainLooper(COLLECT_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + + mMessageDisplayController.onEnrollmentProgress(REMAINING); + + verifyNoMoreInteractions(mEnrollmentCallback); + + when(mClock.millis()).thenReturn((long) (HELP_MINIMUM_DISPLAY_TIME + COLLECT_TIME)); + ShadowLooper.idleMainLooper(HELP_MINIMUM_DISPLAY_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentProgress(REMAINING); + } + + @Test + public void progressDisplayedForMinimumDisplayTime() { + when(mClock.millis()).thenReturn(START_TIME); + + mMessageDisplayController.onEnrollmentProgress(REMAINING); + when(mClock.millis()).thenReturn((long) COLLECT_TIME); + ShadowLooper.idleMainLooper(COLLECT_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentProgress(REMAINING); + + mMessageDisplayController.onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + + verifyNoMoreInteractions(mEnrollmentCallback); + + when(mClock.millis()).thenReturn((long) (COLLECT_TIME + PROGRESS_MINIMUM_DISPLAY_TIME)); + ShadowLooper.idleMainLooper(PROGRESS_MINIMUM_DISPLAY_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + } + + @Test + public void prioritizeHelpMessage_thenShowProgress() { + when(mClock.millis()).thenReturn(START_TIME); + + mMessageDisplayController.onEnrollmentProgress(REMAINING); + mMessageDisplayController.onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + when(mClock.millis()).thenReturn((long) COLLECT_TIME); + ShadowLooper.idleMainLooper(COLLECT_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + verifyNoMoreInteractions(mEnrollmentCallback); + + mMessageDisplayController.onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + when(mClock.millis()).thenReturn((long) (COLLECT_TIME + HELP_MINIMUM_DISPLAY_TIME)); + ShadowLooper.idleMainLooper(HELP_MINIMUM_DISPLAY_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentProgress(REMAINING); + } + + @Test + public void prioritizeProgressOverHelp() { + when(mClock.millis()).thenReturn(START_TIME); + setMessageDisplayController(true /* progressPriorityOverHelp */, + false /* prioritizeAcquireMessages */); + + mMessageDisplayController.onEnrollmentProgress(REMAINING); + mMessageDisplayController.onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + when(mClock.millis()).thenReturn((long) COLLECT_TIME); + ShadowLooper.idleMainLooper(COLLECT_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentProgress(REMAINING); + verifyNoMoreInteractions(mEnrollmentCallback); + } + + @Test + public void prioritizeHelpMessageByCount() { + String newHelpMessage = "New message"; + when(mClock.millis()).thenReturn(START_TIME); + setMessageDisplayController(false /* progressPriorityOverHelp */, + true /* prioritizeAcquireMessages */); + + mMessageDisplayController.onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + mMessageDisplayController.onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + mMessageDisplayController.onEnrollmentHelp(HELP_ID, newHelpMessage); + when(mClock.millis()).thenReturn((long) COLLECT_TIME); + ShadowLooper.idleMainLooper(COLLECT_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + verifyNoMoreInteractions(mEnrollmentCallback); + } + + @Test + public void ignoreSameProgress() { + int progressChange = REMAINING - 1; + when(mClock.millis()).thenReturn(START_TIME); + setMessageDisplayController(true /* progressPriorityOverHelp */, + false /* prioritizeAcquireMessages */); + + mMessageDisplayController.onEnrollmentProgress(REMAINING); + mMessageDisplayController.onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + when(mClock.millis()).thenReturn((long) COLLECT_TIME); + ShadowLooper.idleMainLooper(COLLECT_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentProgress(REMAINING); + verifyNoMoreInteractions(mEnrollmentCallback); + + mMessageDisplayController.onEnrollmentProgress(REMAINING); + mMessageDisplayController.onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + when(mClock.millis()).thenReturn((long) (COLLECT_TIME + PROGRESS_MINIMUM_DISPLAY_TIME)); + ShadowLooper.idleMainLooper(PROGRESS_MINIMUM_DISPLAY_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + + mMessageDisplayController.onEnrollmentProgress(progressChange); + mMessageDisplayController.onEnrollmentHelp(HELP_ID, HELP_MESSAGE); + when(mClock.millis()).thenReturn((long) (COLLECT_TIME + PROGRESS_MINIMUM_DISPLAY_TIME + + HELP_MINIMUM_DISPLAY_TIME)); + ShadowLooper.idleMainLooper(HELP_MINIMUM_DISPLAY_TIME, TimeUnit.MILLISECONDS); + + verify(mEnrollmentCallback).onEnrollmentProgress(progressChange); + } +}