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