Merge changes from topic "biometric-cc"

* changes:
  3/n: Make CDCA transparent and add BiometricPrompt
  2/n: Add comments to launchConfirmationActivity parameters
  1/n: Prepare ConfirmDeviceCredentials to use BiometricPrompt
This commit is contained in:
Kevin Chyn
2018-11-02 00:37:24 +00:00
committed by Android (Google) Code Review
21 changed files with 591 additions and 358 deletions

View File

@@ -80,6 +80,8 @@
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" /> <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" /> <uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.MANAGE_FINGERPRINT" /> <uses-permission android:name="android.permission.MANAGE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
<uses-permission android:name="android.permission.USER_ACTIVITY" /> <uses-permission android:name="android.permission.USER_ACTIVITY" />
<uses-permission android:name="android.permission.CHANGE_APP_IDLE_STATE" /> <uses-permission android:name="android.permission.CHANGE_APP_IDLE_STATE" />
<uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/> <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
@@ -1451,7 +1453,7 @@
<!-- Lock screen settings --> <!-- Lock screen settings -->
<activity android:name=".password.ConfirmDeviceCredentialActivity" <activity android:name=".password.ConfirmDeviceCredentialActivity"
android:exported="true" android:exported="true"
android:theme="@android:style/Theme.NoDisplay"> android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter android:priority="1"> <intent-filter android:priority="1">
<action android:name="android.app.action.CONFIRM_DEVICE_CREDENTIAL" /> <action android:name="android.app.action.CONFIRM_DEVICE_CREDENTIAL" />
<action android:name="android.app.action.CONFIRM_FRP_CREDENTIAL" /> <action android:name="android.app.action.CONFIRM_FRP_CREDENTIAL" />

View File

@@ -0,0 +1,24 @@
<?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
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:zAdjustment="top">
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
android:interpolator="@android:interpolator/linear_out_slow_in"
android:duration="350"/>
</set>

View File

@@ -0,0 +1,24 @@
<?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
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:zAdjustment="top">
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
android:interpolator="@android:interpolator/linear_out_slow_in"
android:duration="350" />
</set>

View File

@@ -1,30 +0,0 @@
<!--
~ Copyright (C) 2015 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
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32.0"
android:viewportHeight="32.0">
<path
android:fillColor="?android:attr/colorError"
android:pathData="M15.99,2.5C8.53,2.5 2.5,8.54 2.5,16.0s6.03,13.5 13.49,13.5S29.5,23.46 29.5,16.0S23.45,2.5 15.99,2.5zM16.0,26.8c-5.97,0.0 -10.8,-4.83 -10.8,-10.8S10.03,5.2 16.0,5.2S26.8,10.03 26.8,16.0S21.97,26.8 16.0,26.8z"/>
<path
android:fillColor="?android:attr/colorError"
android:pathData="M14.65,20.05l2.7,0.0l0.0,2.7l-2.7,0.0z"/>
<path
android:fillColor="?android:attr/colorError"
android:pathData="M14.65,9.25l2.7,0.0l0.0,8.1l-2.7,0.0z"/>
</vector>

View File

@@ -87,14 +87,6 @@
</LinearLayout> </LinearLayout>
<ImageView
android:id="@+id/fingerprintIcon"
android:layout_gravity="end|bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="28dp"
android:layout_marginEnd="20dp"
android:visibility="gone"/>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>

View File

@@ -108,14 +108,6 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1"/> android:layout_weight="1"/>
<ImageView
android:id="@+id/fingerprintIcon"
android:layout_gravity="center_vertical"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:visibility="gone"/>
</LinearLayout> </LinearLayout>
</com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient> </com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient>

View File

@@ -85,14 +85,6 @@
android:layout_marginEnd="?attr/suwMarginSides" android:layout_marginEnd="?attr/suwMarginSides"
android:layout_marginBottom="24dp" android:layout_marginBottom="24dp"
android:gravity="center_vertical"/> android:gravity="center_vertical"/>
<ImageView
android:id="@+id/fingerprintIcon"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/confirm_fingerprint_icon_content_description"
android:visibility="gone"/>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -89,19 +89,5 @@
</LinearLayout> </LinearLayout>
<View android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<ImageView
android:id="@+id/fingerprintIcon"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="24dp"
android:contentDescription="@string/confirm_fingerprint_icon_content_description"
android:visibility="gone"/>
</com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient> </com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient>
</FrameLayout> </FrameLayout>

View File

@@ -70,16 +70,6 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" /> android:layout_weight="1" />
<ImageView
android:id="@+id/fingerprintIcon"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="24dp"
android:contentDescription="@string/confirm_fingerprint_icon_content_description"
android:visibility="gone"/>
<Button <Button
android:id="@+id/cancelButton" android:id="@+id/cancelButton"
style="@style/SuwGlifButton.Secondary" style="@style/SuwGlifButton.Secondary"

View File

@@ -97,15 +97,6 @@
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:gravity="center_vertical"/> android:gravity="center_vertical"/>
<ImageView
android:id="@+id/fingerprintIcon"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="24dp"
android:contentDescription="@string/confirm_fingerprint_icon_content_description"
android:visibility="gone"/>
</LinearLayout> </LinearLayout>
</com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient> </com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient>

View File

@@ -90,16 +90,6 @@
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:gravity="center_vertical"/> android:gravity="center_vertical"/>
<ImageView
android:id="@+id/fingerprintIcon"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="?attr/suwMarginSides"
android:layout_marginBottom="24dp"
android:contentDescription="@string/confirm_fingerprint_icon_content_description"
android:visibility="gone"/>
<Button <Button
android:id="@+id/cancelButton" android:id="@+id/cancelButton"
style="@style/SuwGlifButton.Secondary" style="@style/SuwGlifButton.Secondary"

View File

@@ -1051,9 +1051,6 @@
<!-- Button to confirm the last removing the last fingerprint. [CHAR LIMIT=20]--> <!-- Button to confirm the last removing the last fingerprint. [CHAR LIMIT=20]-->
<string name="fingerprint_last_delete_confirm">Yes, remove</string> <string name="fingerprint_last_delete_confirm">Yes, remove</string>
<!-- Content description for the fingerprint icon when the user is prompted to enter his credentials. Not shown on the screen. [CHAR LIMIT=NONE] -->
<string name="confirm_fingerprint_icon_content_description">Use your fingerprint to continue.</string>
<!-- Title of the preferences category for preference items to control encryption --> <!-- Title of the preferences category for preference items to control encryption -->
<string name="crypt_keeper_settings_title">Encryption</string> <string name="crypt_keeper_settings_title">Encryption</string>
@@ -3709,6 +3706,10 @@
<!-- About phone settings screen, Safety Legal dialog title until the link is fully loaded --> <!-- About phone settings screen, Safety Legal dialog title until the link is fully loaded -->
<string name="settings_safetylegal_activity_loading">Loading\u2026</string> <string name="settings_safetylegal_activity_loading">Loading\u2026</string>
<!-- ConfirmDeviceCredential settings-->
<!-- Button text shown on BiometricPrompt (system dialog that asks for biometric authentication) giving the user the option to use an alternate form of authentication (Pin/Pattern/Pass) [CHAR LIMIT=30] -->
<string name="confirm_device_credential_use_alternate_method">Use alternate method</string>
<!-- Lock Pattern settings --> <!-- Lock Pattern settings -->
<!-- Header on first screen of choose password/PIN flow [CHAR LIMIT=40] --> <!-- Header on first screen of choose password/PIN flow [CHAR LIMIT=40] -->
<string name="lockpassword_choose_your_screen_lock_header">Set screen lock</string> <string name="lockpassword_choose_your_screen_lock_header">Set screen lock</string>
@@ -3771,8 +3772,8 @@
<string name="lockpassword_confirm_your_password_generic_profile">Enter your work password to continue</string> <string name="lockpassword_confirm_your_password_generic_profile">Enter your work password to continue</string>
<!-- This string shows up on a screen where a user can enter a pattern that <!-- This string shows up on a screen where a user can enter a pattern that
unlocks their device. This is an extra security measure that's required for them to unlocks their device. This is an extra security measure that's required for them to
continue. [CHAR LIMIT=100] --> continue. [CHAR LIMIT=100] -->
<string name="lockpassword_strong_auth_required_device_pattern">For added security, use your device pattern</string> <string name="lockpassword_strong_auth_required_device_pattern">For added security, use your device pattern</string>
<!-- This string shows up on a screen where a user can enter a PIN that unlocks their device. <!-- This string shows up on a screen where a user can enter a PIN that unlocks their device.
This is an extra security measure that's required for them to continue. [CHAR LIMIT=100] This is an extra security measure that's required for them to continue. [CHAR LIMIT=100]
@@ -8374,9 +8375,6 @@
<!-- Explanation that the app that will NEVER be launched to open web links to domains that it understands --> <!-- Explanation that the app that will NEVER be launched to open web links to domains that it understands -->
<string name="app_link_open_never">Don&#8217;t open in this app</string> <string name="app_link_open_never">Don&#8217;t open in this app</string>
<!-- Fingerprint hint message when finger was not recognized.-->
<string name="fingerprint_not_recognized">Not recognized</string>
<!-- Title for Default Apps settings [CHAR LIMIT=30] --> <!-- Title for Default Apps settings [CHAR LIMIT=30] -->
<string name="default_apps_title">Default</string> <string name="default_apps_title">Default</string>

View File

@@ -1,130 +0,0 @@
/*
* Copyright (C) 2015 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.hardware.fingerprint.FingerprintManager;
import android.os.CancellationSignal;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.Utils;
/**
* Small helper class to manage text/icon around fingerprint authentication UI.
*/
public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
private static final long ERROR_TIMEOUT = 1300;
private ImageView mIcon;
private TextView mErrorTextView;
private CancellationSignal mCancellationSignal;
private int mUserId;
private Callback mCallback;
private FingerprintManager mFingerprintManager;
public FingerprintUiHelper(ImageView icon, TextView errorTextView, Callback callback,
int userId) {
mFingerprintManager = Utils.getFingerprintManagerOrNull(icon.getContext());
mIcon = icon;
mErrorTextView = errorTextView;
mCallback = callback;
mUserId = userId;
}
public void startListening() {
if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()
&& mFingerprintManager.getEnrolledFingerprints(mUserId).size() > 0) {
mCancellationSignal = new CancellationSignal();
mFingerprintManager.setActiveUser(mUserId);
mFingerprintManager.authenticate(
null, mCancellationSignal, 0 /* flags */, this, null, mUserId);
setFingerprintIconVisibility(true);
mIcon.setImageResource(R.drawable.ic_fingerprint);
}
}
public void stopListening() {
if (mCancellationSignal != null) {
mCancellationSignal.cancel();
mCancellationSignal = null;
}
}
public boolean isListening() {
return mCancellationSignal != null && !mCancellationSignal.isCanceled();
}
private void setFingerprintIconVisibility(boolean visible) {
mIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
mCallback.onFingerprintIconVisibilityChanged(visible);
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
// Only happens if we get preempted by another activity. Ignored.
return;
}
showError(errString);
setFingerprintIconVisibility(false);
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
showError(helpString);
}
@Override
public void onAuthenticationFailed() {
showError(mIcon.getResources().getString(
R.string.fingerprint_not_recognized));
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
mIcon.setImageResource(R.drawable.ic_fingerprint_success);
mCallback.onAuthenticated();
}
private void showError(CharSequence error) {
if (!isListening()) {
return;
}
mIcon.setImageResource(R.drawable.ic_fingerprint_error);
mErrorTextView.setText(error);
mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT);
}
private Runnable mResetErrorTextRunnable = new Runnable() {
@Override
public void run() {
mErrorTextView.setText("");
mIcon.setImageResource(R.drawable.ic_fingerprint);
}
};
public interface Callback {
void onAuthenticated();
void onFingerprintIconVisibilityChanged(boolean visible);
}
}

View File

@@ -0,0 +1,198 @@
/*
* 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.password;
import android.app.settings.SettingsEnums;
import android.content.DialogInterface;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
import android.hardware.biometrics.BiometricPrompt.AuthenticationResult;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import com.android.settings.core.InstrumentedFragment;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.concurrent.Executor;
/**
* A fragment that wraps the BiometricPrompt and manages its lifecycle.
*/
public class BiometricFragment extends InstrumentedFragment {
private static final String KEY_TITLE = "title";
private static final String KEY_SUBTITLE = "subtitle";
private static final String KEY_DESCRIPTION = "description";
private static final String KEY_NEGATIVE_TEXT = "negative_text";
// Re-set by the application. Should be done upon orientation changes, etc
private Executor mClientExecutor;
private AuthenticationCallback mClientCallback;
// Created/Initialized once and retained
private final Handler mHandler = new Handler(Looper.getMainLooper());
private PromptInfo mPromptInfo;
private BiometricPrompt mBiometricPrompt;
private CancellationSignal mCancellationSignal;
private AuthenticationCallback mAuthenticationCallback =
new AuthenticationCallback() {
@Override
public void onAuthenticationError(int error, @NonNull CharSequence message) {
mClientExecutor.execute(() -> {
mClientCallback.onAuthenticationError(error, message);
});
cleanup();
}
@Override
public void onAuthenticationSucceeded(AuthenticationResult result) {
mClientExecutor.execute(() -> {
mClientCallback.onAuthenticationSucceeded(result);
});
cleanup();
}
};
private final DialogInterface.OnClickListener mNegativeButtonListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mAuthenticationCallback.onAuthenticationError(
BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON,
mPromptInfo.getNegativeButtonText());
}
};
public static BiometricFragment newInstance(PromptInfo info) {
BiometricFragment biometricFragment = new BiometricFragment();
biometricFragment.setArguments(info.getBundle());
return biometricFragment;
}
public void setCallbacks(Executor executor, AuthenticationCallback callback) {
mClientExecutor = executor;
mClientCallback = callback;
}
public void cancel() {
if (mCancellationSignal != null) {
mCancellationSignal.cancel();
}
cleanup();
}
private void cleanup() {
if (getActivity() != null) {
getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit();
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mPromptInfo = new PromptInfo(getArguments());
mBiometricPrompt = new BiometricPrompt.Builder(getContext())
.setTitle(mPromptInfo.getTitle())
.setUseDefaultTitle() // use default title if title is null/empty
.setSubtitle(mPromptInfo.getSubtitle())
.setDescription(mPromptInfo.getDescription())
.setNegativeButton(mPromptInfo.getNegativeButtonText(), mClientExecutor,
mNegativeButtonListener)
.build();
mCancellationSignal = new CancellationSignal();
// TODO: CC doesn't use crypto for now
mBiometricPrompt.authenticate(mCancellationSignal, mClientExecutor,
mAuthenticationCallback);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.BIOMETRIC_FRAGMENT;
}
/**
* A simple wrapper for BiometricPrompt.PromptInfo. Since we want to manage the lifecycle
* of BiometricPrompt correctly, the information needs to be stored in here.
*/
static class PromptInfo {
private final Bundle mBundle;
private PromptInfo(Bundle bundle) {
mBundle = bundle;
}
Bundle getBundle() {
return mBundle;
}
public CharSequence getTitle() {
return mBundle.getCharSequence(KEY_TITLE);
}
public CharSequence getSubtitle() {
return mBundle.getCharSequence(KEY_SUBTITLE);
}
public CharSequence getDescription() {
return mBundle.getCharSequence(KEY_DESCRIPTION);
}
public CharSequence getNegativeButtonText() {
return mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
}
public static class Builder {
private final Bundle mBundle = new Bundle();
public Builder setTitle(@NonNull CharSequence title) {
mBundle.putCharSequence(KEY_TITLE, title);
return this;
}
public Builder setSubtitle(@Nullable CharSequence subtitle) {
mBundle.putCharSequence(KEY_SUBTITLE, subtitle);
return this;
}
public Builder setDescription(@Nullable CharSequence description) {
mBundle.putCharSequence(KEY_DESCRIPTION, description);
return this;
}
public Builder setNegativeButtonText(@NonNull CharSequence text) {
mBundle.putCharSequence(KEY_NEGATIVE_TEXT, text);
return this;
}
public PromptInfo build() {
return new PromptInfo(mBundle);
}
}
}
}

View File

@@ -78,7 +78,13 @@ public final class ChooseLockSettingsHelper {
* @see Activity#onActivityResult(int, int, android.content.Intent) * @see Activity#onActivityResult(int, int, android.content.Intent)
*/ */
public boolean launchConfirmationActivity(int request, CharSequence title) { public boolean launchConfirmationActivity(int request, CharSequence title) {
return launchConfirmationActivity(request, title, null, null, false, false); return launchConfirmationActivity(
request /* request */,
title /* title */,
null /* header */,
null /* description */,
false /* returnCredentials */,
false /* external */);
} }
/** /**
@@ -91,7 +97,13 @@ public final class ChooseLockSettingsHelper {
* @see Activity#onActivityResult(int, int, android.content.Intent) * @see Activity#onActivityResult(int, int, android.content.Intent)
*/ */
public boolean launchConfirmationActivity(int request, CharSequence title, boolean returnCredentials) { public boolean launchConfirmationActivity(int request, CharSequence title, boolean returnCredentials) {
return launchConfirmationActivity(request, title, null, null, returnCredentials, false); return launchConfirmationActivity(
request /* request */,
title /* title */,
null /* header */,
null /* description */,
returnCredentials /* returnCredentials */,
false /* external */);
} }
/** /**
@@ -106,8 +118,16 @@ public final class ChooseLockSettingsHelper {
*/ */
public boolean launchConfirmationActivity(int request, CharSequence title, public boolean launchConfirmationActivity(int request, CharSequence title,
boolean returnCredentials, int userId) { boolean returnCredentials, int userId) {
return launchConfirmationActivity(request, title, null, null, return launchConfirmationActivity(
returnCredentials, false, false, 0, Utils.enforceSameOwner(mActivity, userId)); request /* request */,
title /* title */,
null /* header */,
null /* description */,
returnCredentials /* returnCredentials */,
false /* external */,
false /* hasChallenge */,
0 /* challenge */,
Utils.enforceSameOwner(mActivity, userId) /* userId */);
} }
/** /**
@@ -127,8 +147,16 @@ public final class ChooseLockSettingsHelper {
boolean launchConfirmationActivity(int request, @Nullable CharSequence title, boolean launchConfirmationActivity(int request, @Nullable CharSequence title,
@Nullable CharSequence header, @Nullable CharSequence description, @Nullable CharSequence header, @Nullable CharSequence description,
boolean returnCredentials, boolean external) { boolean returnCredentials, boolean external) {
return launchConfirmationActivity(request, title, header, description, return launchConfirmationActivity(
returnCredentials, external, false, 0, Utils.getCredentialOwnerUserId(mActivity)); request /* request */,
title /* title */,
header /* header */,
description /* description */,
returnCredentials /* returnCredentials */,
external /* external */,
false /* hasChallenge */,
0 /* challenge */,
Utils.getCredentialOwnerUserId(mActivity) /* userId */);
} }
/** /**
@@ -149,8 +177,16 @@ public final class ChooseLockSettingsHelper {
boolean launchConfirmationActivity(int request, @Nullable CharSequence title, boolean launchConfirmationActivity(int request, @Nullable CharSequence title,
@Nullable CharSequence header, @Nullable CharSequence description, @Nullable CharSequence header, @Nullable CharSequence description,
boolean returnCredentials, boolean external, int userId) { boolean returnCredentials, boolean external, int userId) {
return launchConfirmationActivity(request, title, header, description, return launchConfirmationActivity(
returnCredentials, external, false, 0, Utils.enforceSameOwner(mActivity, userId)); request /* request */,
title /* title */,
header /* header */,
description /* description */,
returnCredentials /* returnCredentials */,
external /* external */,
false /* hasChallenge */,
0 /* challenge */,
Utils.enforceSameOwner(mActivity, userId) /* userId */);
} }
/** /**
@@ -166,8 +202,16 @@ public final class ChooseLockSettingsHelper {
public boolean launchConfirmationActivity(int request, @Nullable CharSequence title, public boolean launchConfirmationActivity(int request, @Nullable CharSequence title,
@Nullable CharSequence header, @Nullable CharSequence description, @Nullable CharSequence header, @Nullable CharSequence description,
long challenge) { long challenge) {
return launchConfirmationActivity(request, title, header, description, return launchConfirmationActivity(
true, false, true, challenge, Utils.getCredentialOwnerUserId(mActivity)); request /* request */,
title /* title */,
header /* header */,
description /* description */,
true /* returnCredentials */,
false /* external */,
true /* hasChallenge */,
challenge /* challenge */,
Utils.getCredentialOwnerUserId(mActivity) /* userId */);
} }
/** /**
@@ -184,8 +228,16 @@ public final class ChooseLockSettingsHelper {
public boolean launchConfirmationActivity(int request, @Nullable CharSequence title, public boolean launchConfirmationActivity(int request, @Nullable CharSequence title,
@Nullable CharSequence header, @Nullable CharSequence description, @Nullable CharSequence header, @Nullable CharSequence description,
long challenge, int userId) { long challenge, int userId) {
return launchConfirmationActivity(request, title, header, description, return launchConfirmationActivity(
true, false, true, challenge, Utils.enforceSameOwner(mActivity, userId)); request /* request */,
title /* title */,
header /* header */,
description /* description */,
true /* returnCredentials */,
false /* external */,
true /* hasChallenge */,
challenge /* challenge */,
Utils.enforceSameOwner(mActivity, userId) /* userId */);
} }
/** /**
@@ -205,8 +257,16 @@ public final class ChooseLockSettingsHelper {
public boolean launchConfirmationActivityWithExternalAndChallenge(int request, public boolean launchConfirmationActivityWithExternalAndChallenge(int request,
@Nullable CharSequence title, @Nullable CharSequence header, @Nullable CharSequence title, @Nullable CharSequence header,
@Nullable CharSequence description, boolean external, long challenge, int userId) { @Nullable CharSequence description, boolean external, long challenge, int userId) {
return launchConfirmationActivity(request, title, header, description, false, return launchConfirmationActivity(
external, true, challenge, Utils.enforceSameOwner(mActivity, userId)); request /* request */,
title /* title */,
header /* header */,
description /* description */,
false /* returnCredentials */,
external /* external */,
true /* hasChallenge */,
challenge /* challenge */,
Utils.enforceSameOwner(mActivity, userId) /* userId */);
} }
/** /**
@@ -219,31 +279,69 @@ public final class ChooseLockSettingsHelper {
@Nullable CharSequence description, int userId) { @Nullable CharSequence description, int userId) {
final Bundle extras = new Bundle(); final Bundle extras = new Bundle();
extras.putBoolean(EXTRA_ALLOW_ANY_USER, true); extras.putBoolean(EXTRA_ALLOW_ANY_USER, true);
return launchConfirmationActivity(request, title, header, description, false, return launchConfirmationActivity(
false, true, 0, userId, extras); request /* request */,
title /* title */,
header /* header */,
description /* description */,
false /* returnCredentials */,
false /* external */,
true /* hasChallenge */,
0 /* challenge */,
userId /* userId */,
extras /* extras */);
} }
private boolean launchConfirmationActivity(int request, @Nullable CharSequence title, private boolean launchConfirmationActivity(int request, @Nullable CharSequence title,
@Nullable CharSequence header, @Nullable CharSequence description, @Nullable CharSequence header, @Nullable CharSequence description,
boolean returnCredentials, boolean external, boolean hasChallenge, boolean returnCredentials, boolean external, boolean hasChallenge,
long challenge, int userId) { long challenge, int userId) {
return launchConfirmationActivity(request, title, header, description, returnCredentials, return launchConfirmationActivity(
external, hasChallenge, challenge, userId, null /* alternateButton */, null); request /* request */,
title /* title */,
header /* header */,
description /* description */,
returnCredentials /* returnCredentials */,
external /* external */,
hasChallenge /* hasChallenge */,
challenge /* challenge */,
userId /* userId */,
null /* alternateButton */,
null /* extras */);
} }
private boolean launchConfirmationActivity(int request, @Nullable CharSequence title, private boolean launchConfirmationActivity(int request, @Nullable CharSequence title,
@Nullable CharSequence header, @Nullable CharSequence description, @Nullable CharSequence header, @Nullable CharSequence description,
boolean returnCredentials, boolean external, boolean hasChallenge, boolean returnCredentials, boolean external, boolean hasChallenge,
long challenge, int userId, Bundle extras) { long challenge, int userId, Bundle extras) {
return launchConfirmationActivity(request, title, header, description, returnCredentials, return launchConfirmationActivity(
external, hasChallenge, challenge, userId, null /* alternateButton */, extras); request /* request */,
title /* title */,
header /* header */,
description /* description */,
returnCredentials /* returnCredentials */,
external /* external */,
hasChallenge /* hasChallenge */,
challenge /* challenge */,
userId /* userId */,
null /* alternateButton */,
extras /* extras */);
} }
public boolean launchFrpConfirmationActivity(int request, @Nullable CharSequence header, public boolean launchFrpConfirmationActivity(int request, @Nullable CharSequence header,
@Nullable CharSequence description, @Nullable CharSequence alternateButton) { @Nullable CharSequence description, @Nullable CharSequence alternateButton) {
return launchConfirmationActivity(request, null /* title */, header, description, return launchConfirmationActivity(
false /* returnCredentials */, true /* external */, false /* hasChallenge */, request /* request */,
0 /* challenge */, LockPatternUtils.USER_FRP, alternateButton, null); null /* title */,
header /* header */,
description /* description */,
false /* returnCredentials */,
true /* external */,
false /* hasChallenge */,
0 /* challenge */,
LockPatternUtils.USER_FRP /* userId */,
alternateButton /* alternateButton */,
null /* extras */);
} }
private boolean launchConfirmationActivity(int request, @Nullable CharSequence title, private boolean launchConfirmationActivity(int request, @Nullable CharSequence title,
@@ -285,11 +383,11 @@ public final class ChooseLockSettingsHelper {
intent.putExtra(ConfirmDeviceCredentialBaseFragment.TITLE_TEXT, title); intent.putExtra(ConfirmDeviceCredentialBaseFragment.TITLE_TEXT, title);
intent.putExtra(ConfirmDeviceCredentialBaseFragment.HEADER_TEXT, header); intent.putExtra(ConfirmDeviceCredentialBaseFragment.HEADER_TEXT, header);
intent.putExtra(ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT, message); intent.putExtra(ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT, message);
intent.putExtra(ConfirmDeviceCredentialBaseFragment.ALLOW_FP_AUTHENTICATION, external);
// TODO: Remove dark theme and show_cancel_button options since they are no longer used // TODO: Remove dark theme and show_cancel_button options since they are no longer used
intent.putExtra(ConfirmDeviceCredentialBaseFragment.DARK_THEME, false); intent.putExtra(ConfirmDeviceCredentialBaseFragment.DARK_THEME, false);
intent.putExtra(ConfirmDeviceCredentialBaseFragment.SHOW_CANCEL_BUTTON, false); intent.putExtra(ConfirmDeviceCredentialBaseFragment.SHOW_CANCEL_BUTTON, false);
intent.putExtra(ConfirmDeviceCredentialBaseFragment.SHOW_WHEN_LOCKED, external); intent.putExtra(ConfirmDeviceCredentialBaseFragment.SHOW_WHEN_LOCKED, external);
intent.putExtra(ConfirmDeviceCredentialBaseFragment.USE_FADE_ANIMATION, external);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, returnCredentials); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, returnCredentials);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, hasChallenge); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, hasChallenge);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);

View File

@@ -22,21 +22,40 @@ import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import java.util.concurrent.Executor;
/** /**
* Launch this when you want to confirm the user is present by asking them to enter their * Launch this when you want to confirm the user is present by asking them to enter their
* PIN/password/pattern. * PIN/password/pattern.
*/ */
public class ConfirmDeviceCredentialActivity extends Activity { public class ConfirmDeviceCredentialActivity extends FragmentActivity {
public static final String TAG = ConfirmDeviceCredentialActivity.class.getSimpleName(); public static final String TAG = ConfirmDeviceCredentialActivity.class.getSimpleName();
// The normal flow that apps go through
private static final int CREDENTIAL_NORMAL = 1;
// Unlocks the managed profile when the primary profile is unlocked
private static final int CREDENTIAL_MANAGED = 2;
private static final String TAG_BIOMETRIC_FRAGMENT = "fragment";
public static class InternalActivity extends ConfirmDeviceCredentialActivity { public static class InternalActivity extends ConfirmDeviceCredentialActivity {
} }
@@ -60,57 +79,217 @@ public class ConfirmDeviceCredentialActivity extends Activity {
return intent; return intent;
} }
private BiometricManager mBiometricManager;
private BiometricFragment mBiometricFragment;
private DevicePolicyManager mDevicePolicyManager;
private LockPatternUtils mLockPatternUtils;
private UserManager mUserManager;
private ChooseLockSettingsHelper mChooseLockSettingsHelper;
private Handler mHandler = new Handler(Looper.getMainLooper());
private String mTitle;
private String mDetails;
private int mUserId;
private int mEffectiveUserId;
private int mCredentialMode;
private boolean mGoingToBackground;
private Executor mExecutor = (runnable -> {
mHandler.post(runnable);
});
private AuthenticationCallback mAuthenticationCallback = new AuthenticationCallback() {
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
if (!mGoingToBackground) {
if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED) {
finish();
} else {
// All other errors go to some version of CC
showConfirmCredentials();
}
}
}
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
setResult(Activity.RESULT_OK);
finish();
}
};
@Override @Override
public void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mBiometricManager = getSystemService(BiometricManager.class);
mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
mUserManager = UserManager.get(this);
mLockPatternUtils = new LockPatternUtils(this);
Intent intent = getIntent(); Intent intent = getIntent();
String title = intent.getStringExtra(KeyguardManager.EXTRA_TITLE); mTitle = intent.getStringExtra(KeyguardManager.EXTRA_TITLE);
String details = intent.getStringExtra(KeyguardManager.EXTRA_DESCRIPTION); mDetails = intent.getStringExtra(KeyguardManager.EXTRA_DESCRIPTION);
String alternateButton = intent.getStringExtra( String alternateButton = intent.getStringExtra(
KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL); KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
boolean frp = KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction()); boolean frp = KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction());
int userId = UserHandle.myUserId(); mUserId = UserHandle.myUserId();
mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
if (isInternalActivity()) { if (isInternalActivity()) {
try { try {
userId = Utils.getUserIdFromBundle(this, intent.getExtras()); mUserId = Utils.getUserIdFromBundle(this, intent.getExtras());
} catch (SecurityException se) { } catch (SecurityException se) {
Log.e(TAG, "Invalid intent extra", se); Log.e(TAG, "Invalid intent extra", se);
} }
} }
final boolean isManagedProfile = UserManager.get(this).isManagedProfile(userId); final boolean isManagedProfile = UserManager.get(this).isManagedProfile(mUserId);
// if the client app did not hand in a title and we are about to show the work challenge, // if the client app did not hand in a title and we are about to show the work challenge,
// check whether there is a policy setting the organization name and use that as title // check whether there is a policy setting the organization name and use that as title
if ((title == null) && isManagedProfile) { if ((mTitle == null) && isManagedProfile) {
title = getTitleFromOrganizationName(userId); mTitle = getTitleFromOrganizationName(mUserId);
} }
ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this); mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this);
final LockPatternUtils lockPatternUtils = new LockPatternUtils(this); final LockPatternUtils lockPatternUtils = new LockPatternUtils(this);
boolean launched;
boolean launchedBiometric = false;
boolean launchedCDC = false;
// If the target is a managed user and user key not unlocked yet, we will force unlock // If the target is a managed user and user key not unlocked yet, we will force unlock
// tied profile so it will enable work mode and unlock managed profile, when personal // tied profile so it will enable work mode and unlock managed profile, when personal
// challenge is unlocked. // challenge is unlocked.
if (frp) { if (frp) {
launched = helper.launchFrpConfirmationActivity(0, title, details, alternateButton); launchedCDC = mChooseLockSettingsHelper.launchFrpConfirmationActivity(
0, mTitle, mDetails, alternateButton);
} else if (isManagedProfile && isInternalActivity() } else if (isManagedProfile && isInternalActivity()
&& !lockPatternUtils.isSeparateProfileChallengeEnabled(userId)) { && !lockPatternUtils.isSeparateProfileChallengeEnabled(mUserId)) {
mCredentialMode = CREDENTIAL_MANAGED;
if (isBiometricAllowed()) {
showBiometricPrompt();
launchedBiometric = true;
} else {
showConfirmCredentials();
}
} else {
mCredentialMode = CREDENTIAL_NORMAL;
if (isBiometricAllowed()) {
// Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
// onAuthenticationError and do the right thing automatically.
showBiometricPrompt();
launchedBiometric = true;
} else {
showConfirmCredentials();
}
}
if (launchedCDC) {
finish();
} else if (launchedBiometric) {
// Keep this activity alive until BiometricPrompt goes away
} else {
Log.d(TAG, "No pattern, password or PIN set.");
setResult(Activity.RESULT_OK);
finish();
}
}
@Override
protected void onStart() {
super.onStart();
// Translucent activity that is "visible", so it doesn't complain about finish()
// not being called before onResume().
setVisible(true);
}
@Override
public void onPause() {
super.onPause();
if (!isChangingConfigurations()) {
mGoingToBackground = true;
if (mBiometricFragment != null) {
mBiometricFragment.cancel();
}
finish();
} else {
mGoingToBackground = false;
}
}
// User could be locked while Effective user is unlocked even though the effective owns the
// credential. Otherwise, biometric can't unlock fbe/keystore through
// verifyTiedProfileChallenge. In such case, we also wanna show the user message that
// biometric is disabled due to device restart.
private boolean isStrongAuthRequired() {
return !mLockPatternUtils.isBiometricAllowedForUser(mEffectiveUserId)
|| !mUserManager.isUserUnlocked(mUserId);
}
private boolean isBiometricDisabledByAdmin() {
final int disabledFeatures =
mDevicePolicyManager.getKeyguardDisabledFeatures(null, mEffectiveUserId);
return (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS) != 0;
}
private boolean isBiometricAllowed() {
return !isStrongAuthRequired() && !isBiometricDisabledByAdmin();
}
private void showBiometricPrompt() {
mBiometricManager.setActiveUser(mUserId);
mBiometricFragment = (BiometricFragment) getSupportFragmentManager()
.findFragmentByTag(TAG_BIOMETRIC_FRAGMENT);
boolean newFragment = false;
if (mBiometricFragment == null) {
final BiometricFragment.PromptInfo info = new BiometricFragment.PromptInfo.Builder()
.setTitle(mTitle)
.setSubtitle(mDetails)
.setNegativeButtonText(getResources()
.getString(R.string.confirm_device_credential_use_alternate_method))
.build();
mBiometricFragment = BiometricFragment.newInstance(info);
newFragment = true;
}
mBiometricFragment.setCallbacks(mExecutor, mAuthenticationCallback);
if (newFragment) {
getSupportFragmentManager().beginTransaction()
.add(mBiometricFragment, TAG_BIOMETRIC_FRAGMENT).commit();
}
}
/**
* Shows ConfirmDeviceCredentials for normal apps.
*/
private void showConfirmCredentials() {
boolean launched = false;
if (mCredentialMode == CREDENTIAL_MANAGED) {
// We set the challenge as 0L, so it will force to unlock managed profile when it // We set the challenge as 0L, so it will force to unlock managed profile when it
// unlocks primary profile screen lock, by calling verifyTiedProfileChallenge() // unlocks primary profile screen lock, by calling verifyTiedProfileChallenge()
launched = helper.launchConfirmationActivityWithExternalAndChallenge( launched = mChooseLockSettingsHelper
0 /* request code */, null /* title */, title, details, true /* isExternal */, .launchConfirmationActivityWithExternalAndChallenge(
0L /* challenge */, userId); 0 /* request code */, null /* title */, mTitle, mDetails,
} else { true /* isExternal */, 0L /* challenge */, mUserId);
launched = helper.launchConfirmationActivity(0 /* request code */, null /* title */, } else if (mCredentialMode == CREDENTIAL_NORMAL){
title, details, false /* returnCredentials */, true /* isExternal */, userId); launched = mChooseLockSettingsHelper.launchConfirmationActivity(
0 /* request code */, null /* title */,
mTitle, mDetails, false /* returnCredentials */, true /* isExternal */,
mUserId);
} }
if (!launched) { if (!launched) {
Log.d(TAG, "No pattern, password or PIN set."); Log.d(TAG, "No pin/pattern/pass set");
setResult(Activity.RESULT_OK); setResult(Activity.RESULT_OK);
} }
finish(); finish();
} }
@Override
public void finish() {
super.finish();
// Finish without animation since the activity is just there so we can launch
// BiometricPrompt.
overridePendingTransition(R.anim.confirm_credential_biometric_transition_enter, 0);
}
private boolean isInternalActivity() { private boolean isInternalActivity() {
return this instanceof ConfirmDeviceCredentialActivity.InternalActivity; return this instanceof ConfirmDeviceCredentialActivity.InternalActivity;
} }

View File

@@ -140,6 +140,15 @@ public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivi
} }
} }
@Override
public void finish() {
super.finish();
if (getIntent().getBooleanExtra(
ConfirmDeviceCredentialBaseFragment.USE_FADE_ANIMATION, false)) {
overridePendingTransition(0, R.anim.confirm_credential_biometric_transition_exit);
}
}
public void prepareEnterAnimation() { public void prepareEnterAnimation() {
getFragment().prepareEnterAnimation(); getFragment().prepareEnterAnimation();
} }

View File

@@ -53,26 +53,24 @@ import androidx.fragment.app.FragmentManager;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
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.fingerprint.FingerprintUiHelper;
import com.android.settings.core.InstrumentedFragment; import com.android.settings.core.InstrumentedFragment;
/** /**
* Base fragment to be shared for PIN/Pattern/Password confirmation fragments. * Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
*/ */
public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFragment public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFragment {
implements FingerprintUiHelper.Callback {
public static final String PACKAGE = "com.android.settings"; public static final String PACKAGE = "com.android.settings";
public static final String TITLE_TEXT = PACKAGE + ".ConfirmCredentials.title"; public static final String TITLE_TEXT = PACKAGE + ".ConfirmCredentials.title";
public static final String HEADER_TEXT = PACKAGE + ".ConfirmCredentials.header"; public static final String HEADER_TEXT = PACKAGE + ".ConfirmCredentials.header";
public static final String DETAILS_TEXT = PACKAGE + ".ConfirmCredentials.details"; public static final String DETAILS_TEXT = PACKAGE + ".ConfirmCredentials.details";
public static final String ALLOW_FP_AUTHENTICATION =
PACKAGE + ".ConfirmCredentials.allowFpAuthentication";
public static final String DARK_THEME = PACKAGE + ".ConfirmCredentials.darkTheme"; public static final String DARK_THEME = PACKAGE + ".ConfirmCredentials.darkTheme";
public static final String SHOW_CANCEL_BUTTON = public static final String SHOW_CANCEL_BUTTON =
PACKAGE + ".ConfirmCredentials.showCancelButton"; PACKAGE + ".ConfirmCredentials.showCancelButton";
public static final String SHOW_WHEN_LOCKED = public static final String SHOW_WHEN_LOCKED =
PACKAGE + ".ConfirmCredentials.showWhenLocked"; PACKAGE + ".ConfirmCredentials.showWhenLocked";
public static final String USE_FADE_ANIMATION =
PACKAGE + ".ConfirmCredentials.useFadeAnimation";
protected static final int USER_TYPE_PRIMARY = 1; protected static final int USER_TYPE_PRIMARY = 1;
protected static final int USER_TYPE_MANAGED_PROFILE = 2; protected static final int USER_TYPE_MANAGED_PROFILE = 2;
@@ -81,10 +79,8 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
/** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */ /** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */
protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000; protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000;
private FingerprintUiHelper mFingerprintHelper;
protected boolean mReturnCredentials = false; protected boolean mReturnCredentials = false;
protected Button mCancelButton; protected Button mCancelButton;
protected ImageView mFingerprintIcon;
protected int mEffectiveUserId; protected int mEffectiveUserId;
protected int mUserId; protected int mUserId;
protected UserManager mUserManager; protected UserManager mUserManager;
@@ -123,9 +119,7 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
mCancelButton = (Button) view.findViewById(R.id.cancelButton); mCancelButton = (Button) view.findViewById(R.id.cancelButton);
mFingerprintIcon = (ImageView) view.findViewById(R.id.fingerprintIcon);
mFingerprintHelper = new FingerprintUiHelper(
mFingerprintIcon, view.findViewById(R.id.errorText), this, mUserId);
boolean showCancelButton = getActivity().getIntent().getBooleanExtra( boolean showCancelButton = getActivity().getIntent().getBooleanExtra(
SHOW_CANCEL_BUTTON, false); SHOW_CANCEL_BUTTON, false);
boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText); boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText);
@@ -153,29 +147,16 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
} }
} }
private boolean isFingerprintDisabledByAdmin() {
final int disabledFeatures =
mDevicePolicyManager.getKeyguardDisabledFeatures(null, mEffectiveUserId);
return (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0;
}
// User could be locked while Effective user is unlocked even though the effective owns the // User could be locked while Effective user is unlocked even though the effective owns the
// credential. Otherwise, fingerprint can't unlock fbe/keystore through // credential. Otherwise, fingerprint can't unlock fbe/keystore through
// verifyTiedProfileChallenge. In such case, we also wanna show the user message that // verifyTiedProfileChallenge. In such case, we also wanna show the user message that
// fingerprint is disabled due to device restart. // fingerprint is disabled due to device restart.
protected boolean isStrongAuthRequired() { protected boolean isStrongAuthRequired() {
return mFrp return mFrp
|| !mLockPatternUtils.isFingerprintAllowedForUser(mEffectiveUserId) || !mLockPatternUtils.isBiometricAllowedForUser(mEffectiveUserId)
|| !mUserManager.isUserUnlocked(mUserId); || !mUserManager.isUserUnlocked(mUserId);
} }
private boolean isFingerprintAllowed() {
return !mReturnCredentials
&& getActivity().getIntent().getBooleanExtra(ALLOW_FP_AUTHENTICATION, false)
&& !isStrongAuthRequired()
&& !isFingerprintDisabledByAdmin();
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
@@ -183,13 +164,6 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
} }
protected void refreshLockScreen() { protected void refreshLockScreen() {
if (isFingerprintAllowed()) {
mFingerprintHelper.startListening();
} else {
if (mFingerprintHelper.isListening()) {
mFingerprintHelper.stopListening();
}
}
updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
} }
@@ -214,28 +188,10 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
if (mFingerprintHelper.isListening()) {
mFingerprintHelper.stopListening();
}
}
@Override
public void onAuthenticated() {
// Check whether we are still active.
if (getActivity() != null && getActivity().isResumed()) {
TrustManager trustManager =
(TrustManager) getActivity().getSystemService(Context.TRUST_SERVICE);
trustManager.setDeviceLockedForUser(mEffectiveUserId, false);
authenticationSucceeded();
checkForPendingIntent();
}
} }
protected abstract void authenticationSucceeded(); protected abstract void authenticationSucceeded();
@Override
public void onFingerprintIconVisibilityChanged(boolean visible) {
}
public void prepareEnterAnimation() { public void prepareEnterAnimation() {
} }

View File

@@ -105,7 +105,6 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
private CountDownTimer mCountdownTimer; private CountDownTimer mCountdownTimer;
private boolean mIsAlpha; private boolean mIsAlpha;
private InputMethodManager mImm; private InputMethodManager mImm;
private boolean mUsingFingerprint = false;
private AppearAnimationUtils mAppearAnimationUtils; private AppearAnimationUtils mAppearAnimationUtils;
private DisappearAnimationUtils mDisappearAnimationUtils; private DisappearAnimationUtils mDisappearAnimationUtils;
@@ -243,7 +242,6 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
mCancelButton.setAlpha(0f); mCancelButton.setAlpha(0f);
mPasswordEntry.setAlpha(0f); mPasswordEntry.setAlpha(0f);
mErrorTextView.setAlpha(0f); mErrorTextView.setAlpha(0f);
mFingerprintIcon.setAlpha(0f);
} }
private View[] getActiveViews() { private View[] getActiveViews() {
@@ -255,9 +253,6 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
} }
result.add(mPasswordEntry); result.add(mPasswordEntry);
result.add(mErrorTextView); result.add(mErrorTextView);
if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
result.add(mFingerprintIcon);
}
return result.toArray(new View[] {}); return result.toArray(new View[] {});
} }
@@ -303,17 +298,12 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId); mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
} }
@Override
public void onFingerprintIconVisibilityChanged(boolean visible) {
mUsingFingerprint = visible;
}
private void updatePasswordEntry() { private void updatePasswordEntry() {
final boolean isLockedOut = final boolean isLockedOut =
mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0; mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0;
mPasswordEntry.setEnabled(!isLockedOut); mPasswordEntry.setEnabled(!isLockedOut);
mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut); mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut);
if (isLockedOut || mUsingFingerprint) { if (isLockedOut) {
mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/); mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/);
} else { } else {
mPasswordEntry.scheduleShowSoftInput(); mPasswordEntry.scheduleShowSoftInput();

View File

@@ -231,7 +231,6 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
mCancelButton.setAlpha(0f); mCancelButton.setAlpha(0f);
mLockPatternView.setAlpha(0f); mLockPatternView.setAlpha(0f);
mDetailsTextView.setAlpha(0f); mDetailsTextView.setAlpha(0f);
mFingerprintIcon.setAlpha(0f);
} }
private int getDefaultDetails() { private int getDefaultDetails() {
@@ -265,9 +264,6 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
} }
result.add(row); result.add(row);
} }
if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
result.add(new ArrayList<Object>(Collections.singletonList(mFingerprintIcon)));
}
Object[][] resultArr = new Object[result.size()][cellStates[0].length]; Object[][] resultArr = new Object[result.size()][cellStates[0].length];
for (int i = 0; i < result.size(); i++) { for (int i = 0; i < result.size(); i++) {
ArrayList<Object> row = result.get(i); ArrayList<Object> row = result.get(i);
@@ -377,16 +373,6 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
} }
} }
@Override
public void onFingerprintIconVisibilityChanged(boolean visible) {
if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) {
// In landscape, adjust spacing depending on fingerprint icon visibility.
mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
}
}
/** /**
* The pattern listener that responds according to a user confirming * The pattern listener that responds according to a user confirming
* an existing lock pattern. * an existing lock pattern.

View File

@@ -62,8 +62,6 @@ public class ChooseLockSettingsHelperTest {
assertEquals( assertEquals(
true, true,
(startedIntent.getFlags() & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0); (startedIntent.getFlags() & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0);
assertEquals(true, startedIntent.getBooleanExtra(
ConfirmDeviceCredentialBaseFragment.ALLOW_FP_AUTHENTICATION, false));
assertFalse(startedIntent.getBooleanExtra( assertFalse(startedIntent.getBooleanExtra(
ConfirmDeviceCredentialBaseFragment.DARK_THEME, false)); ConfirmDeviceCredentialBaseFragment.DARK_THEME, false));
assertFalse(startedIntent.getBooleanExtra( assertFalse(startedIntent.getBooleanExtra(
@@ -100,8 +98,6 @@ public class ChooseLockSettingsHelperTest {
assertEquals( assertEquals(
false, false,
(startedIntent.getFlags() & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0); (startedIntent.getFlags() & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0);
assertEquals(false, startedIntent.getBooleanExtra(
ConfirmDeviceCredentialBaseFragment.ALLOW_FP_AUTHENTICATION, false));
assertFalse(startedIntent.getBooleanExtra( assertFalse(startedIntent.getBooleanExtra(
ConfirmDeviceCredentialBaseFragment.DARK_THEME, false)); ConfirmDeviceCredentialBaseFragment.DARK_THEME, false));
assertFalse(startedIntent.getBooleanExtra( assertFalse(startedIntent.getBooleanExtra(