• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static android.app.Activity.RESULT_FIRST_USER;
21 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED;
22 
23 import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
24 
25 import android.annotation.Nullable;
26 import android.app.Dialog;
27 import android.app.KeyguardManager;
28 import android.app.RemoteLockscreenValidationSession;
29 import android.app.admin.DevicePolicyManager;
30 import android.app.admin.ManagedSubscriptionsPolicy;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.DialogInterface;
34 import android.content.Intent;
35 import android.content.pm.UserInfo;
36 import android.hardware.biometrics.BiometricManager;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.os.UserHandle;
40 import android.os.UserManager;
41 import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
42 import android.telecom.TelecomManager;
43 import android.text.TextUtils;
44 import android.util.FeatureFlagUtils;
45 import android.util.Log;
46 import android.view.View;
47 import android.widget.Button;
48 import android.widget.CheckBox;
49 import android.widget.TextView;
50 
51 import androidx.appcompat.app.AlertDialog;
52 import androidx.fragment.app.DialogFragment;
53 import androidx.fragment.app.FragmentManager;
54 
55 import com.android.internal.widget.LockPatternUtils;
56 import com.android.internal.widget.LockscreenCredential;
57 import com.android.settings.R;
58 import com.android.settings.Utils;
59 import com.android.settings.core.InstrumentedFragment;
60 
61 import com.google.android.setupdesign.GlifLayout;
62 
63 /**
64  * Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
65  */
66 public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFragment {
67     public static final String TAG = ConfirmDeviceCredentialBaseFragment.class.getSimpleName();
68     public static final String TITLE_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.title";
69     public static final String HEADER_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.header";
70     public static final String DETAILS_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.details";
71     public static final String DARK_THEME = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.darkTheme";
72     public static final String SHOW_CANCEL_BUTTON =
73             SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showCancelButton";
74     public static final String SHOW_WHEN_LOCKED =
75             SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showWhenLocked";
76     public static final String USE_FADE_ANIMATION =
77             SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.useFadeAnimation";
78     public static final String IS_REMOTE_LOCKSCREEN_VALIDATION =
79             SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.isRemoteLockscreenValidation";
80 
81     protected static final int USER_TYPE_PRIMARY = 1;
82     protected static final int USER_TYPE_MANAGED_PROFILE = 2;
83     protected static final int USER_TYPE_SECONDARY = 3;
84 
85     /** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */
86     protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000;
87 
88     protected static final String FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION =
89             "remote_lockscreen_validation";
90 
91     protected boolean mReturnCredentials = false;
92     protected boolean mReturnGatekeeperPassword = false;
93     protected boolean mForceVerifyPath = false;
94     protected GlifLayout mGlifLayout;
95     protected CheckBox mCheckBox;
96     protected Button mCancelButton;
97     /** Button allowing managed profile password reset, null when is not shown. */
98     @Nullable protected Button mForgotButton;
99     protected int mEffectiveUserId;
100     protected int mUserId;
101     protected UserManager mUserManager;
102     protected LockPatternUtils mLockPatternUtils;
103     protected DevicePolicyManager mDevicePolicyManager;
104     protected TextView mErrorTextView;
105     protected final Handler mHandler = new Handler();
106     protected boolean mFrp;
107     protected boolean mRemoteValidation;
108     protected boolean mRequestWriteRepairModePassword;
109     protected boolean mRepairMode;
110     protected CharSequence mAlternateButtonText;
111     protected BiometricManager mBiometricManager;
112     @Nullable protected RemoteLockscreenValidationSession mRemoteLockscreenValidationSession;
113     /** Credential saved so the credential can be set for device if remote validation passes */
114     @Nullable protected RemoteLockscreenValidationClient mRemoteLockscreenValidationClient;
115     protected RemoteLockscreenValidationFragment mRemoteLockscreenValidationFragment;
116 
isInternalActivity()117     private boolean isInternalActivity() {
118         return (getActivity() instanceof ConfirmLockPassword.InternalActivity)
119                 || (getActivity() instanceof ConfirmLockPattern.InternalActivity);
120     }
121 
122     @Override
onCreate(@ullable Bundle savedInstanceState)123     public void onCreate(@Nullable Bundle savedInstanceState) {
124         super.onCreate(savedInstanceState);
125         final Intent intent = getActivity().getIntent();
126         mAlternateButtonText = intent.getCharSequenceExtra(
127                 KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
128         mReturnCredentials = intent.getBooleanExtra(
129                 ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false);
130 
131         mReturnGatekeeperPassword = intent.getBooleanExtra(
132                 ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false);
133         mForceVerifyPath = intent.getBooleanExtra(
134                 ChooseLockSettingsHelper.EXTRA_KEY_FORCE_VERIFY, false);
135         mRequestWriteRepairModePassword = intent.getBooleanExtra(
136                 ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false);
137 
138         if (intent.getBooleanExtra(IS_REMOTE_LOCKSCREEN_VALIDATION, false)) {
139             if (FeatureFlagUtils.isEnabled(getContext(),
140                     FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION)) {
141                 mRemoteValidation = true;
142             } else {
143                 onRemoteLockscreenValidationFailure(
144                         "Remote lockscreen validation not enabled.");
145             }
146         }
147         if (mRemoteValidation) {
148             mRemoteLockscreenValidationSession = intent.getParcelableExtra(
149                     KeyguardManager.EXTRA_REMOTE_LOCKSCREEN_VALIDATION_SESSION,
150                     RemoteLockscreenValidationSession.class);
151             if (mRemoteLockscreenValidationSession == null
152                     || mRemoteLockscreenValidationSession.getRemainingAttempts() == 0) {
153                 onRemoteLockscreenValidationFailure("RemoteLockscreenValidationSession is null or "
154                         + "no more attempts for remote lockscreen validation.");
155             }
156 
157             ComponentName remoteLockscreenValidationServiceComponent =
158                     intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class);
159             if (remoteLockscreenValidationServiceComponent == null) {
160                 onRemoteLockscreenValidationFailure(
161                         "RemoteLockscreenValidationService ComponentName is null");
162             }
163             mRemoteLockscreenValidationClient = RemoteLockscreenValidationClient
164                     .create(getContext(), remoteLockscreenValidationServiceComponent);
165             if (!mRemoteLockscreenValidationClient.isServiceAvailable()) {
166                 onRemoteLockscreenValidationFailure(String.format(
167                         "RemoteLockscreenValidationService at %s is not available",
168                         remoteLockscreenValidationServiceComponent.getClassName()));
169             }
170 
171             mRemoteLockscreenValidationFragment =
172                     (RemoteLockscreenValidationFragment) getFragmentManager()
173                             .findFragmentByTag(FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION);
174             if (mRemoteLockscreenValidationFragment == null) {
175                 mRemoteLockscreenValidationFragment = new RemoteLockscreenValidationFragment();
176                 getFragmentManager().beginTransaction().add(mRemoteLockscreenValidationFragment,
177                         FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION).commit();
178             }
179         }
180 
181         // Only take this argument into account if it belongs to the current profile.
182         mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras(),
183                 isInternalActivity());
184         mFrp = (mUserId == LockPatternUtils.USER_FRP);
185         mRepairMode = (mUserId == LockPatternUtils.USER_REPAIR_MODE);
186         mUserManager = UserManager.get(getActivity());
187         mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
188         mLockPatternUtils = new LockPatternUtils(getActivity());
189         mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService(
190                 Context.DEVICE_POLICY_SERVICE);
191         mBiometricManager = getActivity().getSystemService(BiometricManager.class);
192     }
193 
194     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)195     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
196         super.onViewCreated(view, savedInstanceState);
197         mCancelButton = view.findViewById(R.id.cancelButton);
198         boolean showCancelButton = mRemoteValidation || getActivity().getIntent().getBooleanExtra(
199                 SHOW_CANCEL_BUTTON, false);
200         boolean hasAlternateButton = (mFrp || mRemoteValidation) && !TextUtils.isEmpty(
201                 mAlternateButtonText);
202         mCancelButton.setVisibility(showCancelButton || hasAlternateButton
203                 ? View.VISIBLE : View.GONE);
204         if (hasAlternateButton) {
205             mCancelButton.setText(mAlternateButtonText);
206         }
207         mCancelButton.setOnClickListener(v -> {
208             if (hasAlternateButton) {
209                 getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
210                 getActivity().finish();
211             } else if (mRemoteValidation) {
212                 onRemoteLockscreenValidationFailure("Forgot lockscreen credential button pressed.");
213             }
214         });
215         setupForgotButtonIfManagedProfile(view);
216 
217         mCheckBox = view.findViewById(R.id.checkbox);
218         if (mCheckBox != null && mRemoteValidation) {
219             mCheckBox.setVisibility(View.VISIBLE);
220         }
221         setupEmergencyCallButtonIfManagedSubscription(view);
222     }
223 
setupEmergencyCallButtonIfManagedSubscription(View view)224     private void setupEmergencyCallButtonIfManagedSubscription(View view) {
225         int policyType = getContext().getSystemService(
226                 DevicePolicyManager.class).getManagedSubscriptionsPolicy().getPolicyType();
227 
228         if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
229             Button emergencyCallButton = view.findViewById(R.id.emergencyCallButton);
230             if (emergencyCallButton == null) {
231                 Log.wtf(TAG,
232                         "Emergency call button not found in managed profile credential dialog");
233                 return;
234             }
235             emergencyCallButton.setVisibility(View.VISIBLE);
236             emergencyCallButton.setOnClickListener(v -> {
237                 final Intent intent = getActivity()
238                         .getSystemService(TelecomManager.class)
239                         .createLaunchEmergencyDialerIntent(null)
240                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
241                                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
242                 getActivity().startActivity(intent);
243                 getActivity().finish();
244             });
245         }
246     }
247 
setupForgotButtonIfManagedProfile(View view)248     private void setupForgotButtonIfManagedProfile(View view) {
249         if (mUserManager.isManagedProfile(mUserId)
250                 && mUserManager.isQuietModeEnabled(UserHandle.of(mUserId))
251                 && mDevicePolicyManager.canProfileOwnerResetPasswordWhenLocked(mUserId)) {
252             mForgotButton = view.findViewById(R.id.forgotButton);
253             if (mForgotButton == null) {
254                 Log.wtf(TAG, "Forgot button not found in managed profile credential dialog");
255                 return;
256             }
257             mForgotButton.setVisibility(View.VISIBLE);
258             mForgotButton.setOnClickListener(v -> {
259                 final Intent intent = new Intent();
260                 intent.setClassName(SETTINGS_PACKAGE_NAME, ForgotPasswordActivity.class.getName());
261                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
262                 intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
263                 getActivity().startActivity(intent);
264                 getActivity().finish();
265             });
266         }
267     }
268 
269     // User could be locked while Effective user is unlocked even though the effective owns the
270     // credential. Otherwise, fingerprint can't unlock fbe/keystore through
271     // verifyTiedProfileChallenge. In such case, we also wanna show the user message that
272     // fingerprint is disabled due to device restart.
isStrongAuthRequired()273     protected boolean isStrongAuthRequired() {
274         return mFrp || mRepairMode
275                 || !mLockPatternUtils.isBiometricAllowedForUser(mEffectiveUserId)
276                 || !mUserManager.isUserUnlocked(mUserId);
277     }
278 
279     @Override
onResume()280     public void onResume() {
281         super.onResume();
282         refreshLockScreen();
283     }
284 
refreshLockScreen()285     protected void refreshLockScreen() {
286         updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
287     }
288 
setAccessibilityTitle(CharSequence supplementalText)289     protected void setAccessibilityTitle(CharSequence supplementalText) {
290         Intent intent = getActivity().getIntent();
291         if (intent != null) {
292             CharSequence titleText = intent.getCharSequenceExtra(
293                     ConfirmDeviceCredentialBaseFragment.TITLE_TEXT);
294             if (supplementalText == null) {
295                 return;
296             }
297             if (titleText == null) {
298                 getActivity().setTitle(supplementalText);
299             } else {
300                 String accessibilityTitle =
301                         new StringBuilder(titleText).append(",").append(supplementalText).toString();
302                 getActivity().setTitle(Utils.createAccessibleSequence(titleText, accessibilityTitle));
303             }
304         }
305     }
306 
307     @Override
onPause()308     public void onPause() {
309         super.onPause();
310     }
311 
312     @Override
onDestroy()313     public void onDestroy() {
314         if (mRemoteLockscreenValidationClient != null) {
315             mRemoteLockscreenValidationClient.disconnect();
316         }
317         super.onDestroy();
318     }
319 
authenticationSucceeded()320     protected abstract void authenticationSucceeded();
321 
prepareEnterAnimation()322     public void prepareEnterAnimation() {
323     }
324 
startEnterAnimation()325     public void startEnterAnimation() {
326     }
327 
reportFailedAttempt()328     protected void reportFailedAttempt() {
329         updateErrorMessage(
330                 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
331         mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
332     }
333 
updateErrorMessage(int numAttempts)334     protected void updateErrorMessage(int numAttempts) {
335         final int maxAttempts =
336                 mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId);
337         if (maxAttempts <= 0 || numAttempts <= 0) {
338             return;
339         }
340 
341         // Update the on-screen error string
342         if (mErrorTextView != null) {
343             final String message = getActivity().getString(
344                     R.string.lock_failed_attempts_before_wipe, numAttempts, maxAttempts);
345             showError(message, 0);
346         }
347 
348         // Only show popup dialog before the last attempt and before wipe
349         final int remainingAttempts = maxAttempts - numAttempts;
350         if (remainingAttempts > 1) {
351             return;
352         }
353         final FragmentManager fragmentManager = getChildFragmentManager();
354         final int userType = getUserTypeForWipe();
355         if (remainingAttempts == 1) {
356             // Last try
357             final String title = getActivity().getString(
358                     R.string.lock_last_attempt_before_wipe_warning_title);
359             final String overrideMessageId = getLastTryOverrideErrorMessageId(userType);
360             final int defaultMessageId = getLastTryDefaultErrorMessage(userType);
361             final String message = mDevicePolicyManager.getResources().getString(
362                     overrideMessageId, () -> getString(defaultMessageId));
363             LastTryDialog.show(fragmentManager, title, message,
364                     android.R.string.ok, false /* dismiss */);
365         } else {
366             // Device, profile, or secondary user is wiped
367             final String message = getWipeMessage(userType);
368             LastTryDialog.show(fragmentManager, null /* title */, message,
369                     com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss,
370                     true /* dismiss */);
371         }
372     }
373 
getUserTypeForWipe()374     private int getUserTypeForWipe() {
375         final UserInfo userToBeWiped = mUserManager.getUserInfo(
376                 mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
377         if (userToBeWiped == null || userToBeWiped.isPrimary()) {
378             return USER_TYPE_PRIMARY;
379         } else if (userToBeWiped.isManagedProfile()) {
380             return USER_TYPE_MANAGED_PROFILE;
381         } else {
382             return USER_TYPE_SECONDARY;
383         }
384     }
385 
getLastTryOverrideErrorMessageId(int userType)386     protected abstract String getLastTryOverrideErrorMessageId(int userType);
getLastTryDefaultErrorMessage(int userType)387     protected abstract int getLastTryDefaultErrorMessage(int userType);
388 
getWipeMessage(int userType)389     private String getWipeMessage(int userType) {
390         switch (userType) {
391             case USER_TYPE_PRIMARY:
392                 return getString(com.android.settingslib
393                         .R.string.failed_attempts_now_wiping_device);
394             case USER_TYPE_MANAGED_PROFILE:
395                 return mDevicePolicyManager.getResources().getString(
396                         WORK_PROFILE_LOCK_ATTEMPTS_FAILED,
397                         () -> getString(
398                           com.android.settingslib.R.string.failed_attempts_now_wiping_profile));
399             case USER_TYPE_SECONDARY:
400                 return getString(com.android.settingslib.R.string.failed_attempts_now_wiping_user);
401             default:
402                 throw new IllegalArgumentException("Unrecognized user type:" + userType);
403         }
404     }
405 
406     private final Runnable mResetErrorRunnable = new Runnable() {
407         @Override
408         public void run() {
409             mErrorTextView.setText("");
410         }
411     };
412 
showError(CharSequence msg, long timeout)413     protected void showError(CharSequence msg, long timeout) {
414         mErrorTextView.setText(msg);
415         onShowError();
416         mHandler.removeCallbacks(mResetErrorRunnable);
417         if (timeout != 0) {
418             mHandler.postDelayed(mResetErrorRunnable, timeout);
419         }
420     }
421 
validateGuess(LockscreenCredential credentialGuess)422     protected void validateGuess(LockscreenCredential credentialGuess) {
423         mRemoteLockscreenValidationFragment.validateLockscreenGuess(
424                 mRemoteLockscreenValidationClient, credentialGuess,
425                 mRemoteLockscreenValidationSession.getSourcePublicKey(), mCheckBox.isChecked());
426     }
427 
updateRemoteLockscreenValidationViews()428     protected void updateRemoteLockscreenValidationViews() {
429         if (!mRemoteValidation || mRemoteLockscreenValidationFragment == null) {
430             return;
431         }
432 
433         boolean enable = mRemoteLockscreenValidationFragment.isRemoteValidationInProgress();
434         mGlifLayout.setProgressBarShown(enable);
435         mCheckBox.setEnabled(!enable);
436         mCancelButton.setEnabled(!enable);
437     }
438 
439     /**
440      * Finishes the activity with result code {@link android.app.Activity#RESULT_FIRST_USER}
441      * after logging the error message.
442      * @param message Optional message to log.
443      */
onRemoteLockscreenValidationFailure(String message)444     public void onRemoteLockscreenValidationFailure(String message) {
445         if (!TextUtils.isEmpty(message)) {
446             Log.w(TAG, message);
447         }
448         getActivity().setResult(RESULT_FIRST_USER);
449         getActivity().finish();
450     }
451 
onShowError()452     protected abstract void onShowError();
453 
showError(int msg, long timeout)454     protected void showError(int msg, long timeout) {
455         showError(getText(msg), timeout);
456     }
457 
458     public static class LastTryDialog extends DialogFragment {
459         private static final String TAG = LastTryDialog.class.getSimpleName();
460 
461         private static final String ARG_TITLE = "title";
462         private static final String ARG_MESSAGE = "message";
463         private static final String ARG_BUTTON = "button";
464         private static final String ARG_DISMISS = "dismiss";
465 
show(FragmentManager from, String title, String message, int button, boolean dismiss)466         static boolean show(FragmentManager from, String title, String message, int button,
467                 boolean dismiss) {
468             LastTryDialog existent = (LastTryDialog) from.findFragmentByTag(TAG);
469             if (existent != null && !existent.isRemoving()) {
470                 return false;
471             }
472             Bundle args = new Bundle();
473             args.putString(ARG_TITLE, title);
474             args.putString(ARG_MESSAGE, message);
475             args.putInt(ARG_BUTTON, button);
476             args.putBoolean(ARG_DISMISS, dismiss);
477 
478             DialogFragment dialog = new LastTryDialog();
479             dialog.setArguments(args);
480             dialog.show(from, TAG);
481             from.executePendingTransactions();
482             return true;
483         }
484 
hide(FragmentManager from)485         static void hide(FragmentManager from) {
486             LastTryDialog dialog = (LastTryDialog) from.findFragmentByTag(TAG);
487             if (dialog != null) {
488                 dialog.dismissAllowingStateLoss();
489                 from.executePendingTransactions();
490             }
491         }
492 
493         /**
494          * Dialog setup.
495          * <p>
496          * To make it less likely that the dialog is dismissed accidentally, for example if the
497          * device is malfunctioning or if the device is in a pocket, we set
498          * {@code setCanceledOnTouchOutside(false)}.
499          */
500         @Override
onCreateDialog(Bundle savedInstanceState)501         public Dialog onCreateDialog(Bundle savedInstanceState) {
502             Dialog dialog = new AlertDialog.Builder(getActivity())
503                     .setTitle(getArguments().getString(ARG_TITLE))
504                     .setMessage(getArguments().getString(ARG_MESSAGE))
505                     .setPositiveButton(getArguments().getInt(ARG_BUTTON), null)
506                     .create();
507             dialog.setCanceledOnTouchOutside(false);
508             return dialog;
509         }
510 
511         @Override
onDismiss(final DialogInterface dialog)512         public void onDismiss(final DialogInterface dialog) {
513             super.onDismiss(dialog);
514             if (getActivity() != null && getArguments().getBoolean(ARG_DISMISS)) {
515                 getActivity().finish();
516             }
517         }
518     }
519 }
520