4/n: Add basic enrollment for Face

Bug: 110589286

Test: fingerprint enrolling still works
Test: enrollment flow with and without a pin set up still works properly
Test: enrollment continues when configuration changes, stops otherwise

Change-Id: I39f76c7f1a16e9533cef573f87cf4b81cb20cb18
This commit is contained in:
Kevin Chyn
2018-06-28 17:59:32 -07:00
parent 2d41659c32
commit ea65b51a36
22 changed files with 1046 additions and 341 deletions

View File

@@ -1570,6 +1570,8 @@
android:theme="@style/GlifTheme.Light"/> android:theme="@style/GlifTheme.Light"/>
<activity android:name=".biometrics.face.FaceEnrollIntroduction" android:exported="false" /> <activity android:name=".biometrics.face.FaceEnrollIntroduction" android:exported="false" />
<activity android:name=".biometrics.face.FaceEnrollEnrolling" android:exported="false" />
<activity android:name=".biometrics.face.FaceEnrollFinish" android:exported="false" />
<activity android:name=".biometrics.fingerprint.FingerprintSettings" android:exported="false"/> <activity android:name=".biometrics.fingerprint.FingerprintSettings" android:exported="false"/>
<activity android:name=".biometrics.fingerprint.FingerprintEnrollFindSensor" android:exported="false"/> <activity android:name=".biometrics.fingerprint.FingerprintEnrollFindSensor" android:exported="false"/>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2018 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.
-->
<com.android.setupwizardlib.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_wizard_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?attr/face_layout_theme"
app:suwFooter="@layout/face_enroll_enrolling_footer">
<LinearLayout
style="@style/SuwContentFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipToPadding="false"
android:clipChildren="false">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<com.android.setupwizardlib.view.FillContentLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
<!-- TODO: replace this with actual content-->
<ImageView
style="@style/SuwContentIllustration"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@null"
android:src="@drawable/face_enroll_introduction" />
</com.android.setupwizardlib.view.FillContentLayout>
<TextView
style="@style/TextAppearance.FaceErrorText"
android:id="@+id/error_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:accessibilityLiveRegion="polite"
android:gravity="center"
android:visibility="invisible"/>
</LinearLayout>
</LinearLayout>
</com.android.setupwizardlib.GlifLayout>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2018 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
-->
<!-- TODO: Use aapt:attr when it is fixed (b/36809755) -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/SuwGlifButtonBar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
style="@style/SuwGlifButton.Secondary"
android:id="@+id/skip_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/security_settings_face_enroll_enrolling_skip" />
</LinearLayout>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2018 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
-->
<com.android.setupwizardlib.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_wizard_layout"
style="?attr/face_layout_theme"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:suwFooter="@layout/face_enroll_finish_footer">
<LinearLayout
style="@style/SuwContentFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipToPadding="false"
android:clipChildren="false">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<com.android.setupwizardlib.view.FillContentLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
<ImageView
style="@style/SuwContentIllustration"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@null"
android:src="@drawable/face_enroll_introduction" />
</com.android.setupwizardlib.view.FillContentLayout>
</LinearLayout>
</LinearLayout>
</com.android.setupwizardlib.GlifLayout>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2018 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
-->
<!-- TODO: Use aapt:attr when it is fixed (b/36809755) -->
<com.android.setupwizardlib.view.ButtonBarLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/SuwGlifButtonBar.Stackable"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
style="@style/SuwGlifButton.Primary"
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/security_settings_face_enroll_done" />
</com.android.setupwizardlib.view.ButtonBarLayout>

View File

@@ -897,12 +897,28 @@
<string name="security_settings_face_enroll_introduction_message_unlock_disabled">Use you</string> <string name="security_settings_face_enroll_introduction_message_unlock_disabled">Use you</string>
<!-- Introduction detail message shwon in face enrollment screen in setup wizard. [CHAR LIMIT=NONE] --> <!-- Introduction detail message shwon in face enrollment screen in setup wizard. [CHAR LIMIT=NONE] -->
<string name="security_settings_face_enroll_introduction_message_setup">Use your face to unlock your phone, authorize purchases, or sign in to apps</string> <string name="security_settings_face_enroll_introduction_message_setup">Use your face to unlock your phone, authorize purchases, or sign in to apps</string>
<!-- Title shown in face enrollment dialog [CHAR LIMIT=40] -->
<string name="security_settings_face_enroll_repeat_title">Center your face in the circle</string>
<!-- Button text to skip enrollment of face [CHAR LIMIT=40] -->
<string name="security_settings_face_enroll_enrolling_skip">Do it later</string>
<!-- Text shown when "Add face" button is disabled --> <!-- Text shown when "Add face" button is disabled -->
<string name="face_add_max">You can add up to <xliff:g id="count" example="5">%d</xliff:g> fingerprints</string> <string name="face_add_max">You can add up to <xliff:g id="count" example="5">%d</xliff:g> fingerprints</string>
<!-- Text shown when users has enrolled a maximum number of faces [CHAR LIMIT=NONE] --> <!-- Text shown when users has enrolled a maximum number of faces [CHAR LIMIT=NONE] -->
<string name="face_intro_error_max">You\u2019ve added the maximum number of faces</string> <string name="face_intro_error_max">You\u2019ve added the maximum number of faces</string>
<!-- Text shown when an unknown error caused the device to be unable to add faces [CHAR LIMIT=NONE] --> <!-- Text shown when an unknown error caused the device to be unable to add faces [CHAR LIMIT=NONE] -->
<string name="face_intro_error_unknown">Can\u2019t add more faces</string> <string name="face_intro_error_unknown">Can\u2019t add more faces</string>
<!-- Dialog message for dialog which shows when face cannot be enrolled. [CHAR LIMIT=45] -->
<string name="security_settings_face_enroll_error_dialog_title">Enrollment was not completed</string>
<!-- Button text shown in face dialog shown when an error occurs during enrollment [CHAR LIMIT=22] -->
<string name="security_settings_face_enroll_dialog_ok">OK</string>
<!-- Dialog message for dialog which shows when face cannot be enrolled due to being idle too long. -->
<string name="security_settings_face_enroll_error_timeout_dialog_message">Face enrollment time limit reached. Try again.</string>
<!-- Dialog message for dialog which shows when face cannot be enrolled due to an internal error or face can't be read. -->
<string name="security_settings_face_enroll_error_generic_dialog_message">Face enrollment didn\'t work.</string>
<!-- Message shown in face enrollment dialog once enrollment is completed -->
<string name="security_settings_face_enroll_finish_title">All set. Looking good.</string>
<!-- Button text to exit face wizard after everything is done [CHAR LIMIT=15] -->
<string name="security_settings_face_enroll_done">Done</string>
<!-- Fingerprint enrollment and settings --><skip /> <!-- Fingerprint enrollment and settings --><skip />
<!-- Title shown for menu item that launches fingerprint settings or enrollment [CHAR LIMIT=22] --> <!-- Title shown for menu item that launches fingerprint settings or enrollment [CHAR LIMIT=22] -->

View File

@@ -364,6 +364,11 @@
<item name="android:icon">@drawable/ic_fingerprint_header</item> <item name="android:icon">@drawable/ic_fingerprint_header</item>
</style> </style>
<style name="TextAppearance.FaceErrorText"
parent="android:TextAppearance.Material.Body1">
<item name="android:textColor">?android:attr/colorError</item>
</style>
<style name="FaceLayoutTheme"> <style name="FaceLayoutTheme">
<item name="android:icon">@drawable/ic_face_header</item> <item name="android:icon">@drawable/ic_face_header</item>
</style> </style>

View File

@@ -30,29 +30,33 @@ import android.widget.TextView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SetupWizardUtils; import com.android.settings.SetupWizardUtils;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling; import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
import com.android.settings.biometrics.fingerprint.FingerprintSettings;
import com.android.settings.core.InstrumentedActivity; import com.android.settings.core.InstrumentedActivity;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.setupwizardlib.GlifLayout; import com.android.setupwizardlib.GlifLayout;
/** /**
* Base activity for all fingerprint enrollment steps. * Base activity for all biometric enrollment steps.
*/ */
public abstract class BiometricEnrollBase extends InstrumentedActivity public abstract class BiometricEnrollBase extends InstrumentedActivity
implements View.OnClickListener { implements View.OnClickListener {
public static final int RESULT_FINISHED = BiometricSettings.RESULT_FINISHED; public static final int RESULT_FINISHED = BiometricSettings.RESULT_FINISHED;
public static final int RESULT_SKIP = BiometricSettings.RESULT_SKIP; public static final int RESULT_SKIP = BiometricSettings.RESULT_SKIP;
public static final int RESULT_TIMEOUT = BiometricSettings.RESULT_TIMEOUT; public static final int RESULT_TIMEOUT = BiometricSettings.RESULT_TIMEOUT;
public static final String EXTRA_KEY_LAUNCHED_CONFIRM = "launched_confirm_lock";
public static final int CONFIRM_REQUEST = 1;
public static final int ENROLLING = 2;
protected boolean mLaunchedConfirmLock;
protected byte[] mToken; protected byte[] mToken;
protected int mUserId; protected int mUserId;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mToken = getIntent().getByteArrayExtra( mToken = getIntent().getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
if (savedInstanceState != null && mToken == null) { if (savedInstanceState != null && mToken == null) {
mLaunchedConfirmLock = savedInstanceState.getBoolean(EXTRA_KEY_LAUNCHED_CONFIRM);
mToken = savedInstanceState.getByteArray( mToken = savedInstanceState.getByteArray(
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
} }
@@ -68,6 +72,7 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putBoolean(EXTRA_KEY_LAUNCHED_CONFIRM, mLaunchedConfirmLock);
outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
} }
@@ -77,6 +82,10 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity
initViews(); initViews();
} }
protected boolean shouldLaunchConfirmLock() {
return mToken == null && !mLaunchedConfirmLock;
}
protected void initViews() { protected void initViews() {
getWindow().setStatusBarColor(Color.TRANSPARENT); getWindow().setStatusBarColor(Color.TRANSPARENT);
Button nextButton = getNextButton(); Button nextButton = getNextButton();
@@ -129,4 +138,25 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity
} }
return intent; return intent;
} }
protected void launchConfirmLock(int titleResId, long challenge) {
ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this);
boolean launchedConfirmationActivity;
if (mUserId == UserHandle.USER_NULL) {
launchedConfirmationActivity = helper.launchConfirmationActivity(CONFIRM_REQUEST,
getString(titleResId),
null, null, challenge);
} else {
launchedConfirmationActivity = helper.launchConfirmationActivity(CONFIRM_REQUEST,
getString(titleResId),
null, null, challenge, mUserId);
}
if (!launchedConfirmationActivity) {
// This shouldn't happen, as we should only end up at this step if a lock thingy is
// already set.
finish();
} else {
mLaunchedConfirmLock = true;
}
}
} }

View File

@@ -102,9 +102,11 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
protected abstract String getExtraKeyForBiometric(); protected abstract String getExtraKeyForBiometric();
/** /**
* @return the intent for proceeding to the next step of enrollment * @return the intent for proceeding to the next step of enrollment. For Fingerprint, this
* should lead to the "Find Sensor" activity. For Face, this should lead to the "Enrolling"
* activity.
*/ */
protected abstract Intent getFindSensorIntent(); protected abstract Intent getEnrollingIntent();
/** /**
* @param span * @param span
@@ -179,7 +181,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
} }
private void launchFindSensor(byte[] token) { private void launchFindSensor(byte[] token) {
Intent intent = getFindSensorIntent(); Intent intent = getEnrollingIntent();
if (token != null) { if (token != null) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
} }

View File

@@ -0,0 +1,218 @@
/*
* Copyright (C) 2018 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;
import android.annotation.Nullable;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.UserHandle;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
import java.util.ArrayList;
/**
* Abstract sidecar fragment to handle the state around biometric enrollment. This sidecar manages
* the state of enrollment throughout the activity lifecycle so the app can continue after an
* event like rotation.
*/
public abstract class BiometricEnrollSidecar extends InstrumentedFragment {
public interface Listener {
void onEnrollmentHelp(CharSequence helpString);
void onEnrollmentError(int errMsgId, CharSequence errString);
void onEnrollmentProgressChange(int steps, int remaining);
}
private int mEnrollmentSteps = -1;
private int mEnrollmentRemaining = 0;
private Listener mListener;
private boolean mEnrolling;
private Handler mHandler = new Handler();
private boolean mDone;
private ArrayList<QueuedEvent> mQueuedEvents;
protected CancellationSignal mEnrollmentCancel;
protected byte[] mToken;
protected int mUserId;
private abstract class QueuedEvent {
public abstract void send(Listener listener);
}
private class QueuedEnrollmentProgress extends QueuedEvent {
int enrollmentSteps;
int remaining;
public QueuedEnrollmentProgress(int enrollmentSteps, int remaining) {
this.enrollmentSteps = enrollmentSteps;
this.remaining = remaining;
}
@Override
public void send(Listener listener) {
listener.onEnrollmentProgressChange(enrollmentSteps, remaining);
}
}
private class QueuedEnrollmentHelp extends QueuedEvent {
int helpMsgId;
CharSequence helpString;
public QueuedEnrollmentHelp(int helpMsgId, CharSequence helpString) {
this.helpMsgId = helpMsgId;
this.helpString = helpString;
}
@Override
public void send(Listener listener) {
listener.onEnrollmentHelp(helpString);
}
}
private class QueuedEnrollmentError extends QueuedEvent {
int errMsgId;
CharSequence errString;
public QueuedEnrollmentError(int errMsgId, CharSequence errString) {
this.errMsgId = errMsgId;
this.errString = errString;
}
@Override
public void send(Listener listener) {
listener.onEnrollmentError(errMsgId, errString);
}
}
private final Runnable mTimeoutRunnable = new Runnable() {
@Override
public void run() {
cancelEnrollment();
}
};
public BiometricEnrollSidecar() {
mQueuedEvents = new ArrayList<>();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mToken = activity.getIntent().getByteArrayExtra(
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
mUserId = activity.getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
}
@Override
public void onStart() {
super.onStart();
if (!mEnrolling) {
startEnrollment();
}
}
@Override
public void onStop() {
super.onStop();
if (!getActivity().isChangingConfigurations()) {
cancelEnrollment();
}
}
protected void startEnrollment() {
mHandler.removeCallbacks(mTimeoutRunnable);
mEnrollmentSteps = -1;
mEnrollmentCancel = new CancellationSignal();
mEnrolling = true;
}
public boolean cancelEnrollment() {
mHandler.removeCallbacks(mTimeoutRunnable);
if (mEnrolling) {
mEnrollmentCancel.cancel();
mEnrolling = false;
mEnrollmentSteps = -1;
return true;
}
return false;
}
protected void onEnrollmentProgress(int remaining) {
if (mEnrollmentSteps == -1) {
mEnrollmentSteps = remaining;
}
mEnrollmentRemaining = remaining;
mDone = remaining == 0;
if (mListener != null) {
mListener.onEnrollmentProgressChange(mEnrollmentSteps, remaining);
} else {
mQueuedEvents.add(new QueuedEnrollmentProgress(mEnrollmentSteps, remaining));
}
}
protected void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
if (mListener != null) {
mListener.onEnrollmentHelp(helpString);
} else {
mQueuedEvents.add(new QueuedEnrollmentHelp(helpMsgId, helpString));
}
}
protected void onEnrollmentError(int errMsgId, CharSequence errString) {
if (mListener != null) {
mListener.onEnrollmentError(errMsgId, errString);
} else {
mQueuedEvents.add(new QueuedEnrollmentError(errMsgId, errString));
}
mEnrolling = false;
}
public void setListener(Listener listener) {
mListener = listener;
if (mListener != null) {
for (int i=0; i<mQueuedEvents.size(); i++) {
QueuedEvent event = mQueuedEvents.get(i);
event.send(mListener);
}
mQueuedEvents.clear();
}
}
public int getEnrollmentSteps() {
return mEnrollmentSteps;
}
public int getEnrollmentRemaining() {
return mEnrollmentRemaining;
}
public boolean isDone() {
return mDone;
}
public boolean isEnrolling() {
return mEnrolling;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2018 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;
import static com.android.settings.biometrics.BiometricSettings.RESULT_FINISHED;
import static com.android.settings.biometrics.BiometricSettings.RESULT_TIMEOUT;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.hardware.biometrics.BiometricConstants;
import android.os.Bundle;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
/**
* Abstract dialog, shown when an error occurs during biometric enrollment.
*/
public abstract class BiometricErrorDialog extends InstrumentedDialogFragment {
public static final String KEY_ERROR_MSG = "error_msg";
public static final String KEY_ERROR_ID = "error_id";
public abstract int getTitleResId();
public abstract int getOkButtonTextResId();
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
CharSequence errorString = getArguments().getCharSequence(KEY_ERROR_MSG);
final int errMsgId = getArguments().getInt(KEY_ERROR_ID);
builder.setTitle(getTitleResId())
.setMessage(errorString)
.setCancelable(false)
.setPositiveButton(getOkButtonTextResId(),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
boolean wasTimeout =
errMsgId == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
Activity activity = getActivity();
activity.setResult(wasTimeout ?
RESULT_TIMEOUT : RESULT_FINISHED);
activity.finish();
}
});
AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2018 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;
import android.content.Intent;
import android.os.UserHandle;
import android.view.View;
import com.android.settings.R;
import com.android.settings.password.ChooseLockSettingsHelper;
/**
* Abstract base activity which handles the actual enrolling for biometrics.
*/
public abstract class BiometricsEnrollEnrolling extends BiometricEnrollBase
implements BiometricEnrollSidecar.Listener {
private static final String TAG_SIDECAR = "sidecar";
protected BiometricEnrollSidecar mSidecar;
/**
* @return the intent for the finish activity
*/
protected abstract Intent getFinishIntent();
/**
* @return an instance of the biometric enroll sidecar
*/
protected abstract BiometricEnrollSidecar getSidecar();
/**
* @return true if enrollment should start automatically.
*/
protected abstract boolean shouldStartAutomatically();
/**
* @return true if enrollment should finish when onStop is called.
*/
protected boolean shouldFinishOnStop() {
return true;
}
@Override
protected void onStart() {
super.onStart();
if (shouldStartAutomatically()) {
startEnrollment();
}
}
@Override
protected void onStop() {
super.onStop();
if (mSidecar != null) {
mSidecar.setListener(null);
}
if (shouldFinishOnStop() && !isChangingConfigurations()) {
if (mSidecar != null) {
mSidecar.cancelEnrollment();
getFragmentManager().beginTransaction().remove(mSidecar).commitAllowingStateLoss();
}
finish();
}
}
@Override
public void onBackPressed() {
if (mSidecar != null) {
mSidecar.setListener(null);
mSidecar.cancelEnrollment();
getFragmentManager().beginTransaction().remove(mSidecar).commitAllowingStateLoss();
mSidecar = null;
}
super.onBackPressed();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.skip_button:
setResult(RESULT_SKIP);
finish();
break;
default:
super.onClick(v);
}
}
public void startEnrollment() {
mSidecar = (BiometricEnrollSidecar) getFragmentManager().findFragmentByTag(TAG_SIDECAR);
if (mSidecar == null) {
mSidecar = getSidecar();
getFragmentManager().beginTransaction().add(mSidecar, TAG_SIDECAR).commit();
}
mSidecar.setListener(this);
}
protected void launchFinish(byte[] token) {
Intent intent = getFinishIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
| Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
if (mUserId != UserHandle.USER_NULL) {
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
}
startActivity(intent);
overridePendingTransition(R.anim.suw_slide_next_in, R.anim.suw_slide_next_out);
finish();
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright (C) 2018 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.face;
import android.content.Intent;
import android.hardware.face.FaceManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.Button;
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollSidecar;
import com.android.settings.biometrics.BiometricsEnrollEnrolling;
import com.android.settings.biometrics.BiometricErrorDialog;
import com.android.settings.password.ChooseLockSettingsHelper;
public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling {
private static final String TAG = "FaceEnrollEnrolling";
private static final boolean DEBUG = true;
private TextView mErrorText;
private Interpolator mLinearOutSlowInInterpolator;
private boolean mShouldFinishOnStop = true;
public static class FaceErrorDialog extends BiometricErrorDialog {
static FaceErrorDialog newInstance(CharSequence msg, int msgId) {
FaceErrorDialog dialog = new FaceErrorDialog();
Bundle args = new Bundle();
args.putCharSequence(KEY_ERROR_MSG, msg);
args.putInt(KEY_ERROR_ID, msgId);
dialog.setArguments(args);
return dialog;
}
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.DIALOG_FACE_ERROR;
}
@Override
public int getTitleResId() {
return R.string.security_settings_face_enroll_error_dialog_title;
}
@Override
public int getOkButtonTextResId() {
return R.string.security_settings_face_enroll_dialog_ok;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.face_enroll_enrolling);
setHeaderText(R.string.security_settings_face_enroll_repeat_title);
mErrorText = findViewById(R.id.error_text);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
this, android.R.interpolator.linear_out_slow_in);
Button skipButton = findViewById(R.id.skip_button);
skipButton.setOnClickListener(this);
if (shouldLaunchConfirmLock()) {
launchConfirmLock(R.string.security_settings_face_preference_title,
Utils.getFaceManagerOrNull(this).preEnroll());
mShouldFinishOnStop = false;
} else {
startEnrollment();
}
}
@Override
protected Intent getFinishIntent() {
return new Intent(this, FaceEnrollFinish.class);
}
@Override
protected BiometricEnrollSidecar getSidecar() {
return new FaceEnrollSidecar();
}
@Override
protected boolean shouldStartAutomatically() {
return false;
}
@Override
protected boolean shouldFinishOnStop() {
return mShouldFinishOnStop;
}
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.FACE_ENROLL_ENROLLING;
}
@Override
public void onEnrollmentHelp(CharSequence helpString) {
if (!TextUtils.isEmpty(helpString)) {
showError(helpString);
}
}
@Override
public void onEnrollmentError(int errMsgId, CharSequence errString) {
int msgId;
switch (errMsgId) {
case FaceManager.FACE_ERROR_TIMEOUT:
msgId = R.string.security_settings_face_enroll_error_timeout_dialog_message;
break;
default:
msgId = R.string.security_settings_face_enroll_error_generic_dialog_message;
break;
}
showErrorDialog(getText(msgId), errMsgId);
}
@Override
public void onEnrollmentProgressChange(int steps, int remaining) {
if (DEBUG) {
Log.v(TAG, "Steps: " + steps + " Remaining: " + remaining);
}
// TODO: Update the actual animation
showError("Steps: " + steps + " Remaining: " + remaining);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CONFIRM_REQUEST) {
if (resultCode == RESULT_OK && data != null) {
mShouldFinishOnStop = true;
mToken = data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
overridePendingTransition(R.anim.suw_slide_next_in, R.anim.suw_slide_next_out);
getIntent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
startEnrollment();
} else {
finish();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void showErrorDialog(CharSequence msg, int msgId) {
BiometricErrorDialog dialog = FaceErrorDialog.newInstance(msg, msgId);
dialog.show(getFragmentManager(), FaceErrorDialog.class.getName());
}
private void showError(CharSequence error) {
mErrorText.setText(error);
if (mErrorText.getVisibility() == View.INVISIBLE) {
mErrorText.setVisibility(View.VISIBLE);
mErrorText.setTranslationY(getResources().getDimensionPixelSize(
R.dimen.fingerprint_error_text_appear_distance));
mErrorText.setAlpha(0f);
mErrorText.animate()
.alpha(1f)
.translationY(0f)
.setDuration(200)
.setInterpolator(mLinearOutSlowInInterpolator)
.start();
} else {
mErrorText.animate().cancel();
mErrorText.setAlpha(1f);
mErrorText.setTranslationY(0f);
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2018 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.face;
import android.os.Bundle;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.biometrics.BiometricEnrollBase;
/**
* Activity which concludes face enrollment.
*/
public class FaceEnrollFinish extends BiometricEnrollBase {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.face_enroll_finish);
setHeaderText(R.string.security_settings_face_enroll_finish_title);
}
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.FACE_ENROLL_FINISHED;
}
@Override
public void onNextButtonClick() {
setResult(RESULT_FINISHED);
finish();
}
}

View File

@@ -113,8 +113,8 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
} }
@Override @Override
protected Intent getFindSensorIntent() { protected Intent getEnrollingIntent() {
return null; // TODO return new Intent(this, FaceEnrollEnrolling.class);
} }
@Override @Override

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2018 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.face;
import android.app.Activity;
import android.hardware.face.FaceManager;
import android.os.UserHandle;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollSidecar;
/**
* Sidecar fragment to handle the state around face enrollment
*/
public class FaceEnrollSidecar extends BiometricEnrollSidecar {
private FaceManager mFaceManager;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mFaceManager = Utils.getFaceManagerOrNull(activity);
}
@Override
public void startEnrollment() {
super.startEnrollment();
if (mUserId != UserHandle.USER_NULL) {
mFaceManager.setActiveUser(mUserId);
}
mFaceManager.enroll(mToken, mEnrollmentCancel,
0 /* flags */, mUserId, mEnrollmentCallback);
}
private FaceManager.EnrollmentCallback mEnrollmentCallback
= new FaceManager.EnrollmentCallback() {
@Override
public void onEnrollmentProgress(int remaining) {
FaceEnrollSidecar.super.onEnrollmentProgress(remaining);
}
@Override
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
FaceEnrollSidecar.super.onEnrollmentHelp(helpMsgId, helpString);
}
@Override
public void onEnrollmentError(int errMsgId, CharSequence errString) {
FaceEnrollSidecar.super.onEnrollmentError(errMsgId, errString);
}
};
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.FACE_ENROLL_SIDECAR;
}
}

View File

@@ -18,7 +18,6 @@ package com.android.settings.biometrics.fingerprint;
import android.animation.Animator; import android.animation.Animator;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
@@ -30,7 +29,6 @@ import android.graphics.drawable.LayerDrawable;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle;
import android.os.VibrationEffect; import android.os.VibrationEffect;
import android.os.Vibrator; import android.os.Vibrator;
import android.text.TextUtils; import android.text.TextUtils;
@@ -44,15 +42,15 @@ import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricEnrollSidecar;
import com.android.settings.biometrics.BiometricErrorDialog;
import com.android.settings.biometrics.BiometricsEnrollEnrolling;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
/** /**
* Activity which handles the actual enrolling for fingerprint. * Activity which handles the actual enrolling for fingerprint.
*/ */
public class FingerprintEnrollEnrolling extends BiometricEnrollBase public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
implements FingerprintEnrollSidecar.Listener {
static final String TAG_SIDECAR = "sidecar"; static final String TAG_SIDECAR = "sidecar";
@@ -93,13 +91,38 @@ public class FingerprintEnrollEnrolling extends BiometricEnrollBase
private Interpolator mLinearOutSlowInInterpolator; private Interpolator mLinearOutSlowInInterpolator;
private Interpolator mFastOutLinearInInterpolator; private Interpolator mFastOutLinearInInterpolator;
private int mIconTouchCount; private int mIconTouchCount;
private FingerprintEnrollSidecar mSidecar;
private boolean mAnimationCancelled; private boolean mAnimationCancelled;
private AnimatedVectorDrawable mIconAnimationDrawable; private AnimatedVectorDrawable mIconAnimationDrawable;
private AnimatedVectorDrawable mIconBackgroundBlinksDrawable; private AnimatedVectorDrawable mIconBackgroundBlinksDrawable;
private boolean mRestoring; private boolean mRestoring;
private Vibrator mVibrator; private Vibrator mVibrator;
public static class FingerprintErrorDialog extends BiometricErrorDialog {
static FingerprintErrorDialog newInstance(CharSequence msg, int msgId) {
FingerprintErrorDialog dialog = new FingerprintErrorDialog();
Bundle args = new Bundle();
args.putCharSequence(KEY_ERROR_MSG, msg);
args.putInt(KEY_ERROR_ID, msgId);
dialog.setArguments(args);
return dialog;
}
@Override
public int getMetricsCategory() {
return MetricsEvent.DIALOG_FINGERPINT_ERROR;
}
@Override
public int getTitleResId() {
return R.string.security_settings_fingerprint_enroll_error_dialog_title;
}
@Override
public int getOkButtonTextResId() {
return R.string.security_settings_fingerprint_enroll_dialog_ok;
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -147,15 +170,19 @@ public class FingerprintEnrollEnrolling extends BiometricEnrollBase
mRestoring = savedInstanceState != null; mRestoring = savedInstanceState != null;
} }
@Override
protected BiometricEnrollSidecar getSidecar() {
return new FingerprintEnrollSidecar();
}
@Override
protected boolean shouldStartAutomatically() {
return true;
}
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
mSidecar = (FingerprintEnrollSidecar) getFragmentManager().findFragmentByTag(TAG_SIDECAR);
if (mSidecar == null) {
mSidecar = new FingerprintEnrollSidecar();
getFragmentManager().beginTransaction().add(mSidecar, TAG_SIDECAR).commit();
}
mSidecar.setListener(this);
updateProgress(false /* animate */); updateProgress(false /* animate */);
updateDescription(); updateDescription();
if (mRestoring) { if (mRestoring) {
@@ -182,40 +209,7 @@ public class FingerprintEnrollEnrolling extends BiometricEnrollBase
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
if (mSidecar != null) {
mSidecar.setListener(null);
}
stopIconAnimation(); stopIconAnimation();
if (!isChangingConfigurations()) {
if (mSidecar != null) {
mSidecar.cancelEnrollment();
getFragmentManager().beginTransaction().remove(mSidecar).commitAllowingStateLoss();
}
finish();
}
}
@Override
public void onBackPressed() {
if (mSidecar != null) {
mSidecar.setListener(null);
mSidecar.cancelEnrollment();
getFragmentManager().beginTransaction().remove(mSidecar).commitAllowingStateLoss();
mSidecar = null;
}
super.onBackPressed();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.skip_button:
setResult(RESULT_SKIP);
finish();
break;
default:
super.onClick(v);
}
} }
private void animateProgress(int progress) { private void animateProgress(int progress) {
@@ -235,20 +229,6 @@ public class FingerprintEnrollEnrolling extends BiometricEnrollBase
mIconBackgroundBlinksDrawable.start(); mIconBackgroundBlinksDrawable.start();
} }
private void launchFinish(byte[] token) {
Intent intent = getFinishIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
| Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
if (mUserId != UserHandle.USER_NULL) {
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
}
startActivity(intent);
overridePendingTransition(R.anim.suw_slide_next_in, R.anim.suw_slide_next_out);
finish();
}
protected Intent getFinishIntent() { protected Intent getFinishIntent() {
return new Intent(this, FingerprintEnrollFinish.class); return new Intent(this, FingerprintEnrollFinish.class);
} }
@@ -263,7 +243,6 @@ public class FingerprintEnrollEnrolling extends BiometricEnrollBase
} }
} }
@Override @Override
public void onEnrollmentHelp(CharSequence helpString) { public void onEnrollmentHelp(CharSequence helpString) {
if (!TextUtils.isEmpty(helpString)) { if (!TextUtils.isEmpty(helpString)) {
@@ -323,8 +302,8 @@ public class FingerprintEnrollEnrolling extends BiometricEnrollBase
} }
private void showErrorDialog(CharSequence msg, int msgId) { private void showErrorDialog(CharSequence msg, int msgId) {
ErrorDialog dlg = ErrorDialog.newInstance(msg, msgId); BiometricErrorDialog dlg = FingerprintErrorDialog.newInstance(msg, msgId);
dlg.show(getFragmentManager(), ErrorDialog.class.getName()); dlg.show(getFragmentManager(), FingerprintErrorDialog.class.getName());
} }
private void showIconTouchDialog() { private void showIconTouchDialog() {
@@ -455,54 +434,4 @@ public class FingerprintEnrollEnrolling extends BiometricEnrollBase
return MetricsEvent.DIALOG_FINGERPRINT_ICON_TOUCH; return MetricsEvent.DIALOG_FINGERPRINT_ICON_TOUCH;
} }
} }
public static class ErrorDialog extends InstrumentedDialogFragment {
/**
* Create a new instance of ErrorDialog.
*
* @param msg the string to show for message text
* @param msgId the FingerprintManager error id so we know the cause
* @return a new ErrorDialog
*/
static ErrorDialog newInstance(CharSequence msg, int msgId) {
ErrorDialog dlg = new ErrorDialog();
Bundle args = new Bundle();
args.putCharSequence("error_msg", msg);
args.putInt("error_id", msgId);
dlg.setArguments(args);
return dlg;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
CharSequence errorString = getArguments().getCharSequence("error_msg");
final int errMsgId = getArguments().getInt("error_id");
builder.setTitle(R.string.security_settings_fingerprint_enroll_error_dialog_title)
.setMessage(errorString)
.setCancelable(false)
.setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
boolean wasTimeout =
errMsgId == FingerprintManager.FINGERPRINT_ERROR_TIMEOUT;
Activity activity = getActivity();
activity.setResult(wasTimeout ?
RESULT_TIMEOUT : RESULT_FINISHED);
activity.finish();
}
});
AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
@Override
public int getMetricsCategory() {
return MetricsEvent.DIALOG_FINGERPINT_ERROR;
}
}
} }

View File

@@ -19,16 +19,14 @@ package com.android.settings.biometrics.fingerprint;
import android.content.Intent; import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollSidecar.Listener; import com.android.settings.biometrics.BiometricEnrollSidecar.Listener;
import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.password.ChooseLockSettingsHelper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -38,14 +36,9 @@ import androidx.annotation.Nullable;
*/ */
public class FingerprintEnrollFindSensor extends BiometricEnrollBase { public class FingerprintEnrollFindSensor extends BiometricEnrollBase {
@VisibleForTesting
static final int CONFIRM_REQUEST = 1;
private static final int ENROLLING = 2;
public static final String EXTRA_KEY_LAUNCHED_CONFIRM = "launched_confirm_lock";
@Nullable @Nullable
private FingerprintFindSensorAnimation mAnimation; private FingerprintFindSensorAnimation mAnimation;
private boolean mLaunchedConfirmLock;
private FingerprintEnrollSidecar mSidecar; private FingerprintEnrollSidecar mSidecar;
private boolean mNextClicked; private boolean mNextClicked;
@@ -57,13 +50,10 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase {
skipButton.setOnClickListener(this); skipButton.setOnClickListener(this);
setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title); setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title);
if (savedInstanceState != null) {
mLaunchedConfirmLock = savedInstanceState.getBoolean(EXTRA_KEY_LAUNCHED_CONFIRM); if (shouldLaunchConfirmLock()) {
mToken = savedInstanceState.getByteArray( launchConfirmLock(R.string.security_settings_fingerprint_preference_title,
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); Utils.getFingerprintManagerOrNull(this).preEnroll());
}
if (mToken == null && !mLaunchedConfirmLock) {
launchConfirmLock();
} else if (mToken != null) { } else if (mToken != null) {
startLookingForFingerprint(); // already confirmed, so start looking for fingerprint startLookingForFingerprint(); // already confirmed, so start looking for fingerprint
} }
@@ -132,13 +122,6 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase {
} }
} }
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(EXTRA_KEY_LAUNCHED_CONFIRM, mLaunchedConfirmLock);
outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
}
@Override @Override
public void onClick(View v) { public void onClick(View v) {
switch (v.getId()) { switch (v.getId()) {
@@ -209,28 +192,6 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase {
} }
} }
private void launchConfirmLock() {
long challenge = Utils.getFingerprintManagerOrNull(this).preEnroll();
ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this);
boolean launchedConfirmationActivity = false;
if (mUserId == UserHandle.USER_NULL) {
launchedConfirmationActivity = helper.launchConfirmationActivity(CONFIRM_REQUEST,
getString(R.string.security_settings_fingerprint_preference_title),
null, null, challenge);
} else {
launchedConfirmationActivity = helper.launchConfirmationActivity(CONFIRM_REQUEST,
getString(R.string.security_settings_fingerprint_preference_title),
null, null, challenge, mUserId);
}
if (!launchedConfirmationActivity) {
// This shouldn't happen, as we should only end up at this step if a lock thingy is
// already set.
finish();
} else {
mLaunchedConfirmLock = true;
}
}
@Override @Override
public int getMetricsCategory() { public int getMetricsCategory() {
return MetricsEvent.FINGERPRINT_FIND_SENSOR; return MetricsEvent.FINGERPRINT_FIND_SENSOR;

View File

@@ -117,7 +117,7 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
} }
@Override @Override
protected Intent getFindSensorIntent() { protected Intent getEnrollingIntent() {
return new Intent(this, FingerprintEnrollFindSensor.class); return new Intent(this, FingerprintEnrollFindSensor.class);
} }

View File

@@ -16,164 +16,35 @@
package com.android.settings.biometrics.fingerprint; package com.android.settings.biometrics.fingerprint;
import android.annotation.Nullable;
import android.app.Activity; import android.app.Activity;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.UserHandle; import android.os.UserHandle;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.core.InstrumentedFragment; import com.android.settings.biometrics.BiometricEnrollSidecar;
import com.android.settings.password.ChooseLockSettingsHelper;
import java.util.ArrayList;
/** /**
* Sidecar fragment to handle the state around fingerprint enrollment. * Sidecar fragment to handle the state around fingerprint enrollment.
*/ */
public class FingerprintEnrollSidecar extends InstrumentedFragment { public class FingerprintEnrollSidecar extends BiometricEnrollSidecar {
private int mEnrollmentSteps = -1;
private int mEnrollmentRemaining = 0;
private Listener mListener;
private boolean mEnrolling;
private CancellationSignal mEnrollmentCancel;
private Handler mHandler = new Handler();
private byte[] mToken;
private boolean mDone;
private int mUserId;
private FingerprintManager mFingerprintManager; private FingerprintManager mFingerprintManager;
private ArrayList<QueuedEvent> mQueuedEvents;
private abstract class QueuedEvent {
public abstract void send(Listener listener);
}
private class QueuedEnrollmentProgress extends QueuedEvent {
int enrollmentSteps;
int remaining;
public QueuedEnrollmentProgress(int enrollmentSteps, int remaining) {
this.enrollmentSteps = enrollmentSteps;
this.remaining = remaining;
}
@Override
public void send(Listener listener) {
listener.onEnrollmentProgressChange(enrollmentSteps, remaining);
}
}
private class QueuedEnrollmentHelp extends QueuedEvent {
int helpMsgId;
CharSequence helpString;
public QueuedEnrollmentHelp(int helpMsgId, CharSequence helpString) {
this.helpMsgId = helpMsgId;
this.helpString = helpString;
}
@Override
public void send(Listener listener) {
listener.onEnrollmentHelp(helpString);
}
}
private class QueuedEnrollmentError extends QueuedEvent {
int errMsgId;
CharSequence errString;
public QueuedEnrollmentError(int errMsgId, CharSequence errString) {
this.errMsgId = errMsgId;
this.errString = errString;
}
@Override
public void send(Listener listener) {
listener.onEnrollmentError(errMsgId, errString);
}
}
public FingerprintEnrollSidecar() {
mQueuedEvents = new ArrayList<>();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override @Override
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(activity); super.onAttach(activity);
mFingerprintManager = Utils.getFingerprintManagerOrNull(activity); mFingerprintManager = Utils.getFingerprintManagerOrNull(activity);
mToken = activity.getIntent().getByteArrayExtra(
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
mUserId = activity.getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
} }
@Override @Override
public void onStart() { protected void startEnrollment() {
super.onStart(); super.startEnrollment();
if (!mEnrolling) {
startEnrollment();
}
}
@Override
public void onStop() {
super.onStop();
if (!getActivity().isChangingConfigurations()) {
cancelEnrollment();
}
}
private void startEnrollment() {
mHandler.removeCallbacks(mTimeoutRunnable);
mEnrollmentSteps = -1;
mEnrollmentCancel = new CancellationSignal();
if (mUserId != UserHandle.USER_NULL) { if (mUserId != UserHandle.USER_NULL) {
mFingerprintManager.setActiveUser(mUserId); mFingerprintManager.setActiveUser(mUserId);
} }
mFingerprintManager.enroll(mToken, mEnrollmentCancel, mFingerprintManager.enroll(mToken, mEnrollmentCancel,
0 /* flags */, mUserId, mEnrollmentCallback); 0 /* flags */, mUserId, mEnrollmentCallback);
mEnrolling = true;
}
boolean cancelEnrollment() {
mHandler.removeCallbacks(mTimeoutRunnable);
if (mEnrolling) {
mEnrollmentCancel.cancel();
mEnrolling = false;
mEnrollmentSteps = -1;
return true;
}
return false;
}
public void setListener(Listener listener) {
mListener = listener;
if (mListener != null) {
for (int i=0; i<mQueuedEvents.size(); i++) {
QueuedEvent event = mQueuedEvents.get(i);
event.send(mListener);
}
mQueuedEvents.clear();
}
}
public int getEnrollmentSteps() {
return mEnrollmentSteps;
}
public int getEnrollmentRemaining() {
return mEnrollmentRemaining;
}
public boolean isDone() {
return mDone;
} }
private FingerprintManager.EnrollmentCallback mEnrollmentCallback private FingerprintManager.EnrollmentCallback mEnrollmentCallback
@@ -181,42 +52,17 @@ public class FingerprintEnrollSidecar extends InstrumentedFragment {
@Override @Override
public void onEnrollmentProgress(int remaining) { public void onEnrollmentProgress(int remaining) {
if (mEnrollmentSteps == -1) { FingerprintEnrollSidecar.super.onEnrollmentProgress(remaining);
mEnrollmentSteps = remaining;
}
mEnrollmentRemaining = remaining;
mDone = remaining == 0;
if (mListener != null) {
mListener.onEnrollmentProgressChange(mEnrollmentSteps, remaining);
} else {
mQueuedEvents.add(new QueuedEnrollmentProgress(mEnrollmentSteps, remaining));
}
} }
@Override @Override
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
if (mListener != null) { FingerprintEnrollSidecar.super.onEnrollmentHelp(helpMsgId, helpString);
mListener.onEnrollmentHelp(helpString);
} else {
mQueuedEvents.add(new QueuedEnrollmentHelp(helpMsgId, helpString));
}
} }
@Override @Override
public void onEnrollmentError(int errMsgId, CharSequence errString) { public void onEnrollmentError(int errMsgId, CharSequence errString) {
if (mListener != null) { FingerprintEnrollSidecar.super.onEnrollmentError(errMsgId, errString);
mListener.onEnrollmentError(errMsgId, errString);
} else {
mQueuedEvents.add(new QueuedEnrollmentError(errMsgId, errString));
}
mEnrolling = false;
}
};
private final Runnable mTimeoutRunnable = new Runnable() {
@Override
public void run() {
cancelEnrollment();
} }
}; };
@@ -224,14 +70,4 @@ public class FingerprintEnrollSidecar extends InstrumentedFragment {
public int getMetricsCategory() { public int getMetricsCategory() {
return MetricsEvent.FINGERPRINT_ENROLL_SIDECAR; return MetricsEvent.FINGERPRINT_ENROLL_SIDECAR;
} }
public interface Listener {
void onEnrollmentHelp(CharSequence helpString);
void onEnrollmentError(int errMsgId, CharSequence errString);
void onEnrollmentProgressChange(int steps, int remaining);
}
public boolean isEnrolling() {
return mEnrolling;
}
} }

View File

@@ -70,7 +70,7 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu
} }
@Override @Override
protected Intent getFindSensorIntent() { protected Intent getEnrollingIntent() {
final Intent intent = new Intent(this, SetupFingerprintEnrollFindSensor.class); final Intent intent = new Intent(this, SetupFingerprintEnrollFindSensor.class);
SetupWizardUtils.copySetupExtras(getIntent(), intent); SetupWizardUtils.copySetupExtras(getIntent(), intent);
return intent; return intent;

View File

@@ -157,7 +157,7 @@ public class FingerprintEnrollFindSensorTest {
@Test @Test
public void onActivityResult_withNullIntentShouldNotCrash() { public void onActivityResult_withNullIntentShouldNotCrash() {
// this should not crash // this should not crash
mActivity.onActivityResult(FingerprintEnrollFindSensor.CONFIRM_REQUEST, Activity.RESULT_OK, mActivity.onActivityResult(BiometricEnrollBase.CONFIRM_REQUEST, Activity.RESULT_OK,
null); null);
assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(Activity.RESULT_CANCELED); assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(Activity.RESULT_CANCELED);
} }