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             authenticationSucceeded();
    193             checkForPendingIntent();
    194         }
    195     }
    196 
    197     protected abstract void authenticationSucceeded();
    198 
    199     @Override
    200     public void onFingerprintIconVisibilityChanged(boolean visible) {
    201     }
    202 
    203     public void prepareEnterAnimation() {
    204     }
    205 
    206     public void startEnterAnimation() {
    207     }
    208 
    209     protected void checkForPendingIntent() {
    210         int taskId = getActivity().getIntent().getIntExtra(Intent.EXTRA_TASK_ID, -1);
    211         if (taskId != -1) {
    212             try {
    213                 IActivityManager activityManager = ActivityManagerNative.getDefault();
    214                 final ActivityOptions options = ActivityOptions.makeBasic();
    215                 options.setLaunchStackId(ActivityManager.StackId.INVALID_STACK_ID);
    216                 activityManager.startActivityFromRecents(taskId, options.toBundle());
    217                 return;
    218             } catch (RemoteException e) {
    219                 // Do nothing.
    220             }
    221         }
    222         IntentSender intentSender = getActivity().getIntent()
    223                 .getParcelableExtra(Intent.EXTRA_INTENT);
    224         if (intentSender != null) {
    225             try {
    226                 getActivity().startIntentSenderForResult(intentSender, -1, null, 0, 0, 0);
    227             } catch (IntentSender.SendIntentException e) {
    228                 /* ignore */
    229             }
    230         }
    231     }
    232 
    233     private void setWorkChallengeBackground(View baseView, int userId) {
    234         View mainContent = getActivity().findViewById(com.android.settings.R.id.main_content);
    235         if (mainContent != null) {
    236             // Remove the main content padding so that the background image is full screen.
    237             mainContent.setPadding(0, 0, 0, 0);
    238         }
    239 
    240         DevicePolicyManager dpm = (DevicePolicyManager) getActivity().getSystemService(
    241                 Context.DEVICE_POLICY_SERVICE);
    242         baseView.setBackground(new ColorDrawable(dpm.getOrganizationColorForUser(userId)));
    243         ImageView imageView = (ImageView) baseView.findViewById(R.id.background_image);
    244         if (imageView != null) {
    245             Drawable image = getResources().getDrawable(R.drawable.work_challenge_background);
    246             image.setColorFilter(
    247                     getResources().getColor(R.color.confirm_device_credential_transparent_black),
    248                     PorterDuff.Mode.DARKEN);
    249             imageView.setImageDrawable(image);
    250             Point screenSize = new Point();
    251             getActivity().getWindowManager().getDefaultDisplay().getSize(screenSize);
    252             imageView.setLayoutParams(new FrameLayout.LayoutParams(
    253                     ViewGroup.LayoutParams.MATCH_PARENT,
    254                     screenSize.y));
    255         }
    256     }
    257 
    258     protected boolean isProfileChallenge() {
    259         return Utils.isManagedProfile(UserManager.get(getContext()), mEffectiveUserId);
    260     }
    261 
    262     protected void reportSuccessfullAttempt() {
    263         if (isProfileChallenge()) {
    264             mLockPatternUtils.reportSuccessfulPasswordAttempt(mEffectiveUserId);
    265             // Keyguard is responsible to disable StrongAuth for primary user. Disable StrongAuth
    266             // for work challenge only here.
    267             mLockPatternUtils.userPresent(mEffectiveUserId);
    268         }
    269     }
    270 
    271     protected void reportFailedAttempt() {
    272         if (isProfileChallenge()) {
    273             // + 1 for this attempt.
    274             updateErrorMessage(
    275                     mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
    276             mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
    277         }
    278     }
    279 
    280     protected void updateErrorMessage(int numAttempts) {
    281         final int maxAttempts =
    282                 mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId);
    283         if (maxAttempts > 0 && numAttempts > 0) {
    284             int remainingAttempts = maxAttempts - numAttempts;
    285             if (remainingAttempts == 1) {
    286                 // Last try
    287                 final String title = getActivity().getString(
    288                         R.string.lock_profile_wipe_warning_title);
    289                 final String message = getActivity().getString(getLastTryErrorMessage());
    290                 showDialog(title, message, android.R.string.ok, false /* dismiss */);
    291             } else if (remainingAttempts <= 0) {
    292                 // Profile is wiped
    293                 final String message = getActivity().getString(R.string.lock_profile_wipe_content);
    294                 showDialog(null, message, R.string.lock_profile_wipe_dismiss, true /* dismiss */);
    295             }
    296             if (mErrorTextView != null) {
    297                 final String message = getActivity().getString(R.string.lock_profile_wipe_attempts,
    298                         numAttempts, maxAttempts);
    299                 showError(message, 0);
    300             }
    301         }
    302     }
    303 
    304     protected abstract int getLastTryErrorMessage();
    305 
    306     private final Runnable mResetErrorRunnable = new Runnable() {
    307         @Override
    308         public void run() {
    309             mErrorTextView.setText("");
    310         }
    311     };
    312 
    313     protected void showError(CharSequence msg, long timeout) {
    314         mErrorTextView.setText(msg);
    315         onShowError();
    316         mHandler.removeCallbacks(mResetErrorRunnable);
    317         if (timeout != 0) {
    318             mHandler.postDelayed(mResetErrorRunnable, timeout);
    319         }
    320     }
    321 
    322     protected abstract void onShowError();
    323 
    324     protected void showError(int msg, long timeout) {
    325         showError(getText(msg), timeout);
    326     }
    327 
    328     private void showDialog(String title, String message, int buttonString, final boolean dismiss) {
    329         final AlertDialog dialog = new AlertDialog.Builder(getActivity())
    330             .setTitle(title)
    331             .setMessage(message)
    332             .setPositiveButton(buttonString, new OnClickListener() {
    333                 @Override
    334                 public void onClick(DialogInterface dialog, int which) {
    335                     if (dismiss) {
    336                         getActivity().finish();
    337                     }
    338                 }
    339             })
    340             .create();
    341         dialog.show();
    342     }
    343 }
    344