Home | History | Annotate | Download | only in settings
      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;
     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.admin.DevicePolicyManager;
     29 import android.app.trust.TrustManager;
     30 import android.content.Context;
     31 import android.content.DialogInterface;
     32 import android.content.Intent;
     33 import android.content.IntentSender;
     34 import android.graphics.Point;
     35 import android.graphics.PorterDuff;
     36 import android.graphics.drawable.ColorDrawable;
     37 import android.graphics.drawable.Drawable;
     38 import android.os.Bundle;
     39 import android.os.Handler;
     40 import android.os.RemoteException;
     41 import android.os.UserManager;
     42 import android.view.View;
     43 import android.view.ViewGroup;
     44 import android.widget.Button;
     45 import android.widget.FrameLayout;
     46 import android.widget.ImageView;
     47 import android.widget.TextView;
     48 
     49 import com.android.internal.widget.LockPatternUtils;
     50 import com.android.settings.fingerprint.FingerprintUiHelper;
     51 
     52 /**
     53  * Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
     54  */
     55 public abstract class ConfirmDeviceCredentialBaseFragment extends OptionsMenuFragment
     56         implements FingerprintUiHelper.Callback {
     57 
     58     public static final String PACKAGE = "com.android.settings";
     59     public static final String TITLE_TEXT = PACKAGE + ".ConfirmCredentials.title";
     60     public static final String HEADER_TEXT = PACKAGE + ".ConfirmCredentials.header";
     61     public static final String DETAILS_TEXT = PACKAGE + ".ConfirmCredentials.details";
     62     public static final String ALLOW_FP_AUTHENTICATION =
     63             PACKAGE + ".ConfirmCredentials.allowFpAuthentication";
     64     public static final String DARK_THEME = PACKAGE + ".ConfirmCredentials.darkTheme";
     65     public static final String SHOW_CANCEL_BUTTON =
     66             PACKAGE + ".ConfirmCredentials.showCancelButton";
     67     public static final String SHOW_WHEN_LOCKED =
     68             PACKAGE + ".ConfirmCredentials.showWhenLocked";
     69 
     70     private FingerprintUiHelper mFingerprintHelper;
     71     protected boolean mReturnCredentials = false;
     72     protected Button mCancelButton;
     73     protected ImageView mFingerprintIcon;
     74     protected int mEffectiveUserId;
     75     protected int mUserId;
     76     protected UserManager mUserManager;
     77     protected LockPatternUtils mLockPatternUtils;
     78     protected TextView mErrorTextView;
     79     protected final Handler mHandler = new Handler();
     80 
     81     @Override
     82     public void onCreate(@Nullable Bundle savedInstanceState) {
     83         super.onCreate(savedInstanceState);
     84         mReturnCredentials = getActivity().getIntent().getBooleanExtra(
     85                 ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false);
     86         // Only take this argument into account if it belongs to the current profile.
     87         Intent intent = getActivity().getIntent();
     88         mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
     89         mUserManager = UserManager.get(getActivity());
     90         mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
     91         mLockPatternUtils = new LockPatternUtils(getActivity());
     92     }
     93 
     94     @Override
     95     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
     96         super.onViewCreated(view, savedInstanceState);
     97         mCancelButton = (Button) view.findViewById(R.id.cancelButton);
     98         mFingerprintIcon = (ImageView) view.findViewById(R.id.fingerprintIcon);
     99         mFingerprintHelper = new FingerprintUiHelper(
    100                 mFingerprintIcon,
    101                 (TextView) view.findViewById(R.id.errorText), this, mEffectiveUserId);
    102         boolean showCancelButton = getActivity().getIntent().getBooleanExtra(
    103                 SHOW_CANCEL_BUTTON, false);
    104         mCancelButton.setVisibility(showCancelButton ? View.VISIBLE : View.GONE);
    105         mCancelButton.setOnClickListener(new View.OnClickListener() {
    106             @Override
    107             public void onClick(View v) {
    108                 getActivity().finish();
    109             }
    110         });
    111         int credentialOwnerUserId = Utils.getCredentialOwnerUserId(
    112                 getActivity(),
    113                 Utils.getUserIdFromBundle(
    114                         getActivity(),
    115                         getActivity().getIntent().getExtras()));
    116         if (mUserManager.isManagedProfile(credentialOwnerUserId)) {
    117             setWorkChallengeBackground(view, credentialOwnerUserId);
    118         }
    119     }
    120 
    121     private boolean isFingerprintDisabledByAdmin() {
    122         DevicePolicyManager dpm = (DevicePolicyManager) getActivity().getSystemService(
    123                 Context.DEVICE_POLICY_SERVICE);
    124         final int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, mEffectiveUserId);
    125         return (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0;
    126     }
    127 
    128     // User could be locked while Effective user is unlocked even though the effective owns the
    129     // credential. Otherwise, fingerprint can't unlock fbe/keystore through
    130     // verifyTiedProfileChallenge. In such case, we also wanna show the user message that
    131     // fingerprint is disabled due to device restart.
    132     protected boolean isFingerprintDisallowedByStrongAuth() {
    133         return !(mLockPatternUtils.isFingerprintAllowedForUser(mEffectiveUserId)
    134                 && mUserManager.isUserUnlocked(mUserId));
    135     }
    136 
    137     private boolean isFingerprintAllowed() {
    138         return !mReturnCredentials
    139                 && getActivity().getIntent().getBooleanExtra(ALLOW_FP_AUTHENTICATION, false)
    140                 && !isFingerprintDisallowedByStrongAuth()
    141                 && !isFingerprintDisabledByAdmin();
    142     }
    143 
    144     @Override
    145     public void onResume() {
    146         super.onResume();
    147         refreshLockScreen();
    148     }
    149 
    150     protected void refreshLockScreen() {
    151         if (isFingerprintAllowed()) {
    152             mFingerprintHelper.startListening();
    153         } else {
    154             if (mFingerprintHelper.isListening()) {
    155                 mFingerprintHelper.stopListening();
    156             }
    157         }
    158         if (isProfileChallenge()) {
    159             updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(
    160                     mEffectiveUserId));
    161         }
    162     }
    163 
    164     protected void setAccessibilityTitle(CharSequence supplementalText) {
    165         Intent intent = getActivity().getIntent();
    166         if (intent != null) {
    167             CharSequence titleText = intent.getCharSequenceExtra(
    168                     ConfirmDeviceCredentialBaseFragment.TITLE_TEXT);
    169             if (supplementalText == null) {
    170                 return;
    171             }
    172             if (titleText == null) {
    173                 getActivity().setTitle(supplementalText);
    174             } else {
    175                 String accessibilityTitle =
    176                         new StringBuilder(titleText).append(",").append(supplementalText).toString();
    177                 getActivity().setTitle(Utils.createAccessibleSequence(titleText, accessibilityTitle));
    178             }
    179         }
    180     }
    181 
    182     @Override
    183     public void onPause() {
    184         super.onPause();
    185         if (mFingerprintHelper.isListening()) {
    186             mFingerprintHelper.stopListening();
    187         }
    188     }
    189 
    190     @Override
    191     public void onAuthenticated() {
    192         // Check whether we are still active.
    193         if (getActivity() != null && getActivity().isResumed()) {
    194             TrustManager trustManager =
    195                 (TrustManager) getActivity().getSystemService(Context.TRUST_SERVICE);
    196             trustManager.setDeviceLockedForUser(mEffectiveUserId, false);
    197             authenticationSucceeded();
    198             checkForPendingIntent();
    199         }
    200     }
    201 
    202     protected abstract void authenticationSucceeded();
    203 
    204     @Override
    205     public void onFingerprintIconVisibilityChanged(boolean visible) {
    206     }
    207 
    208     public void prepareEnterAnimation() {
    209     }
    210 
    211     public void startEnterAnimation() {
    212     }
    213 
    214     protected void checkForPendingIntent() {
    215         int taskId = getActivity().getIntent().getIntExtra(Intent.EXTRA_TASK_ID, -1);
    216         if (taskId != -1) {
    217             try {
    218                 IActivityManager activityManager = ActivityManager.getService();
    219                 final ActivityOptions options = ActivityOptions.makeBasic();
    220                 options.setLaunchStackId(ActivityManager.StackId.INVALID_STACK_ID);
    221                 activityManager.startActivityFromRecents(taskId, options.toBundle());
    222                 return;
    223             } catch (RemoteException e) {
    224                 // Do nothing.
    225             }
    226         }
    227         IntentSender intentSender = getActivity().getIntent()
    228                 .getParcelableExtra(Intent.EXTRA_INTENT);
    229         if (intentSender != null) {
    230             try {
    231                 getActivity().startIntentSenderForResult(intentSender, -1, null, 0, 0, 0);
    232             } catch (IntentSender.SendIntentException e) {
    233                 /* ignore */
    234             }
    235         }
    236     }
    237 
    238     private void setWorkChallengeBackground(View baseView, int userId) {
    239         View mainContent = getActivity().findViewById(com.android.settings.R.id.main_content);
    240         if (mainContent != null) {
    241             // Remove the main content padding so that the background image is full screen.
    242             mainContent.setPadding(0, 0, 0, 0);
    243         }
    244 
    245         DevicePolicyManager dpm = (DevicePolicyManager) getActivity().getSystemService(
    246                 Context.DEVICE_POLICY_SERVICE);
    247         baseView.setBackground(new ColorDrawable(dpm.getOrganizationColorForUser(userId)));
    248         ImageView imageView = (ImageView) baseView.findViewById(R.id.background_image);
    249         if (imageView != null) {
    250             Drawable image = getResources().getDrawable(R.drawable.work_challenge_background);
    251             image.setColorFilter(
    252                     getResources().getColor(R.color.confirm_device_credential_transparent_black),
    253                     PorterDuff.Mode.DARKEN);
    254             imageView.setImageDrawable(image);
    255             Point screenSize = new Point();
    256             getActivity().getWindowManager().getDefaultDisplay().getSize(screenSize);
    257             imageView.setLayoutParams(new FrameLayout.LayoutParams(
    258                     ViewGroup.LayoutParams.MATCH_PARENT,
    259                     screenSize.y));
    260         }
    261     }
    262 
    263     protected boolean isProfileChallenge() {
    264         return mUserManager.isManagedProfile(mEffectiveUserId);
    265     }
    266 
    267     protected void reportSuccessfullAttempt() {
    268         if (isProfileChallenge()) {
    269             mLockPatternUtils.reportSuccessfulPasswordAttempt(mEffectiveUserId);
    270             // Keyguard is responsible to disable StrongAuth for primary user. Disable StrongAuth
    271             // for work challenge only here.
    272             mLockPatternUtils.userPresent(mEffectiveUserId);
    273         }
    274     }
    275 
    276     protected void reportFailedAttempt() {
    277         if (isProfileChallenge()) {
    278             // + 1 for this attempt.
    279             updateErrorMessage(
    280                     mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
    281             mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
    282         }
    283     }
    284 
    285     protected void updateErrorMessage(int numAttempts) {
    286         final int maxAttempts =
    287                 mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId);
    288         if (maxAttempts > 0 && numAttempts > 0) {
    289             int remainingAttempts = maxAttempts - numAttempts;
    290             if (remainingAttempts == 1) {
    291                 // Last try
    292                 final String title = getActivity().getString(
    293                         R.string.lock_profile_wipe_warning_title);
    294                 LastTryDialog.show(getFragmentManager(), title, getLastTryErrorMessage(),
    295                         android.R.string.ok, false /* dismiss */);
    296             } else if (remainingAttempts <= 0) {
    297                 // Profile is wiped
    298                 LastTryDialog.show(getFragmentManager(), null /* title */,
    299                         R.string.lock_profile_wipe_content, R.string.lock_profile_wipe_dismiss,
    300                         true /* dismiss */);
    301             }
    302             if (mErrorTextView != null) {
    303                 final String message = getActivity().getString(R.string.lock_profile_wipe_attempts,
    304                         numAttempts, maxAttempts);
    305                 showError(message, 0);
    306             }
    307         }
    308     }
    309 
    310     protected abstract int getLastTryErrorMessage();
    311 
    312     private final Runnable mResetErrorRunnable = new Runnable() {
    313         @Override
    314         public void run() {
    315             mErrorTextView.setText("");
    316         }
    317     };
    318 
    319     protected void showError(CharSequence msg, long timeout) {
    320         mErrorTextView.setText(msg);
    321         onShowError();
    322         mHandler.removeCallbacks(mResetErrorRunnable);
    323         if (timeout != 0) {
    324             mHandler.postDelayed(mResetErrorRunnable, timeout);
    325         }
    326     }
    327 
    328     protected abstract void onShowError();
    329 
    330     protected void showError(int msg, long timeout) {
    331         showError(getText(msg), timeout);
    332     }
    333 
    334     public static class LastTryDialog extends DialogFragment {
    335         private static final String TAG = LastTryDialog.class.getSimpleName();
    336 
    337         private static final String ARG_TITLE = "title";
    338         private static final String ARG_MESSAGE = "message";
    339         private static final String ARG_BUTTON = "button";
    340         private static final String ARG_DISMISS = "dismiss";
    341 
    342         static boolean show(FragmentManager from, String title, int message, int button,
    343                 boolean dismiss) {
    344             LastTryDialog existent = (LastTryDialog) from.findFragmentByTag(TAG);
    345             if (existent != null && !existent.isRemoving()) {
    346                 return false;
    347             }
    348             Bundle args = new Bundle();
    349             args.putString(ARG_TITLE, title);
    350             args.putInt(ARG_MESSAGE, message);
    351             args.putInt(ARG_BUTTON, button);
    352             args.putBoolean(ARG_DISMISS, dismiss);
    353 
    354             DialogFragment dialog = new LastTryDialog();
    355             dialog.setArguments(args);
    356             dialog.show(from, TAG);
    357             return true;
    358         }
    359 
    360         static void hide(FragmentManager from) {
    361             LastTryDialog dialog = (LastTryDialog) from.findFragmentByTag(TAG);
    362             if (dialog != null) {
    363                 dialog.dismissAllowingStateLoss();
    364                 from.executePendingTransactions();
    365             }
    366         }
    367 
    368         /**
    369          * Dialog setup.
    370          * <p>
    371          * To make it less likely that the dialog is dismissed accidentally, for example if the
    372          * device is malfunctioning or if the device is in a pocket, we set
    373          * {@code setCanceledOnTouchOutside(false)}.
    374          */
    375         @Override
    376         public Dialog onCreateDialog(Bundle savedInstanceState) {
    377             Dialog dialog = new AlertDialog.Builder(getActivity())
    378                     .setTitle(getArguments().getString(ARG_TITLE))
    379                     .setMessage(getArguments().getInt(ARG_MESSAGE))
    380                     .setPositiveButton(getArguments().getInt(ARG_BUTTON), null)
    381                     .create();
    382             dialog.setCanceledOnTouchOutside(false);
    383             return dialog;
    384         }
    385 
    386         @Override
    387         public void onDismiss(final DialogInterface dialog) {
    388             super.onDismiss(dialog);
    389             if (getActivity() != null && getArguments().getBoolean(ARG_DISMISS)) {
    390                 getActivity().finish();
    391             }
    392         }
    393     }
    394 }
    395