Files
app_Settings/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
Doris Ling ed4685fafb Update activity titles for fragments without preference screen.
1. Move getPreferenceScreenResId() from individual subclass to
InstrumentedPreferenceFragment.

2. Removed InstrumentedPreferenceFragment.getTitle() and let the
preference fragments that do not have preference screen set the activity
title directly instead.

3. Removed OptionsMenuFragment as all it does is call
setHasOptionMenu().
- changed subclasses of OptionsMenuFragment to extend from
InstrumentedPreferenceFragment directly.
- none of the exisitng subclasses actually implements the option menu
related methods to provide any option menu. So, the setHasOptionMenu()
call is not added to the subclasses.

4. Update Languages preference title.
- launch the fragment from the preference controller instead of from the
default handling, as we need the title res id at launch time to get it
work properly when retrieving the title from back stack.

Bug: 64564191
Test: blaze-bin/screenshots/android/i18nscreenshots/i18nscreenshots
Change-Id: Ibecdcab32cbaed8bf604ec5ebe0a926b4e489a7d
2017-10-26 12:01:06 -07:00

449 lines
18 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.password;
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.KeyguardManager;
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.content.pm.UserInfo;
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.text.TextUtils;
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.R;
import com.android.settings.Utils;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.fingerprint.FingerprintUiHelper;
/**
* Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
*/
public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedPreferenceFragment
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";
protected static final int USER_TYPE_PRIMARY = 1;
protected static final int USER_TYPE_MANAGED_PROFILE = 2;
protected static final int USER_TYPE_SECONDARY = 3;
/** 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;
private FingerprintUiHelper mFingerprintHelper;
protected boolean mReturnCredentials = false;
protected Button mCancelButton;
protected ImageView mFingerprintIcon;
protected int mEffectiveUserId;
protected int mUserId;
protected UserManager mUserManager;
protected LockPatternUtils mLockPatternUtils;
protected DevicePolicyManager mDevicePolicyManager;
protected TextView mErrorTextView;
protected final Handler mHandler = new Handler();
protected boolean mFrp;
private CharSequence mFrpAlternateButtonText;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFrpAlternateButtonText = getActivity().getIntent().getCharSequenceExtra(
KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
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());
mFrp = (mUserId == LockPatternUtils.USER_FRP);
mUserManager = UserManager.get(getActivity());
mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
mLockPatternUtils = new LockPatternUtils(getActivity());
mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService(
Context.DEVICE_POLICY_SERVICE);
}
@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);
boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText);
mCancelButton.setVisibility(showCancelButton || hasAlternateButton
? View.VISIBLE : View.GONE);
if (hasAlternateButton) {
mCancelButton.setText(mFrpAlternateButtonText);
}
mCancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (hasAlternateButton) {
getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
}
getActivity().finish();
}
});
int credentialOwnerUserId = Utils.getCredentialOwnerUserId(
getActivity(),
Utils.getUserIdFromBundle(
getActivity(),
getActivity().getIntent().getExtras()));
if (mUserManager.isManagedProfile(credentialOwnerUserId)) {
setWorkChallengeBackground(view, credentialOwnerUserId);
}
}
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
// 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.
protected boolean isStrongAuthRequired() {
return mFrp
|| !mLockPatternUtils.isFingerprintAllowedForUser(mEffectiveUserId)
|| !mUserManager.isUserUnlocked(mUserId);
}
private boolean isFingerprintAllowed() {
return !mReturnCredentials
&& getActivity().getIntent().getBooleanExtra(ALLOW_FP_AUTHENTICATION, false)
&& !isStrongAuthRequired()
&& !isFingerprintDisabledByAdmin();
}
@Override
public void onResume() {
super.onResume();
refreshLockScreen();
}
protected void refreshLockScreen() {
if (isFingerprintAllowed()) {
mFingerprintHelper.startListening();
} else {
if (mFingerprintHelper.isListening()) {
mFingerprintHelper.stopListening();
}
}
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();
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);
}
baseView.setBackground(
new ColorDrawable(mDevicePolicyManager.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 void reportSuccessfulAttempt() {
mLockPatternUtils.reportSuccessfulPasswordAttempt(mEffectiveUserId);
if (mUserManager.isManagedProfile(mEffectiveUserId)) {
// Keyguard is responsible to disable StrongAuth for primary user. Disable StrongAuth
// for work challenge only here.
mLockPatternUtils.userPresent(mEffectiveUserId);
}
}
protected void reportFailedAttempt() {
updateErrorMessage(
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
}
protected void updateErrorMessage(int numAttempts) {
final int maxAttempts =
mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId);
if (maxAttempts <= 0 || numAttempts <= 0) {
return;
}
// Update the on-screen error string
if (mErrorTextView != null) {
final String message = getActivity().getString(
R.string.lock_failed_attempts_before_wipe, numAttempts, maxAttempts);
showError(message, 0);
}
// Only show popup dialog before the last attempt and before wipe
final int remainingAttempts = maxAttempts - numAttempts;
if (remainingAttempts > 1) {
return;
}
final FragmentManager fragmentManager = getChildFragmentManager();
final int userType = getUserTypeForWipe();
if (remainingAttempts == 1) {
// Last try
final String title = getActivity().getString(
R.string.lock_last_attempt_before_wipe_warning_title);
final int messageId = getLastTryErrorMessage(userType);
LastTryDialog.show(fragmentManager, title, messageId,
android.R.string.ok, false /* dismiss */);
} else {
// Device, profile, or secondary user is wiped
final int messageId = getWipeMessage(userType);
LastTryDialog.show(fragmentManager, null /* title */, messageId,
R.string.lock_failed_attempts_now_wiping_dialog_dismiss, true /* dismiss */);
}
}
private int getUserTypeForWipe() {
final UserInfo userToBeWiped = mUserManager.getUserInfo(
mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
if (userToBeWiped == null || userToBeWiped.isPrimary()) {
return USER_TYPE_PRIMARY;
} else if (userToBeWiped.isManagedProfile()) {
return USER_TYPE_MANAGED_PROFILE;
} else {
return USER_TYPE_SECONDARY;
}
}
protected abstract int getLastTryErrorMessage(int userType);
private int getWipeMessage(int userType) {
switch (userType) {
case USER_TYPE_PRIMARY:
return R.string.lock_failed_attempts_now_wiping_device;
case USER_TYPE_MANAGED_PROFILE:
return R.string.lock_failed_attempts_now_wiping_profile;
case USER_TYPE_SECONDARY:
return R.string.lock_failed_attempts_now_wiping_user;
default:
throw new IllegalArgumentException("Unrecognized user type:" + userType);
}
}
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);
from.executePendingTransactions();
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();
}
}
}
}