diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java index 10bc9e3b430..756f82605d7 100644 --- a/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java @@ -32,6 +32,8 @@ import android.graphics.drawable.LayerDrawable; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; import android.os.UserHandle; +import android.os.VibrationEffect; +import android.os.Vibrator; import android.text.TextUtils; import android.view.MotionEvent; import android.view.View; @@ -74,6 +76,9 @@ public class FingerprintEnrollEnrolling extends FingerprintEnrollBase */ private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3; + private static final VibrationEffect VIBRATE_EFFECT_ERROR = + VibrationEffect.createWaveform(new long[] {0, 5, 55, 60}, -1); + private ProgressBar mProgressBar; private ObjectAnimator mProgressAnim; private TextView mStartMessage; @@ -90,6 +95,7 @@ public class FingerprintEnrollEnrolling extends FingerprintEnrollBase private int mIndicatorBackgroundRestingColor; private int mIndicatorBackgroundActivatedColor; private boolean mRestoring; + private Vibrator mVibrator; @Override protected void onCreate(Bundle savedInstanceState) { @@ -100,6 +106,7 @@ public class FingerprintEnrollEnrolling extends FingerprintEnrollBase mRepeatMessage = (TextView) findViewById(R.id.repeat_message); mErrorText = (TextView) findViewById(R.id.error_text); mProgressBar = (ProgressBar) findViewById(R.id.fingerprint_progress_bar); + mVibrator = getSystemService(Vibrator.class); Button skipButton = findViewById(R.id.skip_button); skipButton.setOnClickListener(this); @@ -368,6 +375,9 @@ public class FingerprintEnrollEnrolling extends FingerprintEnrollBase mErrorText.setAlpha(1f); mErrorText.setTranslationY(0f); } + if (isResumed()) { + mVibrator.vibrate(VIBRATE_EFFECT_ERROR); + } } private void clearError() { @@ -378,12 +388,7 @@ public class FingerprintEnrollEnrolling extends FingerprintEnrollBase R.dimen.fingerprint_error_text_disappear_distance)) .setDuration(100) .setInterpolator(mFastOutLinearInInterpolator) - .withEndAction(new Runnable() { - @Override - public void run() { - mErrorText.setVisibility(View.INVISIBLE); - } - }) + .withEndAction(() -> mErrorText.setVisibility(View.INVISIBLE)) .start(); } } diff --git a/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollEnrollingTest.java b/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollEnrollingTest.java new file mode 100644 index 00000000000..ace535ef68b --- /dev/null +++ b/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollEnrollingTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2017 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.fingerprint; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.verify; +import static org.robolectric.RuntimeEnvironment.application; + +import android.content.Intent; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback; +import android.media.AudioAttributes; +import android.os.CancellationSignal; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.password.ChooseLockSettingsHelper; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowUtils; +import com.android.settings.testutils.shadow.ShadowVibrator; +import com.android.settings.wrapper.FingerprintManagerWrapper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; + +import java.util.concurrent.TimeUnit; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config( + manifest = TestConfig.MANIFEST_PATH, + sdk = Config.NEWEST_SDK, + shadows = { + ShadowUtils.class, + ShadowVibrator.class + }) +public class FingerprintEnrollEnrollingTest { + + @Mock + private FingerprintManagerWrapper mFingerprintManager; + + private FingerprintEnrollEnrolling mActivity; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowUtils.setFingerprintManager(mFingerprintManager); + ShadowVibrator.addToServiceMap(); + + mActivity = Robolectric.buildActivity( + FingerprintEnrollEnrolling.class, + new Intent() + // Set the challenge token so the confirm screen will not be shown + .putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0])) + .setup().get(); + } + + @After + public void tearDown() { + ShadowUtils.reset(); + ShadowVibrator.reset(); + } + + @Test + public void fingerprintEnrollHelp_shouldShowHelpTextAndVibrate() { + EnrollmentCallback enrollmentCallback = verifyAndCaptureEnrollmentCallback(); + + enrollmentCallback.onEnrollmentProgress(123); + enrollmentCallback.onEnrollmentHelp( + FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS, + "test enrollment help"); + + TextView errorText = mActivity.findViewById(R.id.error_text); + assertThat(errorText.getText()).isEqualTo("test enrollment help"); + + Robolectric.getForegroundThreadScheduler().advanceBy(2, TimeUnit.MILLISECONDS); + + + ShadowVibrator shadowVibrator = + Shadow.extract(application.getSystemService(Vibrator.class)); + verify(shadowVibrator.delegate).vibrate( + anyInt(), + nullable(String.class), + any(VibrationEffect.class), + nullable(AudioAttributes.class)); + } + + private EnrollmentCallback verifyAndCaptureEnrollmentCallback() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(EnrollmentCallback.class); + verify(mFingerprintManager).enroll( + any(byte[].class), + any(CancellationSignal.class), + anyInt(), + anyInt(), + callbackCaptor.capture()); + + return callbackCaptor.getValue(); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowVibrator.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowVibrator.java new file mode 100644 index 00000000000..9046720930e --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowVibrator.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.testutils.shadow; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.media.AudioAttributes; +import android.os.SystemVibrator; +import android.os.VibrationEffect; +import android.os.Vibrator; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.fakes.RoboVibrator; +import org.robolectric.shadows.ShadowContextImpl; +import org.robolectric.util.ReflectionHelpers; + +import java.util.Map; + +@Implements(SystemVibrator.class) +public class ShadowVibrator { + + private static Map getSystemServiceMap() { + return ReflectionHelpers.getStaticField(ShadowContextImpl.class, "SYSTEM_SERVICE_MAP"); + } + + public static void addToServiceMap() { + getSystemServiceMap().put(Context.VIBRATOR_SERVICE, SystemVibrator.class.getName()); + } + + public static void reset() { + getSystemServiceMap().put(Context.VIBRATOR_SERVICE, RoboVibrator.class.getName()); + } + + public final Vibrator delegate = mock(Vibrator.class); + + @Implementation + public void vibrate(int uid, String opPkg, VibrationEffect vibe, AudioAttributes attributes) { + delegate.vibrate(uid, opPkg, vibe, attributes); + } +}