diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index f6f980aa28c..0142ea2ff99 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -103,6 +103,8 @@ import com.android.internal.app.UnlaunchableAppActivity; import com.android.internal.util.ArrayUtils; import com.android.internal.util.UserIcons; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.password.FingerprintManagerWrapper; +import com.android.settings.password.IFingerprintManager; import java.io.IOException; import java.io.InputStream; @@ -1197,6 +1199,15 @@ public final class Utils extends com.android.settingslib.Utils { } } + public static IFingerprintManager getFingerprintManagerWrapperOrNull(Context context) { + FingerprintManager fingerprintManager = getFingerprintManagerOrNull(context); + if (fingerprintManager != null) { + return new FingerprintManagerWrapper(fingerprintManager); + } else { + return null; + } + } + /** * Launches an intent which may optionally have a user id defined. * @param fragment Fragment to use to launch the activity. diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java index 510e95380d5..107838ce587 100644 --- a/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java @@ -82,9 +82,7 @@ public class FingerprintEnrollFindSensor extends FingerprintEnrollBase { @Override public void onEnrollmentProgressChange(int steps, int remaining) { mNextClicked = true; - if (!mSidecar.cancelEnrollment()) { - proceedToEnrolling(); - } + proceedToEnrolling(true /* cancelEnrollment */); } @Override @@ -95,7 +93,7 @@ public class FingerprintEnrollFindSensor extends FingerprintEnrollBase { public void onEnrollmentError(int errMsgId, CharSequence errString) { if (mNextClicked && errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { mNextClicked = false; - proceedToEnrolling(); + proceedToEnrolling(false /* cancelEnrollment */); } } }); @@ -123,15 +121,23 @@ public class FingerprintEnrollFindSensor extends FingerprintEnrollBase { @Override protected void onNextButtonClick() { mNextClicked = true; - if (mSidecar == null || (mSidecar != null && !mSidecar.cancelEnrollment())) { - proceedToEnrolling(); - } + proceedToEnrolling(true /* cancelEnrollment */); } - private void proceedToEnrolling() { - getFragmentManager().beginTransaction().remove(mSidecar).commit(); - mSidecar = null; - startActivityForResult(getEnrollingIntent(), ENROLLING); + private void proceedToEnrolling(boolean cancelEnrollment) { + if (mSidecar != null) { + if (cancelEnrollment) { + if (mSidecar.cancelEnrollment()) { + // Enrollment cancel requested. When the cancellation is successful, + // onEnrollmentError will be called with FINGERPRINT_ERROR_CANCELED, calling + // this again. + return; + } + } + getFragmentManager().beginTransaction().remove(mSidecar).commit(); + mSidecar = null; + startActivityForResult(getEnrollingIntent(), ENROLLING); + } } @Override diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollSidecar.java b/src/com/android/settings/fingerprint/FingerprintEnrollSidecar.java index e0c5d654f45..7fc7a040021 100644 --- a/src/com/android/settings/fingerprint/FingerprintEnrollSidecar.java +++ b/src/com/android/settings/fingerprint/FingerprintEnrollSidecar.java @@ -28,12 +28,13 @@ import android.os.UserHandle; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.ChooseLockSettingsHelper; import com.android.settings.Utils; -import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.InstrumentedFragment; +import com.android.settings.password.IFingerprintManager; /** * Sidecar fragment to handle the state around fingerprint enrollment. */ -public class FingerprintEnrollSidecar extends InstrumentedPreferenceFragment { +public class FingerprintEnrollSidecar extends InstrumentedFragment { private int mEnrollmentSteps = -1; private int mEnrollmentRemaining = 0; @@ -44,7 +45,7 @@ public class FingerprintEnrollSidecar extends InstrumentedPreferenceFragment { private byte[] mToken; private boolean mDone; private int mUserId; - private FingerprintManager mFingerprintManager; + private IFingerprintManager mFingerprintManager; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -55,7 +56,7 @@ public class FingerprintEnrollSidecar extends InstrumentedPreferenceFragment { @Override public void onAttach(Activity activity) { super.onAttach(activity); - mFingerprintManager = Utils.getFingerprintManagerOrNull(activity); + mFingerprintManager = Utils.getFingerprintManagerWrapperOrNull(activity); mToken = activity.getIntent().getByteArrayExtra( ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); mUserId = activity.getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL); diff --git a/src/com/android/settings/password/FingerprintManagerWrapper.java b/src/com/android/settings/password/FingerprintManagerWrapper.java index b00f7868e61..51b31afc9e6 100644 --- a/src/com/android/settings/password/FingerprintManagerWrapper.java +++ b/src/com/android/settings/password/FingerprintManagerWrapper.java @@ -18,6 +18,8 @@ package com.android.settings.password; import android.annotation.NonNull; import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback; +import android.os.CancellationSignal; import com.android.internal.util.Preconditions; @@ -33,15 +35,33 @@ public class FingerprintManagerWrapper implements IFingerprintManager { mFingerprintManager = fingerprintManager; } + @Override public boolean isHardwareDetected() { return mFingerprintManager.isHardwareDetected(); } + @Override public boolean hasEnrolledFingerprints(int userId) { return mFingerprintManager.hasEnrolledFingerprints(userId); } + @Override public long preEnroll() { return mFingerprintManager.preEnroll(); } + + @Override + public void setActiveUser(int userId) { + mFingerprintManager.setActiveUser(userId); + } + + @Override + public void enroll( + byte[] token, + CancellationSignal cancel, + int flags, + int userId, + EnrollmentCallback callback) { + mFingerprintManager.enroll(token, cancel, flags, userId, callback); + } } diff --git a/src/com/android/settings/password/IFingerprintManager.java b/src/com/android/settings/password/IFingerprintManager.java index 15a92425888..94021460bfc 100644 --- a/src/com/android/settings/password/IFingerprintManager.java +++ b/src/com/android/settings/password/IFingerprintManager.java @@ -16,6 +16,9 @@ package com.android.settings.password; +import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback; +import android.os.CancellationSignal; + /** * This is the workaround to allow us test {@link SetNewPasswordController} which uses a new hidden * API {@link android.hardware.fingerprint.FingerprintManager#hasEnrolledFingerprints(int)} that @@ -28,4 +31,9 @@ public interface IFingerprintManager { boolean hasEnrolledFingerprints(int userId); long preEnroll(); + + void setActiveUser(int userId); + + void enroll(byte [] token, CancellationSignal cancel, int flags, + int userId, EnrollmentCallback callback); } diff --git a/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollFindSensorTest.java b/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollFindSensorTest.java new file mode 100644 index 00000000000..646774d8363 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollFindSensorTest.java @@ -0,0 +1,121 @@ +/* + * 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.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.verify; +import static org.robolectric.RuntimeEnvironment.application; + +import android.content.ComponentName; +import android.content.Intent; +import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback; +import android.os.CancellationSignal; +import android.widget.Button; + +import com.android.settings.ChooseLockSettingsHelper; +import com.android.settings.R; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.password.IFingerprintManager; +import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor; +import com.android.settings.testutils.shadow.ShadowEventLogWriter; +import com.android.settings.testutils.shadow.ShadowUtils; + +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.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowActivity; +import org.robolectric.shadows.ShadowActivity.IntentForResult; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config( + manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = { + SettingsShadowResources.class, + SettingsShadowResources.SettingsShadowTheme.class, + ShadowDynamicIndexableContentMonitor.class, + ShadowEventLogWriter.class, + ShadowUtils.class + }) +public class FingerprintEnrollFindSensorTest { + + @Mock + private IFingerprintManager mFingerprintManager; + + private FingerprintEnrollFindSensor mActivity; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowUtils.setFingerprintManager(mFingerprintManager); + + RuntimeEnvironment.getAppResourceLoader().getResourceIndex(); + + mActivity = Robolectric.buildActivity( + FingerprintEnrollFindSensor.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(); + } + + @Test + public void clickNextAndFingerprint_shouldNotCrash() { + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(EnrollmentCallback.class); + verify(mFingerprintManager).enroll( + any(byte[].class), + any(CancellationSignal.class), + anyInt(), + anyInt(), + callbackCaptor.capture()); + + Button nextButton = mActivity.findViewById(R.id.next_button); + nextButton.performClick(); + callbackCaptor.getValue().onEnrollmentProgress(123); + nextButton.performClick(); + + ShadowActivity shadowActivity = Shadows.shadowOf(mActivity); + IntentForResult startedActivity = + shadowActivity.getNextStartedActivityForResult(); + assertThat(startedActivity).named("Next activity 1").isNotNull(); + assertThat(startedActivity.intent.getComponent()) + .isEqualTo(new ComponentName(application, FingerprintEnrollEnrolling.class)); + + // Should only start one next activity + assertThat(shadowActivity.getNextStartedActivityForResult()).named("Next activity 2") + .isNull(); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java b/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java index ba828350588..43fb6d9cf4a 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java @@ -139,10 +139,14 @@ public class SettingsShadowResources extends ShadowResources { // Replace all private string references with a placeholder. if (set != null) { for (int i = 0; i < set.getAttributeCount(); ++i) { - if (set.getAttributeValue(i).startsWith("@*android:string")) { - Node node = ReflectionHelpers.callInstanceMethod( - XmlResourceParserImpl.class, set, "getAttributeAt", - ReflectionHelpers.ClassParameter.from(int.class, i)); + String attributeValue = set.getAttributeValue(i); + Node node = ReflectionHelpers.callInstanceMethod( + XmlResourceParserImpl.class, set, "getAttributeAt", + ReflectionHelpers.ClassParameter.from(int.class, i)); + if (attributeValue.contains("attr/fingerprint_layout_theme")) { + // Workaround for https://github.com/robolectric/robolectric/issues/2641 + node.setNodeValue("@style/FingerprintLayoutTheme"); + } else if (attributeValue.startsWith("@*android:string")) { node.setNodeValue("PLACEHOLDER"); } } @@ -156,7 +160,6 @@ public class SettingsShadowResources extends ShadowResources { ReflectionHelpers.getField(assetManager, "appliedStyles"); for (Long idx : appliedStylesList.keySet()) { List appliedStyles = appliedStylesList.get(idx); - int i = 1; for (Object appliedStyle : appliedStyles) { StyleResolver styleResolver = ReflectionHelpers.getField(appliedStyle, "style"); List styleDatas = diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java index 81cc6070377..eb8236200bc 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java @@ -19,6 +19,7 @@ package com.android.settings.testutils.shadow; import android.content.Context; import com.android.settings.Utils; +import com.android.settings.password.IFingerprintManager; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -26,8 +27,23 @@ import org.robolectric.annotation.Implements; @Implements(Utils.class) public class ShadowUtils { + private static IFingerprintManager sFingerprintManager = null; + @Implementation public static int enforceSameOwner(Context context, int userId) { return userId; } + + @Implementation + public static IFingerprintManager getFingerprintManagerWrapperOrNull(Context context) { + return sFingerprintManager; + } + + public static void setFingerprintManager(IFingerprintManager fingerprintManager) { + sFingerprintManager = fingerprintManager; + } + + public static void reset() { + sFingerprintManager = null; + } }