blob: a1eb1066eca0f9c5b797205f9f0b4e2cc3adac85 [file] [log] [blame]
Jorim Jaggi8a09b612015-04-06 17:47:18 -07001/*
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
Robin Leec1cb8a42017-02-06 18:19:40 +000017// TODO (b/35202196): move this class out of the root of the package.
Maurice Lam2eb170c2017-04-28 16:18:47 -070018package com.android.settings.password;
Jorim Jaggi8a09b612015-04-06 17:47:18 -070019
Fan Zhangc3fd2892019-01-29 16:00:19 -080020import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
21
Jorim Jaggi8a09b612015-04-06 17:47:18 -070022import android.annotation.Nullable;
Robin Leec1cb8a42017-02-06 18:19:40 +000023import android.app.Dialog;
Adrian Roos5a9a3cd2017-03-30 18:02:25 -070024import android.app.KeyguardManager;
Benjamin Franzcc8d37a2016-01-18 17:31:26 +000025import android.app.admin.DevicePolicyManager;
Clara Bayarri86ebaa22015-12-10 12:35:27 +000026import android.content.Context;
Clara Bayarri9d357ea2016-01-28 17:50:53 +000027import android.content.DialogInterface;
Julia Reynoldsce25af42015-07-08 16:56:31 -040028import android.content.Intent;
Charles He641c9fc2017-04-26 18:45:48 +010029import android.content.pm.UserInfo;
Benjamin Franz194300d2016-01-13 12:16:25 +000030import android.graphics.Point;
31import android.graphics.PorterDuff;
Benjamin Franzcc8d37a2016-01-18 17:31:26 +000032import android.graphics.drawable.ColorDrawable;
Benjamin Franz194300d2016-01-13 12:16:25 +000033import android.graphics.drawable.Drawable;
Kevin Chyne56df882019-01-24 19:42:05 -080034import android.hardware.biometrics.BiometricManager;
Jorim Jaggi8a09b612015-04-06 17:47:18 -070035import android.os.Bundle;
Clara Bayarri9d357ea2016-01-28 17:50:53 +000036import android.os.Handler;
Benjamin Franz194300d2016-01-13 12:16:25 +000037import android.os.UserManager;
Adrian Roos5a9a3cd2017-03-30 18:02:25 -070038import android.text.TextUtils;
Jorim Jaggi8a09b612015-04-06 17:47:18 -070039import android.view.View;
Benjamin Franz194300d2016-01-13 12:16:25 +000040import android.view.ViewGroup;
Jorim Jaggi8a09b612015-04-06 17:47:18 -070041import android.widget.Button;
Benjamin Franz194300d2016-01-13 12:16:25 +000042import android.widget.FrameLayout;
Jorim Jaggi8a09b612015-04-06 17:47:18 -070043import android.widget.ImageView;
44import android.widget.TextView;
45
Fan Zhang23f8d592018-08-28 15:11:40 -070046import androidx.appcompat.app.AlertDialog;
47import androidx.fragment.app.DialogFragment;
48import androidx.fragment.app.FragmentManager;
49
Clara Bayarri9d357ea2016-01-28 17:50:53 +000050import com.android.internal.widget.LockPatternUtils;
Maurice Lam2eb170c2017-04-28 16:18:47 -070051import com.android.settings.R;
52import com.android.settings.Utils;
tmfang99cc23d2018-06-26 19:01:57 +080053import com.android.settings.core.InstrumentedFragment;
54
Jorim Jaggi8a09b612015-04-06 17:47:18 -070055/**
56 * Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
57 */
Kevin Chyne5a016e2018-10-03 18:39:22 -070058public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFragment {
Jorim Jaggi8a09b612015-04-06 17:47:18 -070059
Fan Zhangc3fd2892019-01-29 16:00:19 -080060 public static final String TITLE_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.title";
61 public static final String HEADER_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.header";
62 public static final String DETAILS_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.details";
63 public static final String DARK_THEME = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.darkTheme";
Jorim Jaggi8a09b612015-04-06 17:47:18 -070064 public static final String SHOW_CANCEL_BUTTON =
Fan Zhangc3fd2892019-01-29 16:00:19 -080065 SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showCancelButton";
Jorim Jaggi8a09b612015-04-06 17:47:18 -070066 public static final String SHOW_WHEN_LOCKED =
Fan Zhangc3fd2892019-01-29 16:00:19 -080067 SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showWhenLocked";
Kevin Chynb3ee2312018-10-04 15:01:43 -070068 public static final String USE_FADE_ANIMATION =
Fan Zhangc3fd2892019-01-29 16:00:19 -080069 SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.useFadeAnimation";
Jorim Jaggi8a09b612015-04-06 17:47:18 -070070
Charles He641c9fc2017-04-26 18:45:48 +010071 protected static final int USER_TYPE_PRIMARY = 1;
72 protected static final int USER_TYPE_MANAGED_PROFILE = 2;
73 protected static final int USER_TYPE_SECONDARY = 3;
74
Charles Hecaf95102017-08-18 17:35:27 +010075 /** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */
76 protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000;
77
Victor Chang5e0a46b2016-05-13 17:06:59 +010078 protected boolean mReturnCredentials = false;
Jorim Jaggiff41a9a2015-06-09 15:31:28 -070079 protected Button mCancelButton;
Clara Bayarri40db4bb2016-01-07 17:10:27 +000080 protected int mEffectiveUserId;
Ricky Wai996d0df2016-04-05 16:33:47 +010081 protected int mUserId;
Andrew Scull783870c2017-03-15 12:42:18 +000082 protected UserManager mUserManager;
Clara Bayarri9d357ea2016-01-28 17:50:53 +000083 protected LockPatternUtils mLockPatternUtils;
Charles He641c9fc2017-04-26 18:45:48 +010084 protected DevicePolicyManager mDevicePolicyManager;
Clara Bayarri9d357ea2016-01-28 17:50:53 +000085 protected TextView mErrorTextView;
86 protected final Handler mHandler = new Handler();
Adrian Roos5a9a3cd2017-03-30 18:02:25 -070087 protected boolean mFrp;
88 private CharSequence mFrpAlternateButtonText;
Kevin Chyne56df882019-01-24 19:42:05 -080089 protected BiometricManager mBiometricManager;
Jorim Jaggi8a09b612015-04-06 17:47:18 -070090
Jeff Sharkey219ec912017-12-19 14:57:39 -070091 private boolean isInternalActivity() {
92 return (getActivity() instanceof ConfirmLockPassword.InternalActivity)
93 || (getActivity() instanceof ConfirmLockPattern.InternalActivity);
94 }
95
Jorim Jaggi8a09b612015-04-06 17:47:18 -070096 @Override
97 public void onCreate(@Nullable Bundle savedInstanceState) {
98 super.onCreate(savedInstanceState);
Adrian Roos5a9a3cd2017-03-30 18:02:25 -070099 mFrpAlternateButtonText = getActivity().getIntent().getCharSequenceExtra(
100 KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
Victor Chang5e0a46b2016-05-13 17:06:59 +0100101 mReturnCredentials = getActivity().getIntent().getBooleanExtra(
102 ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false);
Clara Bayarri40db4bb2016-01-07 17:10:27 +0000103 // Only take this argument into account if it belongs to the current profile.
104 Intent intent = getActivity().getIntent();
Jeff Sharkey219ec912017-12-19 14:57:39 -0700105 mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras(),
106 isInternalActivity());
Adrian Roos5a9a3cd2017-03-30 18:02:25 -0700107 mFrp = (mUserId == LockPatternUtils.USER_FRP);
Andrew Scull783870c2017-03-15 12:42:18 +0000108 mUserManager = UserManager.get(getActivity());
109 mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
Clara Bayarri9d357ea2016-01-28 17:50:53 +0000110 mLockPatternUtils = new LockPatternUtils(getActivity());
Charles He641c9fc2017-04-26 18:45:48 +0100111 mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService(
112 Context.DEVICE_POLICY_SERVICE);
Kevin Chyne56df882019-01-24 19:42:05 -0800113 mBiometricManager = getActivity().getSystemService(BiometricManager.class);
Jorim Jaggi8a09b612015-04-06 17:47:18 -0700114 }
115
116 @Override
117 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
118 super.onViewCreated(view, savedInstanceState);
Jorim Jaggiff41a9a2015-06-09 15:31:28 -0700119 mCancelButton = (Button) view.findViewById(R.id.cancelButton);
Kevin Chyne5a016e2018-10-03 18:39:22 -0700120
Jorim Jaggi8a09b612015-04-06 17:47:18 -0700121 boolean showCancelButton = getActivity().getIntent().getBooleanExtra(
122 SHOW_CANCEL_BUTTON, false);
Adrian Roos5a9a3cd2017-03-30 18:02:25 -0700123 boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText);
124 mCancelButton.setVisibility(showCancelButton || hasAlternateButton
125 ? View.VISIBLE : View.GONE);
126 if (hasAlternateButton) {
127 mCancelButton.setText(mFrpAlternateButtonText);
128 }
Jorim Jaggiff41a9a2015-06-09 15:31:28 -0700129 mCancelButton.setOnClickListener(new View.OnClickListener() {
Jorim Jaggi8a09b612015-04-06 17:47:18 -0700130 @Override
131 public void onClick(View v) {
Adrian Roos5a9a3cd2017-03-30 18:02:25 -0700132 if (hasAlternateButton) {
133 getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
134 }
Jorim Jaggi8a09b612015-04-06 17:47:18 -0700135 getActivity().finish();
136 }
137 });
Benjamin Franz194300d2016-01-13 12:16:25 +0000138 int credentialOwnerUserId = Utils.getCredentialOwnerUserId(
139 getActivity(),
140 Utils.getUserIdFromBundle(
141 getActivity(),
Jeff Sharkey219ec912017-12-19 14:57:39 -0700142 getActivity().getIntent().getExtras(), isInternalActivity()));
Jorim Jaggi8a09b612015-04-06 17:47:18 -0700143 }
144
Victor Chang5e0a46b2016-05-13 17:06:59 +0100145 // User could be locked while Effective user is unlocked even though the effective owns the
146 // credential. Otherwise, fingerprint can't unlock fbe/keystore through
147 // verifyTiedProfileChallenge. In such case, we also wanna show the user message that
148 // fingerprint is disabled due to device restart.
Adrian Roos5a9a3cd2017-03-30 18:02:25 -0700149 protected boolean isStrongAuthRequired() {
150 return mFrp
Kevin Chyne5a016e2018-10-03 18:39:22 -0700151 || !mLockPatternUtils.isBiometricAllowedForUser(mEffectiveUserId)
Adrian Roos5a9a3cd2017-03-30 18:02:25 -0700152 || !mUserManager.isUserUnlocked(mUserId);
Daniel U6655e1b2016-04-07 19:18:26 +0100153 }
154
Jorim Jaggi8a09b612015-04-06 17:47:18 -0700155 @Override
156 public void onResume() {
157 super.onResume();
Ricky Wai9aa434c2016-06-27 11:10:09 +0100158 refreshLockScreen();
159 }
160
161 protected void refreshLockScreen() {
Charles He641c9fc2017-04-26 18:45:48 +0100162 updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
Jorim Jaggi8a09b612015-04-06 17:47:18 -0700163 }
164
Julia Reynolds017ae502016-03-16 11:40:54 -0400165 protected void setAccessibilityTitle(CharSequence supplementalText) {
Julia Reynoldsce25af42015-07-08 16:56:31 -0400166 Intent intent = getActivity().getIntent();
167 if (intent != null) {
168 CharSequence titleText = intent.getCharSequenceExtra(
169 ConfirmDeviceCredentialBaseFragment.TITLE_TEXT);
Doris Ling30e348b2016-08-03 17:26:34 -0700170 if (supplementalText == null) {
Julia Reynoldsddd887a2015-07-10 10:07:21 -0400171 return;
172 }
Doris Ling30e348b2016-08-03 17:26:34 -0700173 if (titleText == null) {
174 getActivity().setTitle(supplementalText);
175 } else {
176 String accessibilityTitle =
177 new StringBuilder(titleText).append(",").append(supplementalText).toString();
178 getActivity().setTitle(Utils.createAccessibleSequence(titleText, accessibilityTitle));
179 }
Julia Reynoldsce25af42015-07-08 16:56:31 -0400180 }
181 }
182
Jorim Jaggi8a09b612015-04-06 17:47:18 -0700183 @Override
184 public void onPause() {
185 super.onPause();
Jorim Jaggi8a09b612015-04-06 17:47:18 -0700186 }
187
Andres Morales59f59762015-04-16 16:30:31 -0700188 protected abstract void authenticationSucceeded();
Jorim Jaggi8a09b612015-04-06 17:47:18 -0700189
Jorim Jaggiff41a9a2015-06-09 15:31:28 -0700190
191 public void prepareEnterAnimation() {
192 }
193
194 public void startEnterAnimation() {
195 }
Clara Bayarric5cde052015-10-22 17:24:43 +0100196
Benjamin Franzcc8d37a2016-01-18 17:31:26 +0000197 private void setWorkChallengeBackground(View baseView, int userId) {
Abodunrinwa Toki80c082f2016-03-08 18:07:35 +0000198 View mainContent = getActivity().findViewById(com.android.settings.R.id.main_content);
199 if (mainContent != null) {
200 // Remove the main content padding so that the background image is full screen.
201 mainContent.setPadding(0, 0, 0, 0);
202 }
203
Charles He641c9fc2017-04-26 18:45:48 +0100204 baseView.setBackground(
205 new ColorDrawable(mDevicePolicyManager.getOrganizationColorForUser(userId)));
Benjamin Franz194300d2016-01-13 12:16:25 +0000206 ImageView imageView = (ImageView) baseView.findViewById(R.id.background_image);
207 if (imageView != null) {
208 Drawable image = getResources().getDrawable(R.drawable.work_challenge_background);
209 image.setColorFilter(
210 getResources().getColor(R.color.confirm_device_credential_transparent_black),
211 PorterDuff.Mode.DARKEN);
212 imageView.setImageDrawable(image);
213 Point screenSize = new Point();
214 getActivity().getWindowManager().getDefaultDisplay().getSize(screenSize);
215 imageView.setLayoutParams(new FrameLayout.LayoutParams(
216 ViewGroup.LayoutParams.MATCH_PARENT,
217 screenSize.y));
218 }
219 }
Clara Bayarri9d357ea2016-01-28 17:50:53 +0000220
Clara Bayarri9d357ea2016-01-28 17:50:53 +0000221 protected void reportFailedAttempt() {
Charles He641c9fc2017-04-26 18:45:48 +0100222 updateErrorMessage(
223 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
224 mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
Clara Bayarri9d357ea2016-01-28 17:50:53 +0000225 }
226
227 protected void updateErrorMessage(int numAttempts) {
228 final int maxAttempts =
229 mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId);
Charles He641c9fc2017-04-26 18:45:48 +0100230 if (maxAttempts <= 0 || numAttempts <= 0) {
231 return;
232 }
233
234 // Update the on-screen error string
235 if (mErrorTextView != null) {
236 final String message = getActivity().getString(
237 R.string.lock_failed_attempts_before_wipe, numAttempts, maxAttempts);
238 showError(message, 0);
239 }
240
241 // Only show popup dialog before the last attempt and before wipe
242 final int remainingAttempts = maxAttempts - numAttempts;
243 if (remainingAttempts > 1) {
244 return;
245 }
246 final FragmentManager fragmentManager = getChildFragmentManager();
247 final int userType = getUserTypeForWipe();
248 if (remainingAttempts == 1) {
249 // Last try
250 final String title = getActivity().getString(
251 R.string.lock_last_attempt_before_wipe_warning_title);
252 final int messageId = getLastTryErrorMessage(userType);
253 LastTryDialog.show(fragmentManager, title, messageId,
254 android.R.string.ok, false /* dismiss */);
255 } else {
256 // Device, profile, or secondary user is wiped
257 final int messageId = getWipeMessage(userType);
258 LastTryDialog.show(fragmentManager, null /* title */, messageId,
259 R.string.lock_failed_attempts_now_wiping_dialog_dismiss, true /* dismiss */);
Clara Bayarri9d357ea2016-01-28 17:50:53 +0000260 }
261 }
262
Charles He641c9fc2017-04-26 18:45:48 +0100263 private int getUserTypeForWipe() {
264 final UserInfo userToBeWiped = mUserManager.getUserInfo(
265 mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
266 if (userToBeWiped == null || userToBeWiped.isPrimary()) {
267 return USER_TYPE_PRIMARY;
268 } else if (userToBeWiped.isManagedProfile()) {
269 return USER_TYPE_MANAGED_PROFILE;
270 } else {
271 return USER_TYPE_SECONDARY;
272 }
273 }
274
275 protected abstract int getLastTryErrorMessage(int userType);
276
277 private int getWipeMessage(int userType) {
278 switch (userType) {
279 case USER_TYPE_PRIMARY:
280 return R.string.lock_failed_attempts_now_wiping_device;
281 case USER_TYPE_MANAGED_PROFILE:
282 return R.string.lock_failed_attempts_now_wiping_profile;
283 case USER_TYPE_SECONDARY:
284 return R.string.lock_failed_attempts_now_wiping_user;
285 default:
286 throw new IllegalArgumentException("Unrecognized user type:" + userType);
287 }
288 }
Clara Bayarri9d357ea2016-01-28 17:50:53 +0000289
290 private final Runnable mResetErrorRunnable = new Runnable() {
291 @Override
292 public void run() {
293 mErrorTextView.setText("");
294 }
295 };
296
297 protected void showError(CharSequence msg, long timeout) {
298 mErrorTextView.setText(msg);
299 onShowError();
300 mHandler.removeCallbacks(mResetErrorRunnable);
301 if (timeout != 0) {
302 mHandler.postDelayed(mResetErrorRunnable, timeout);
303 }
304 }
305
306 protected abstract void onShowError();
307
308 protected void showError(int msg, long timeout) {
309 showError(getText(msg), timeout);
310 }
311
Robin Leec1cb8a42017-02-06 18:19:40 +0000312 public static class LastTryDialog extends DialogFragment {
313 private static final String TAG = LastTryDialog.class.getSimpleName();
314
315 private static final String ARG_TITLE = "title";
316 private static final String ARG_MESSAGE = "message";
317 private static final String ARG_BUTTON = "button";
318 private static final String ARG_DISMISS = "dismiss";
319
320 static boolean show(FragmentManager from, String title, int message, int button,
321 boolean dismiss) {
322 LastTryDialog existent = (LastTryDialog) from.findFragmentByTag(TAG);
323 if (existent != null && !existent.isRemoving()) {
324 return false;
325 }
326 Bundle args = new Bundle();
327 args.putString(ARG_TITLE, title);
328 args.putInt(ARG_MESSAGE, message);
329 args.putInt(ARG_BUTTON, button);
330 args.putBoolean(ARG_DISMISS, dismiss);
331
332 DialogFragment dialog = new LastTryDialog();
333 dialog.setArguments(args);
334 dialog.show(from, TAG);
Charles He641c9fc2017-04-26 18:45:48 +0100335 from.executePendingTransactions();
Robin Leec1cb8a42017-02-06 18:19:40 +0000336 return true;
337 }
338
339 static void hide(FragmentManager from) {
340 LastTryDialog dialog = (LastTryDialog) from.findFragmentByTag(TAG);
341 if (dialog != null) {
342 dialog.dismissAllowingStateLoss();
343 from.executePendingTransactions();
344 }
345 }
346
Robin Lee11bf7802017-02-10 16:12:20 +0000347 /**
348 * Dialog setup.
349 * <p>
350 * To make it less likely that the dialog is dismissed accidentally, for example if the
351 * device is malfunctioning or if the device is in a pocket, we set
352 * {@code setCanceledOnTouchOutside(false)}.
353 */
Robin Leec1cb8a42017-02-06 18:19:40 +0000354 @Override
355 public Dialog onCreateDialog(Bundle savedInstanceState) {
Robin Lee11bf7802017-02-10 16:12:20 +0000356 Dialog dialog = new AlertDialog.Builder(getActivity())
Robin Leec1cb8a42017-02-06 18:19:40 +0000357 .setTitle(getArguments().getString(ARG_TITLE))
358 .setMessage(getArguments().getInt(ARG_MESSAGE))
359 .setPositiveButton(getArguments().getInt(ARG_BUTTON), null)
360 .create();
Robin Lee11bf7802017-02-10 16:12:20 +0000361 dialog.setCanceledOnTouchOutside(false);
362 return dialog;
Robin Leec1cb8a42017-02-06 18:19:40 +0000363 }
364
365 @Override
366 public void onDismiss(final DialogInterface dialog) {
367 super.onDismiss(dialog);
368 if (getActivity() != null && getArguments().getBoolean(ARG_DISMISS)) {
369 getActivity().finish();
370 }
371 }
Clara Bayarri9d357ea2016-01-28 17:50:53 +0000372 }
Jorim Jaggi8a09b612015-04-06 17:47:18 -0700373}