Home | History | Annotate | Download | only in password
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 // TODO (b/35202196): move this class out of the root of the package.
     18 package com.android.settings.password;
     19 
     20 import android.annotation.Nullable;
     21 import android.app.ActivityManager;
     22 import android.app.ActivityOptions;
     23 import android.app.AlertDialog;
     24 import android.app.Dialog;
     25 import android.app.DialogFragment;
     26 import android.app.FragmentManager;
     27 import android.app.IActivityManager;
     28 import android.app.KeyguardManager;
     29 import android.app.admin.DevicePolicyManager;
     30 import android.app.trust.TrustManager;
     31 import android.content.Context;
     32 import android.content.DialogInterface;
     33 import android.content.Intent;
     34 import android.content.IntentSender;
     35 import android.content.pm.UserInfo;
     36 import android.graphics.Point;
     37 import android.graphics.PorterDuff;
     38 import android.graphics.drawable.ColorDrawable;
     39 import android.graphics.drawable.Drawable;
     40 import android.os.Bundle;
     41 import android.os.Handler;
     42 import android.os.RemoteException;
     43 import android.os.UserManager;
     44 import android.text.TextUtils;
     45 import android.view.View;
     46 import android.view.ViewGroup;
     47 import android.widget.Button;
     48 import android.widget.FrameLayout;
     49 import android.widget.ImageView;
     50 import android.widget.TextView;
     51 
     52 import com.android.internal.widget.LockPatternUtils;
     53 import com.android.settings.OptionsMenuFragment;
     54 import com.android.settings.R;
     55 import com.android.settings.Utils;
     56 import com.android.settings.fingerprint.FingerprintUiHelper;
     57 
     58 /**
     59  * Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
     60  */
     61 public abstract class ConfirmDeviceCredentialBaseFragment extends OptionsMenuFragment
     62         implements FingerprintUiHelper.Callback {
     63 
     64     public static final String PACKAGE = "com.android.settings";
     65     public static final String TITLE_TEXT = PACKAGE + ".ConfirmCredentials.title";
     66     public static final String HEADER_TEXT = PACKAGE + ".ConfirmCredentials.header";
     67     public static final String DETAILS_TEXT = PACKAGE + ".ConfirmCredentials.details";
     68     public static final String ALLOW_FP_AUTHENTICATION =
     69             PACKAGE + ".ConfirmCredentials.allowFpAuthentication";
     70     public static final String DARK_THEME = PACKAGE + ".ConfirmCredentials.darkTheme";
     71     public static final String SHOW_CANCEL_BUTTON =
     72             PACKAGE + ".ConfirmCredentials.showCancelButton";
     73     public static final String SHOW_WHEN_LOCKED =
     74             PACKAGE + ".ConfirmCredentials.showWhenLocked";
     75 
     76     protected static final int USER_TYPE_PRIMARY = 1;
     77     protected static final int USER_TYPE_MANAGED_PROFILE = 2;
     78     protected static final int USER_TYPE_SECONDARY = 3;
     79 
     80     /** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */
     81     protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000;
     82 
     83     private FingerprintUiHelper mFingerprintHelper;
     84     protected boolean mReturnCredentials = false;
     85     protected Button mCancelButton;
     86     protected ImageView mFingerprintIcon;
     87     protected int mEffectiveUserId;
     88     protected int mUserId;
     89     protected UserManager mUserManager;
     90     protected LockPatternUtils mLockPatternUtils;
     91     protected DevicePolicyManager mDevicePolicyManager;
     92     protected TextView mErrorTextView;
     93     protected final Handler mHandler = new Handler();
     94     protected boolean mFrp;
     95     private CharSequence mFrpAlternateButtonText;
     96 
     97     @Override
     98     public void onCreate(@Nullable Bundle savedInstanceState) {
     99         super.onCreate(savedInstanceState);
    100         mFrpAlternateButtonText = getActivity().getIntent().getCharSequenceExtra(
    101                 KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
    102         mReturnCredentials = getActivity().getIntent().getBooleanExtra(
    103                 ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false);
    104         // Only take this argument into account if it belongs to the current profile.
    105         Intent intent = getActivity().getIntent();
    106         mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
    107         mFrp = (mUserId == LockPatternUtils.USER_FRP);
    108         mUserManager = UserManager.get(getActivity());
    109         mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
    110         mLockPatternUtils = new LockPatternUtils(getActivity());
    111         mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService(
    112                 Context.DEVICE_POLICY_SERVICE);
    113     }
    114 
    115     @Override
    116     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    117         super.onViewCreated(view, savedInstanceState);
    118         mCancelButton = (Button) view.findViewById(R.id.cancelButton);
    119         mFingerprintIcon = (ImageView) view.findViewById(R.id.fingerprintIcon);
    120         mFingerprintHelper = new FingerprintUiHelper(
    121                 mFingerprintIcon,
    122                 (TextView) view.findViewById(R.id.errorText), this, mEffectiveUserId);
    123         boolean showCancelButton = getActivity().getIntent().getBooleanExtra(
    124                 SHOW_CANCEL_BUTTON, false);
    125         boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText);
    126         mCancelButton.setVisibility(showCancelButton || hasAlternateButton
    127                 ? View.VISIBLE : View.GONE);
    128         if (hasAlternateButton) {
    129             mCancelButton.setText(mFrpAlternateButtonText);
    130         }
    131         mCancelButton.setOnClickListener(new View.OnClickListener() {
    132             @Override
    133             public void onClick(View v) {
    134                 if (hasAlternateButton) {
    135                     getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
    136                 }
    137                 getActivity().finish();
    138             }
    139         });
    140         int credentialOwnerUserId = Utils.getCredentialOwnerUserId(
    141                 getActivity(),
    142                 Utils.getUserIdFromBundle(
    143                         getActivity(),
    144                         getActivity().getIntent().getExtras()));
    145         if (mUserManager.isManagedProfile(credentialOwnerUserId)) {
    146             setWorkChallengeBackground(view, credentialOwnerUserId);
    147         }
    148     }
    149 
    150     private boolean isFingerprintDisabledByAdmin() {
    151         final int disabledFeatures =
    152                 mDevicePolicyManager.getKeyguardDisabledFeatures(null, mEffectiveUserId);
    153         return (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0;
    154     }
    155 
    156     // User could be locked while Effective user is unlocked even though the effective owns the
    157     // credential. Otherwise, fingerprint can't unlock fbe/keystore through
    158     // verifyTiedProfileChallenge. In such case, we also wanna show the user message that
    159     // fingerprint is disabled due to device restart.
    160     protected boolean isStrongAuthRequired() {
    161         return mFrp
    162                 || !mLockPatternUtils.isFingerprintAllowedForUser(mEffectiveUserId)
    163                 || !mUserManager.isUserUnlocked(mUserId);
    164     }
    165 
    166     private boolean isFingerprintAllowed() {
    167         return !mReturnCredentials
    168                 && getActivity().getIntent().getBooleanExtra(ALLOW_FP_AUTHENTICATION, false)
    169                 && !isStrongAuthRequired()
    170                 && !isFingerprintDisabledByAdmin();
    171     }
    172 
    173     @Override
    174     public void onResume() {
    175         super.onResume();
    176         refreshLockScreen();
    177     }
    178 
    179     protected void refreshLockScreen() {
    180         if (isFingerprintAllowed()) {
    181             mFingerprintHelper.startListening();
    182         } else {
    183             if (mFingerprintHelper.isListening()) {
    184                 mFingerprintHelper.stopListening();
    185             }
    186         }
    187         updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
    188     }
    189 
    190     protected void setAccessibilityTitle(CharSequence supplementalText) {
    191         Intent intent = getActivity().getIntent();
    192         if (intent != null) {
    193             CharSequence titleText = intent.getCharSequenceExtra(
    194                     ConfirmDeviceCredentialBaseFragment.TITLE_TEXT);
    195             if (supplementalText == null) {
    196                 return;
    197             }
    198             if (titleText == null) {
    199                 getActivity().setTitle(supplementalText);
    200             } else {
    201                 String accessibilityTitle =
    202                         new StringBuilder(titleText).append(",").append(supplementalText).toString();
    203                 getActivity().setTitle(Utils.createAccessibleSequence(titleText, accessibilityTitle));
    204             }
    205         }
    206     }
    207 
    208     @Override
    209     public void onPause() {
    210         super.onPause();
    211         if (mFingerprintHelper.isListening()) {
    212             mFingerprintHelper.stopListening();
    213         }
    214     }
    215 
    216     @Override
    217     public void onAuthenticated() {
    218         // Check whether we are still active.
    219         if (getActivity() != null && getActivity().isResumed()) {
    220             TrustManager trustManager =
    221                 (TrustManager) getActivity().getSystemService(Context.TRUST_SERVICE);
    222             trustManager.setDeviceLockedForUser(mEffectiveUserId, false);
    223             authenticationSucceeded();
    224             checkForPendingIntent();
    225         }
    226     }
    227 
    228     protected abstract void authenticationSucceeded();
    229 
    230     @Override
    231     public void onFingerprintIconVisibilityChanged(boolean visible) {
    232     }
    233 
    234     public void prepareEnterAnimation() {
    235     }
    236 
    237     public void startEnterAnimation() {
    238     }
    239 
    240     protected void checkForPendingIntent() {
    241         int taskId = getActivity().getIntent().getIntExtra(Intent.EXTRA_TASK_ID, -1);
    242         if (taskId != -1) {
    243             try {
    244                 IActivityManager activityManager = ActivityManager.getService();
    245                 final ActivityOptions options = ActivityOptions.makeBasic();
    246                 options.setLaunchStackId(ActivityManager.StackId.INVALID_STACK_ID);
    247                 activityManager.startActivityFromRecents(taskId, options.toBundle());
    248                 return;
    249             } catch (RemoteException e) {
    250                 // Do nothing.
    251             }
    252         }
    253         IntentSender intentSender = getActivity().getIntent()
    254                 .getParcelableExtra(Intent.EXTRA_INTENT);
    255         if (intentSender != null) {
    256             try {
    257                 getActivity().startIntentSenderForResult(intentSender, -1, null, 0, 0, 0);
    258             } catch (IntentSender.SendIntentException e) {
    259                 /* ignore */
    260             }
    261         }
    262     }
    263 
    264     private void setWorkChallengeBackground(View baseView, int userId) {
    265         View mainContent = getActivity().findViewById(com.android.settings.R.id.main_content);
    266         if (mainContent != null) {
    267             // Remove the main content padding so that the background image is full screen.
    268             mainContent.setPadding(0, 0, 0, 0);
    269         }
    270 
    271         baseView.setBackground(
    272                 new ColorDrawable(mDevicePolicyManager.getOrganizationColorForUser(userId)));
    273         ImageView imageView = (ImageView) baseView.findViewById(R.id.background_image);
    274         if (imageView != null) {
    275             Drawable image = getResources().getDrawable(R.drawable.work_challenge_background);
    276             image.setColorFilter(
    277                     getResources().getColor(R.color.confirm_device_credential_transparent_black),
    278                     PorterDuff.Mode.DARKEN);
    279             imageView.setImageDrawable(image);
    280             Point screenSize = new Point();
    281             getActivity().getWindowManager().getDefaultDisplay().getSize(screenSize);
    282             imageView.setLayoutParams(new FrameLayout.LayoutParams(
    283                     ViewGroup.LayoutParams.MATCH_PARENT,
    284                     screenSize.y));
    285         }
    286     }
    287 
    288     protected void reportSuccessfulAttempt() {
    289         mLockPatternUtils.reportSuccessfulPasswordAttempt(mEffectiveUserId);
    290         if (mUserManager.isManagedProfile(mEffectiveUserId)) {
    291             // Keyguard is responsible to disable StrongAuth for primary user. Disable StrongAuth
    292             // for work challenge only here.
    293             mLockPatternUtils.userPresent(mEffectiveUserId);
    294         }
    295     }
    296 
    297     protected void reportFailedAttempt() {
    298         updateErrorMessage(
    299                 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
    300         mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
    301     }
    302 
    303     protected void updateErrorMessage(int numAttempts) {
    304         final int maxAttempts =
    305                 mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId);
    306         if (maxAttempts <= 0 || numAttempts <= 0) {
    307             return;
    308         }
    309 
    310         // Update the on-screen error string
    311         if (mErrorTextView != null) {
    312             final String message = getActivity().getString(
    313                     R.string.lock_failed_attempts_before_wipe, numAttempts, maxAttempts);
    314             showError(message, 0);
    315         }
    316 
    317         // Only show popup dialog before the last attempt and before wipe
    318         final int remainingAttempts = maxAttempts - numAttempts;
    319         if (remainingAttempts > 1) {
    320             return;
    321         }
    322         final FragmentManager fragmentManager = getChildFragmentManager();
    323         final int userType = getUserTypeForWipe();
    324         if (remainingAttempts == 1) {
    325             // Last try
    326             final String title = getActivity().getString(
    327                     R.string.lock_last_attempt_before_wipe_warning_title);
    328             final int messageId = getLastTryErrorMessage(userType);
    329             LastTryDialog.show(fragmentManager, title, messageId,
    330                     android.R.string.ok, false /* dismiss */);
    331         } else {
    332             // Device, profile, or secondary user is wiped
    333             final int messageId = getWipeMessage(userType);
    334             LastTryDialog.show(fragmentManager, null /* title */, messageId,
    335                     R.string.lock_failed_attempts_now_wiping_dialog_dismiss, true /* dismiss */);
    336         }
    337     }
    338 
    339     private int getUserTypeForWipe() {
    340         final UserInfo userToBeWiped = mUserManager.getUserInfo(
    341                 mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
    342         if (userToBeWiped == null || userToBeWiped.isPrimary()) {
    343             return USER_TYPE_PRIMARY;
    344         } else if (userToBeWiped.isManagedProfile()) {
    345             return USER_TYPE_MANAGED_PROFILE;
    346         } else {
    347             return USER_TYPE_SECONDARY;
    348         }
    349     }
    350 
    351     protected abstract int getLastTryErrorMessage(int userType);
    352 
    353     private int getWipeMessage(int userType) {
    354         switch (userType) {
    355             case USER_TYPE_PRIMARY:
    356                 return R.string.lock_failed_attempts_now_wiping_device;
    357             case USER_TYPE_MANAGED_PROFILE:
    358                 return R.string.lock_failed_attempts_now_wiping_profile;
    359             case USER_TYPE_SECONDARY:
    360                 return R.string.lock_failed_attempts_now_wiping_user;
    361             default:
    362                 throw new IllegalArgumentException("Unrecognized user type:" + userType);
    363         }
    364     }
    365 
    366     private final Runnable mResetErrorRunnable = new Runnable() {
    367         @Override
    368         public void run() {
    369             mErrorTextView.setText("");
    370         }
    371     };
    372 
    373     protected void showError(CharSequence msg, long timeout) {
    374         mErrorTextView.setText(msg);
    375         onShowError();
    376         mHandler.removeCallbacks(mResetErrorRunnable);
    377         if (timeout != 0) {
    378             mHandler.postDelayed(mResetErrorRunnable, timeout);
    379         }
    380     }
    381 
    382     protected abstract void onShowError();
    383 
    384     protected void showError(int msg, long timeout) {
    385         showError(getText(msg), timeout);
    386     }
    387 
    388     public static class LastTryDialog extends DialogFragment {
    389         private static final String TAG = LastTryDialog.class.getSimpleName();
    390 
    391         private static final String ARG_TITLE = "title";
    392         private static final String ARG_MESSAGE = "message";
    393         private static final String ARG_BUTTON = "button";
    394         private static final String ARG_DISMISS = "dismiss";
    395 
    396         static boolean show(FragmentManager from, String title, int message, int button,
    397                 boolean dismiss) {
    398             LastTryDialog existent = (LastTryDialog) from.findFragmentByTag(TAG);
    399             if (existent != null && !existent.isRemoving()) {
    400                 return false;
    401             }
    402             Bundle args = new Bundle();
    403             args.putString(ARG_TITLE, title);
    404             args.putInt(ARG_MESSAGE, message);
    405             args.putInt(ARG_BUTTON, button);
    406             args.putBoolean(ARG_DISMISS, dismiss);
    407 
    408             DialogFragment dialog = new LastTryDialog();
    409             dialog.setArguments(args);
    410             dialog.show(from, TAG);
    411             from.executePendingTransactions();
    412             return true;
    413         }
    414 
    415         static void hide(FragmentManager from) {
    416             LastTryDialog dialog = (LastTryDialog) from.findFragmentByTag(TAG);
    417             if (dialog != null) {
    418                 dialog.dismissAllowingStateLoss();
    419                 from.executePendingTransactions();
    420             }
    421         }
    422 
    423         /**
    424          * Dialog setup.
    425          * <p>
    426          * To make it less likely that the dialog is dismissed accidentally, for example if the
    427          * device is malfunctioning or if the device is in a pocket, we set
    428          * {@code setCanceledOnTouchOutside(false)}.
    429          */
    430         @Override
    431         public Dialog onCreateDialog(Bundle savedInstanceState) {
    432             Dialog dialog = new AlertDialog.Builder(getActivity())
    433                     .setTitle(getArguments().getString(ARG_TITLE))
    434                     .setMessage(getArguments().getInt(ARG_MESSAGE))
    435                     .setPositiveButton(getArguments().getInt(ARG_BUTTON), null)
    436                     .create();
    437             dialog.setCanceledOnTouchOutside(false);
    438             return dialog;
    439         }
    440 
    441         @Override
    442         public void onDismiss(final DialogInterface dialog) {
    443             super.onDismiss(dialog);
    444             if (getActivity() != null && getArguments().getBoolean(ARG_DISMISS)) {
    445                 getActivity().finish();
    446             }
    447         }
    448     }
    449 }
    450