Files
app_Settings/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
Robin Lee 11bf78028e CredentialFragment anti-pocket dialing measures.
Now it is necessary to actually press the exact "OK" or Back button to
get the dialog to dismiss. This makes pocket wiping that little bit less
likely.

Bug: 32934848
Test: manual, enter the password too many times. The dialog should appear. Attempt to dismiss it by tapping outside the dialog. This should not happen.
Test: correctness of .setCanceledOnTouchOutside is outside scope.
Change-Id: Icff8bd9068f636c0a75decb787b8a5c9161a8cbd
2017-02-11 19:49:02 +00:00

396 lines
16 KiB
Java

/*
* 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
*/
// TODO (b/35202196): move this class out of the root of the package.
package com.android.settings;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserManager;
import android.security.KeyStore;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.fingerprint.FingerprintUiHelper;
/**
* Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
*/
public abstract class ConfirmDeviceCredentialBaseFragment extends OptionsMenuFragment
implements FingerprintUiHelper.Callback {
public static final String PACKAGE = "com.android.settings";
public static final String TITLE_TEXT = PACKAGE + ".ConfirmCredentials.title";
public static final String HEADER_TEXT = PACKAGE + ".ConfirmCredentials.header";
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 SHOW_CANCEL_BUTTON =
PACKAGE + ".ConfirmCredentials.showCancelButton";
public static final String SHOW_WHEN_LOCKED =
PACKAGE + ".ConfirmCredentials.showWhenLocked";
private FingerprintUiHelper mFingerprintHelper;
protected boolean mIsStrongAuthRequired;
private boolean mAllowFpAuthentication;
protected boolean mReturnCredentials = false;
protected Button mCancelButton;
protected ImageView mFingerprintIcon;
protected int mEffectiveUserId;
protected int mUserId;
protected LockPatternUtils mLockPatternUtils;
protected TextView mErrorTextView;
protected final Handler mHandler = new Handler();
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAllowFpAuthentication = getActivity().getIntent().getBooleanExtra(
ALLOW_FP_AUTHENTICATION, false);
mReturnCredentials = getActivity().getIntent().getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false);
// Only take this argument into account if it belongs to the current profile.
Intent intent = getActivity().getIntent();
mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
final UserManager userManager = UserManager.get(getActivity());
mEffectiveUserId = userManager.getCredentialOwnerProfile(mUserId);
mLockPatternUtils = new LockPatternUtils(getActivity());
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mCancelButton = (Button) view.findViewById(R.id.cancelButton);
mFingerprintIcon = (ImageView) view.findViewById(R.id.fingerprintIcon);
mFingerprintHelper = new FingerprintUiHelper(
mFingerprintIcon,
(TextView) view.findViewById(R.id.errorText), this, mEffectiveUserId);
boolean showCancelButton = getActivity().getIntent().getBooleanExtra(
SHOW_CANCEL_BUTTON, false);
mCancelButton.setVisibility(showCancelButton ? View.VISIBLE : View.GONE);
mCancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getActivity().finish();
}
});
int credentialOwnerUserId = Utils.getCredentialOwnerUserId(
getActivity(),
Utils.getUserIdFromBundle(
getActivity(),
getActivity().getIntent().getExtras()));
if (UserManager.get(getActivity()).isManagedProfile(credentialOwnerUserId)) {
setWorkChallengeBackground(view, credentialOwnerUserId);
}
}
private boolean isFingerprintDisabledByAdmin() {
DevicePolicyManager dpm = (DevicePolicyManager) getActivity().getSystemService(
Context.DEVICE_POLICY_SERVICE);
final int disabledFeatures = dpm.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
// credential. Otherwise, fingerprint can't unlock fbe/keystore through
// verifyTiedProfileChallenge. In such case, we also wanna show the user message that
// fingerprint is disabled due to device restart.
private boolean isFingerprintDisallowedByStrongAuth() {
return !(mLockPatternUtils.isFingerprintAllowedForUser(mEffectiveUserId)
&& KeyStore.getInstance().state(mUserId) == KeyStore.State.UNLOCKED);
}
@Override
public void onResume() {
super.onResume();
mIsStrongAuthRequired = isFingerprintDisallowedByStrongAuth();
mAllowFpAuthentication = getActivity().getIntent().getBooleanExtra(
ALLOW_FP_AUTHENTICATION, false)
&& !isFingerprintDisabledByAdmin() && !mReturnCredentials && !mIsStrongAuthRequired;
refreshLockScreen();
}
protected void refreshLockScreen() {
if (mAllowFpAuthentication) {
mFingerprintHelper.startListening();
} else {
if (mFingerprintHelper.isListening()) {
mFingerprintHelper.stopListening();
}
}
if (isProfileChallenge()) {
updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(
mEffectiveUserId));
}
}
protected void setAccessibilityTitle(CharSequence supplementalText) {
Intent intent = getActivity().getIntent();
if (intent != null) {
CharSequence titleText = intent.getCharSequenceExtra(
ConfirmDeviceCredentialBaseFragment.TITLE_TEXT);
if (supplementalText == null) {
return;
}
if (titleText == null) {
getActivity().setTitle(supplementalText);
} else {
String accessibilityTitle =
new StringBuilder(titleText).append(",").append(supplementalText).toString();
getActivity().setTitle(Utils.createAccessibleSequence(titleText, accessibilityTitle));
}
}
}
@Override
public void 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();
@Override
public void onFingerprintIconVisibilityChanged(boolean visible) {
}
public void prepareEnterAnimation() {
}
public void startEnterAnimation() {
}
protected void checkForPendingIntent() {
int taskId = getActivity().getIntent().getIntExtra(Intent.EXTRA_TASK_ID, -1);
if (taskId != -1) {
try {
IActivityManager activityManager = ActivityManager.getService();
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchStackId(ActivityManager.StackId.INVALID_STACK_ID);
activityManager.startActivityFromRecents(taskId, options.toBundle());
return;
} catch (RemoteException e) {
// Do nothing.
}
}
IntentSender intentSender = getActivity().getIntent()
.getParcelableExtra(Intent.EXTRA_INTENT);
if (intentSender != null) {
try {
getActivity().startIntentSenderForResult(intentSender, -1, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
/* ignore */
}
}
}
private void setWorkChallengeBackground(View baseView, int userId) {
View mainContent = getActivity().findViewById(com.android.settings.R.id.main_content);
if (mainContent != null) {
// Remove the main content padding so that the background image is full screen.
mainContent.setPadding(0, 0, 0, 0);
}
DevicePolicyManager dpm = (DevicePolicyManager) getActivity().getSystemService(
Context.DEVICE_POLICY_SERVICE);
baseView.setBackground(new ColorDrawable(dpm.getOrganizationColorForUser(userId)));
ImageView imageView = (ImageView) baseView.findViewById(R.id.background_image);
if (imageView != null) {
Drawable image = getResources().getDrawable(R.drawable.work_challenge_background);
image.setColorFilter(
getResources().getColor(R.color.confirm_device_credential_transparent_black),
PorterDuff.Mode.DARKEN);
imageView.setImageDrawable(image);
Point screenSize = new Point();
getActivity().getWindowManager().getDefaultDisplay().getSize(screenSize);
imageView.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
screenSize.y));
}
}
protected boolean isProfileChallenge() {
return UserManager.get(getContext()).isManagedProfile(mEffectiveUserId);
}
protected void reportSuccessfullAttempt() {
if (isProfileChallenge()) {
mLockPatternUtils.reportSuccessfulPasswordAttempt(mEffectiveUserId);
// Keyguard is responsible to disable StrongAuth for primary user. Disable StrongAuth
// for work challenge only here.
mLockPatternUtils.userPresent(mEffectiveUserId);
}
}
protected void reportFailedAttempt() {
if (isProfileChallenge()) {
// + 1 for this attempt.
updateErrorMessage(
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
}
}
protected void updateErrorMessage(int numAttempts) {
final int maxAttempts =
mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId);
if (maxAttempts > 0 && numAttempts > 0) {
int remainingAttempts = maxAttempts - numAttempts;
if (remainingAttempts == 1) {
// Last try
final String title = getActivity().getString(
R.string.lock_profile_wipe_warning_title);
LastTryDialog.show(getFragmentManager(), title, getLastTryErrorMessage(),
android.R.string.ok, false /* dismiss */);
} else if (remainingAttempts <= 0) {
// Profile is wiped
LastTryDialog.show(getFragmentManager(), null /* title */,
R.string.lock_profile_wipe_content, R.string.lock_profile_wipe_dismiss,
true /* dismiss */);
}
if (mErrorTextView != null) {
final String message = getActivity().getString(R.string.lock_profile_wipe_attempts,
numAttempts, maxAttempts);
showError(message, 0);
}
}
}
protected abstract int getLastTryErrorMessage();
private final Runnable mResetErrorRunnable = new Runnable() {
@Override
public void run() {
mErrorTextView.setText("");
}
};
protected void showError(CharSequence msg, long timeout) {
mErrorTextView.setText(msg);
onShowError();
mHandler.removeCallbacks(mResetErrorRunnable);
if (timeout != 0) {
mHandler.postDelayed(mResetErrorRunnable, timeout);
}
}
protected abstract void onShowError();
protected void showError(int msg, long timeout) {
showError(getText(msg), timeout);
}
public static class LastTryDialog extends DialogFragment {
private static final String TAG = LastTryDialog.class.getSimpleName();
private static final String ARG_TITLE = "title";
private static final String ARG_MESSAGE = "message";
private static final String ARG_BUTTON = "button";
private static final String ARG_DISMISS = "dismiss";
static boolean show(FragmentManager from, String title, int message, int button,
boolean dismiss) {
LastTryDialog existent = (LastTryDialog) from.findFragmentByTag(TAG);
if (existent != null && !existent.isRemoving()) {
return false;
}
Bundle args = new Bundle();
args.putString(ARG_TITLE, title);
args.putInt(ARG_MESSAGE, message);
args.putInt(ARG_BUTTON, button);
args.putBoolean(ARG_DISMISS, dismiss);
DialogFragment dialog = new LastTryDialog();
dialog.setArguments(args);
dialog.show(from, TAG);
return true;
}
static void hide(FragmentManager from) {
LastTryDialog dialog = (LastTryDialog) from.findFragmentByTag(TAG);
if (dialog != null) {
dialog.dismissAllowingStateLoss();
from.executePendingTransactions();
}
}
/**
* Dialog setup.
* <p>
* To make it less likely that the dialog is dismissed accidentally, for example if the
* device is malfunctioning or if the device is in a pocket, we set
* {@code setCanceledOnTouchOutside(false)}.
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = new AlertDialog.Builder(getActivity())
.setTitle(getArguments().getString(ARG_TITLE))
.setMessage(getArguments().getInt(ARG_MESSAGE))
.setPositiveButton(getArguments().getInt(ARG_BUTTON), null)
.create();
dialog.setCanceledOnTouchOutside(false);
return dialog;
}
@Override
public void onDismiss(final DialogInterface dialog) {
super.onDismiss(dialog);
if (getActivity() != null && getArguments().getBoolean(ARG_DISMISS)) {
getActivity().finish();
}
}
}
}