• 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 com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
21 
22 import android.annotation.Nullable;
23 import android.app.Dialog;
24 import android.app.KeyguardManager;
25 import android.app.admin.DevicePolicyManager;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.content.pm.UserInfo;
30 import android.hardware.biometrics.BiometricManager;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.view.View;
38 import android.widget.Button;
39 import android.widget.TextView;
40 
41 import androidx.appcompat.app.AlertDialog;
42 import androidx.fragment.app.DialogFragment;
43 import androidx.fragment.app.FragmentManager;
44 
45 import com.android.internal.widget.LockPatternUtils;
46 import com.android.settings.R;
47 import com.android.settings.Utils;
48 import com.android.settings.core.InstrumentedFragment;
49 
50 /**
51  * Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
52  */
53 public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFragment {
54     public static final String TAG = ConfirmDeviceCredentialBaseFragment.class.getSimpleName();
55     public static final String TITLE_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.title";
56     public static final String HEADER_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.header";
57     public static final String DETAILS_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.details";
58     public static final String DARK_THEME = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.darkTheme";
59     public static final String SHOW_CANCEL_BUTTON =
60             SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showCancelButton";
61     public static final String SHOW_WHEN_LOCKED =
62             SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showWhenLocked";
63     public static final String USE_FADE_ANIMATION =
64             SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.useFadeAnimation";
65 
66     protected static final int USER_TYPE_PRIMARY = 1;
67     protected static final int USER_TYPE_MANAGED_PROFILE = 2;
68     protected static final int USER_TYPE_SECONDARY = 3;
69 
70     /** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */
71     protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000;
72 
73     protected boolean mReturnCredentials = false;
74     protected boolean mReturnGatekeeperPassword = false;
75     protected boolean mForceVerifyPath = false;
76     protected Button mCancelButton;
77     /** Button allowing managed profile password reset, null when is not shown. */
78     @Nullable protected Button mForgotButton;
79     protected int mEffectiveUserId;
80     protected int mUserId;
81     protected UserManager mUserManager;
82     protected LockPatternUtils mLockPatternUtils;
83     protected DevicePolicyManager mDevicePolicyManager;
84     protected TextView mErrorTextView;
85     protected final Handler mHandler = new Handler();
86     protected boolean mFrp;
87     private CharSequence mFrpAlternateButtonText;
88     protected BiometricManager mBiometricManager;
89 
isInternalActivity()90     private boolean isInternalActivity() {
91         return (getActivity() instanceof ConfirmLockPassword.InternalActivity)
92                 || (getActivity() instanceof ConfirmLockPattern.InternalActivity);
93     }
94 
95     @Override
onCreate(@ullable Bundle savedInstanceState)96     public void onCreate(@Nullable Bundle savedInstanceState) {
97         super.onCreate(savedInstanceState);
98         final Intent intent = getActivity().getIntent();
99         mFrpAlternateButtonText = intent.getCharSequenceExtra(
100                 KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
101         mReturnCredentials = intent.getBooleanExtra(
102                 ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false);
103 
104         mReturnGatekeeperPassword = intent.getBooleanExtra(
105                 ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false);
106         mForceVerifyPath = intent.getBooleanExtra(
107                 ChooseLockSettingsHelper.EXTRA_KEY_FORCE_VERIFY, false);
108 
109         // Only take this argument into account if it belongs to the current profile.
110         mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras(),
111                 isInternalActivity());
112         mFrp = (mUserId == LockPatternUtils.USER_FRP);
113         mUserManager = UserManager.get(getActivity());
114         mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
115         mLockPatternUtils = new LockPatternUtils(getActivity());
116         mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService(
117                 Context.DEVICE_POLICY_SERVICE);
118         mBiometricManager = getActivity().getSystemService(BiometricManager.class);
119     }
120 
121     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)122     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
123         super.onViewCreated(view, savedInstanceState);
124         mCancelButton = view.findViewById(R.id.cancelButton);
125         boolean showCancelButton = getActivity().getIntent().getBooleanExtra(
126                 SHOW_CANCEL_BUTTON, false);
127         boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText);
128         mCancelButton.setVisibility(showCancelButton || hasAlternateButton
129                 ? View.VISIBLE : View.GONE);
130         if (hasAlternateButton) {
131             mCancelButton.setText(mFrpAlternateButtonText);
132         }
133         mCancelButton.setOnClickListener(v -> {
134             if (hasAlternateButton) {
135                 getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
136             }
137             getActivity().finish();
138         });
139         setupForgotButtonIfManagedProfile(view);
140     }
141 
setupForgotButtonIfManagedProfile(View view)142     private void setupForgotButtonIfManagedProfile(View view) {
143         if (mUserManager.isManagedProfile(mUserId)
144                 && mUserManager.isQuietModeEnabled(UserHandle.of(mUserId))
145                 && mDevicePolicyManager.canProfileOwnerResetPasswordWhenLocked(mUserId)) {
146             mForgotButton = view.findViewById(R.id.forgotButton);
147             if (mForgotButton == null) {
148                 Log.wtf(TAG, "Forgot button not found in managed profile credential dialog");
149                 return;
150             }
151             mForgotButton.setVisibility(View.VISIBLE);
152             mForgotButton.setOnClickListener(v -> {
153                 final Intent intent = new Intent();
154                 intent.setClassName(SETTINGS_PACKAGE_NAME, ForgotPasswordActivity.class.getName());
155                 intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
156                 getActivity().startActivity(intent);
157                 getActivity().finish();
158             });
159         }
160     }
161 
162     // User could be locked while Effective user is unlocked even though the effective owns the
163     // credential. Otherwise, fingerprint can't unlock fbe/keystore through
164     // verifyTiedProfileChallenge. In such case, we also wanna show the user message that
165     // fingerprint is disabled due to device restart.
isStrongAuthRequired()166     protected boolean isStrongAuthRequired() {
167         return mFrp
168                 || !mLockPatternUtils.isBiometricAllowedForUser(mEffectiveUserId)
169                 || !mUserManager.isUserUnlocked(mUserId);
170     }
171 
172     @Override
onResume()173     public void onResume() {
174         super.onResume();
175         refreshLockScreen();
176     }
177 
refreshLockScreen()178     protected void refreshLockScreen() {
179         updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
180     }
181 
setAccessibilityTitle(CharSequence supplementalText)182     protected void setAccessibilityTitle(CharSequence supplementalText) {
183         Intent intent = getActivity().getIntent();
184         if (intent != null) {
185             CharSequence titleText = intent.getCharSequenceExtra(
186                     ConfirmDeviceCredentialBaseFragment.TITLE_TEXT);
187             if (supplementalText == null) {
188                 return;
189             }
190             if (titleText == null) {
191                 getActivity().setTitle(supplementalText);
192             } else {
193                 String accessibilityTitle =
194                         new StringBuilder(titleText).append(",").append(supplementalText).toString();
195                 getActivity().setTitle(Utils.createAccessibleSequence(titleText, accessibilityTitle));
196             }
197         }
198     }
199 
200     @Override
onPause()201     public void onPause() {
202         super.onPause();
203     }
204 
authenticationSucceeded()205     protected abstract void authenticationSucceeded();
206 
207 
prepareEnterAnimation()208     public void prepareEnterAnimation() {
209     }
210 
startEnterAnimation()211     public void startEnterAnimation() {
212     }
213 
reportFailedAttempt()214     protected void reportFailedAttempt() {
215         updateErrorMessage(
216                 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
217         mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
218     }
219 
updateErrorMessage(int numAttempts)220     protected void updateErrorMessage(int numAttempts) {
221         final int maxAttempts =
222                 mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId);
223         if (maxAttempts <= 0 || numAttempts <= 0) {
224             return;
225         }
226 
227         // Update the on-screen error string
228         if (mErrorTextView != null) {
229             final String message = getActivity().getString(
230                     R.string.lock_failed_attempts_before_wipe, numAttempts, maxAttempts);
231             showError(message, 0);
232         }
233 
234         // Only show popup dialog before the last attempt and before wipe
235         final int remainingAttempts = maxAttempts - numAttempts;
236         if (remainingAttempts > 1) {
237             return;
238         }
239         final FragmentManager fragmentManager = getChildFragmentManager();
240         final int userType = getUserTypeForWipe();
241         if (remainingAttempts == 1) {
242             // Last try
243             final String title = getActivity().getString(
244                     R.string.lock_last_attempt_before_wipe_warning_title);
245             final int messageId = getLastTryErrorMessage(userType);
246             LastTryDialog.show(fragmentManager, title, messageId,
247                     android.R.string.ok, false /* dismiss */);
248         } else {
249             // Device, profile, or secondary user is wiped
250             final int messageId = getWipeMessage(userType);
251             LastTryDialog.show(fragmentManager, null /* title */, messageId,
252                     R.string.lock_failed_attempts_now_wiping_dialog_dismiss, true /* dismiss */);
253         }
254     }
255 
getUserTypeForWipe()256     private int getUserTypeForWipe() {
257         final UserInfo userToBeWiped = mUserManager.getUserInfo(
258                 mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
259         if (userToBeWiped == null || userToBeWiped.isPrimary()) {
260             return USER_TYPE_PRIMARY;
261         } else if (userToBeWiped.isManagedProfile()) {
262             return USER_TYPE_MANAGED_PROFILE;
263         } else {
264             return USER_TYPE_SECONDARY;
265         }
266     }
267 
getLastTryErrorMessage(int userType)268     protected abstract int getLastTryErrorMessage(int userType);
269 
getWipeMessage(int userType)270     private int getWipeMessage(int userType) {
271         switch (userType) {
272             case USER_TYPE_PRIMARY:
273                 return R.string.lock_failed_attempts_now_wiping_device;
274             case USER_TYPE_MANAGED_PROFILE:
275                 return R.string.lock_failed_attempts_now_wiping_profile;
276             case USER_TYPE_SECONDARY:
277                 return R.string.lock_failed_attempts_now_wiping_user;
278             default:
279                 throw new IllegalArgumentException("Unrecognized user type:" + userType);
280         }
281     }
282 
283     private final Runnable mResetErrorRunnable = new Runnable() {
284         @Override
285         public void run() {
286             mErrorTextView.setText("");
287         }
288     };
289 
showError(CharSequence msg, long timeout)290     protected void showError(CharSequence msg, long timeout) {
291         mErrorTextView.setText(msg);
292         onShowError();
293         mHandler.removeCallbacks(mResetErrorRunnable);
294         if (timeout != 0) {
295             mHandler.postDelayed(mResetErrorRunnable, timeout);
296         }
297     }
298 
onShowError()299     protected abstract void onShowError();
300 
showError(int msg, long timeout)301     protected void showError(int msg, long timeout) {
302         showError(getText(msg), timeout);
303     }
304 
305     public static class LastTryDialog extends DialogFragment {
306         private static final String TAG = LastTryDialog.class.getSimpleName();
307 
308         private static final String ARG_TITLE = "title";
309         private static final String ARG_MESSAGE = "message";
310         private static final String ARG_BUTTON = "button";
311         private static final String ARG_DISMISS = "dismiss";
312 
show(FragmentManager from, String title, int message, int button, boolean dismiss)313         static boolean show(FragmentManager from, String title, int message, int button,
314                 boolean dismiss) {
315             LastTryDialog existent = (LastTryDialog) from.findFragmentByTag(TAG);
316             if (existent != null && !existent.isRemoving()) {
317                 return false;
318             }
319             Bundle args = new Bundle();
320             args.putString(ARG_TITLE, title);
321             args.putInt(ARG_MESSAGE, message);
322             args.putInt(ARG_BUTTON, button);
323             args.putBoolean(ARG_DISMISS, dismiss);
324 
325             DialogFragment dialog = new LastTryDialog();
326             dialog.setArguments(args);
327             dialog.show(from, TAG);
328             from.executePendingTransactions();
329             return true;
330         }
331 
hide(FragmentManager from)332         static void hide(FragmentManager from) {
333             LastTryDialog dialog = (LastTryDialog) from.findFragmentByTag(TAG);
334             if (dialog != null) {
335                 dialog.dismissAllowingStateLoss();
336                 from.executePendingTransactions();
337             }
338         }
339 
340         /**
341          * Dialog setup.
342          * <p>
343          * To make it less likely that the dialog is dismissed accidentally, for example if the
344          * device is malfunctioning or if the device is in a pocket, we set
345          * {@code setCanceledOnTouchOutside(false)}.
346          */
347         @Override
onCreateDialog(Bundle savedInstanceState)348         public Dialog onCreateDialog(Bundle savedInstanceState) {
349             Dialog dialog = new AlertDialog.Builder(getActivity())
350                     .setTitle(getArguments().getString(ARG_TITLE))
351                     .setMessage(getArguments().getInt(ARG_MESSAGE))
352                     .setPositiveButton(getArguments().getInt(ARG_BUTTON), null)
353                     .create();
354             dialog.setCanceledOnTouchOutside(false);
355             return dialog;
356         }
357 
358         @Override
onDismiss(final DialogInterface dialog)359         public void onDismiss(final DialogInterface dialog) {
360             super.onDismiss(dialog);
361             if (getActivity() != null && getArguments().getBoolean(ARG_DISMISS)) {
362                 getActivity().finish();
363             }
364         }
365     }
366 }
367