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