Files
packages_apps_Settings/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
Robin Lee c1cb8a4c2d Don't show multiple wipe warning dialogs
Stacking them on top of each other like that does not count as defence
in depth and we should not do it.

Bug: 32934848
Test: make RunSettingsRoboTests # added one specifically for this
Change-Id: I9490652c05a630e7c3f9164a9144fadfc0f41ea1
2017-02-09 19:33:00 +00:00

387 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();
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(getArguments().getString(ARG_TITLE))
.setMessage(getArguments().getInt(ARG_MESSAGE))
.setPositiveButton(getArguments().getInt(ARG_BUTTON), null)
.create();
}
@Override
public void onDismiss(final DialogInterface dialog) {
super.onDismiss(dialog);
if (getActivity() != null && getArguments().getBoolean(ARG_DISMISS)) {
getActivity().finish();
}
}
}
}