• 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 android.annotation.Nullable;
21 import android.app.ActivityManager;
22 import android.app.ActivityOptions;
23 import android.app.AlertDialog;
24 import android.app.Dialog;
25 import android.app.DialogFragment;
26 import android.app.FragmentManager;
27 import android.app.IActivityManager;
28 import android.app.KeyguardManager;
29 import android.app.admin.DevicePolicyManager;
30 import android.app.trust.TrustManager;
31 import android.content.Context;
32 import android.content.DialogInterface;
33 import android.content.Intent;
34 import android.content.IntentSender;
35 import android.content.pm.UserInfo;
36 import android.graphics.Point;
37 import android.graphics.PorterDuff;
38 import android.graphics.drawable.ColorDrawable;
39 import android.graphics.drawable.Drawable;
40 import android.os.Bundle;
41 import android.os.Handler;
42 import android.os.RemoteException;
43 import android.os.UserManager;
44 import android.text.TextUtils;
45 import android.view.View;
46 import android.view.ViewGroup;
47 import android.widget.Button;
48 import android.widget.FrameLayout;
49 import android.widget.ImageView;
50 import android.widget.TextView;
51 
52 import com.android.internal.widget.LockPatternUtils;
53 import com.android.settings.OptionsMenuFragment;
54 import com.android.settings.R;
55 import com.android.settings.Utils;
56 import com.android.settings.fingerprint.FingerprintUiHelper;
57 
58 /**
59  * Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
60  */
61 public abstract class ConfirmDeviceCredentialBaseFragment extends OptionsMenuFragment
62         implements FingerprintUiHelper.Callback {
63 
64     public static final String PACKAGE = "com.android.settings";
65     public static final String TITLE_TEXT = PACKAGE + ".ConfirmCredentials.title";
66     public static final String HEADER_TEXT = PACKAGE + ".ConfirmCredentials.header";
67     public static final String DETAILS_TEXT = PACKAGE + ".ConfirmCredentials.details";
68     public static final String ALLOW_FP_AUTHENTICATION =
69             PACKAGE + ".ConfirmCredentials.allowFpAuthentication";
70     public static final String DARK_THEME = PACKAGE + ".ConfirmCredentials.darkTheme";
71     public static final String SHOW_CANCEL_BUTTON =
72             PACKAGE + ".ConfirmCredentials.showCancelButton";
73     public static final String SHOW_WHEN_LOCKED =
74             PACKAGE + ".ConfirmCredentials.showWhenLocked";
75 
76     protected static final int USER_TYPE_PRIMARY = 1;
77     protected static final int USER_TYPE_MANAGED_PROFILE = 2;
78     protected static final int USER_TYPE_SECONDARY = 3;
79 
80     /** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */
81     protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000;
82 
83     private FingerprintUiHelper mFingerprintHelper;
84     protected boolean mReturnCredentials = false;
85     protected Button mCancelButton;
86     protected ImageView mFingerprintIcon;
87     protected int mEffectiveUserId;
88     protected int mUserId;
89     protected UserManager mUserManager;
90     protected LockPatternUtils mLockPatternUtils;
91     protected DevicePolicyManager mDevicePolicyManager;
92     protected TextView mErrorTextView;
93     protected final Handler mHandler = new Handler();
94     protected boolean mFrp;
95     private CharSequence mFrpAlternateButtonText;
96 
97     @Override
onCreate(@ullable Bundle savedInstanceState)98     public void onCreate(@Nullable Bundle savedInstanceState) {
99         super.onCreate(savedInstanceState);
100         mFrpAlternateButtonText = getActivity().getIntent().getCharSequenceExtra(
101                 KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
102         mReturnCredentials = getActivity().getIntent().getBooleanExtra(
103                 ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false);
104         // Only take this argument into account if it belongs to the current profile.
105         Intent intent = getActivity().getIntent();
106         mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
107         mFrp = (mUserId == LockPatternUtils.USER_FRP);
108         mUserManager = UserManager.get(getActivity());
109         mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
110         mLockPatternUtils = new LockPatternUtils(getActivity());
111         mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService(
112                 Context.DEVICE_POLICY_SERVICE);
113     }
114 
115     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)116     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
117         super.onViewCreated(view, savedInstanceState);
118         mCancelButton = (Button) view.findViewById(R.id.cancelButton);
119         mFingerprintIcon = (ImageView) view.findViewById(R.id.fingerprintIcon);
120         mFingerprintHelper = new FingerprintUiHelper(
121                 mFingerprintIcon,
122                 (TextView) view.findViewById(R.id.errorText), this, mEffectiveUserId);
123         boolean showCancelButton = getActivity().getIntent().getBooleanExtra(
124                 SHOW_CANCEL_BUTTON, false);
125         boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText);
126         mCancelButton.setVisibility(showCancelButton || hasAlternateButton
127                 ? View.VISIBLE : View.GONE);
128         if (hasAlternateButton) {
129             mCancelButton.setText(mFrpAlternateButtonText);
130         }
131         mCancelButton.setOnClickListener(new View.OnClickListener() {
132             @Override
133             public void onClick(View v) {
134                 if (hasAlternateButton) {
135                     getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
136                 }
137                 getActivity().finish();
138             }
139         });
140         int credentialOwnerUserId = Utils.getCredentialOwnerUserId(
141                 getActivity(),
142                 Utils.getUserIdFromBundle(
143                         getActivity(),
144                         getActivity().getIntent().getExtras()));
145         if (mUserManager.isManagedProfile(credentialOwnerUserId)) {
146             setWorkChallengeBackground(view, credentialOwnerUserId);
147         }
148     }
149 
isFingerprintDisabledByAdmin()150     private boolean isFingerprintDisabledByAdmin() {
151         final int disabledFeatures =
152                 mDevicePolicyManager.getKeyguardDisabledFeatures(null, mEffectiveUserId);
153         return (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0;
154     }
155 
156     // User could be locked while Effective user is unlocked even though the effective owns the
157     // credential. Otherwise, fingerprint can't unlock fbe/keystore through
158     // verifyTiedProfileChallenge. In such case, we also wanna show the user message that
159     // fingerprint is disabled due to device restart.
isStrongAuthRequired()160     protected boolean isStrongAuthRequired() {
161         return mFrp
162                 || !mLockPatternUtils.isFingerprintAllowedForUser(mEffectiveUserId)
163                 || !mUserManager.isUserUnlocked(mUserId);
164     }
165 
isFingerprintAllowed()166     private boolean isFingerprintAllowed() {
167         return !mReturnCredentials
168                 && getActivity().getIntent().getBooleanExtra(ALLOW_FP_AUTHENTICATION, false)
169                 && !isStrongAuthRequired()
170                 && !isFingerprintDisabledByAdmin();
171     }
172 
173     @Override
onResume()174     public void onResume() {
175         super.onResume();
176         refreshLockScreen();
177     }
178 
refreshLockScreen()179     protected void refreshLockScreen() {
180         if (isFingerprintAllowed()) {
181             mFingerprintHelper.startListening();
182         } else {
183             if (mFingerprintHelper.isListening()) {
184                 mFingerprintHelper.stopListening();
185             }
186         }
187         updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
188     }
189 
setAccessibilityTitle(CharSequence supplementalText)190     protected void setAccessibilityTitle(CharSequence supplementalText) {
191         Intent intent = getActivity().getIntent();
192         if (intent != null) {
193             CharSequence titleText = intent.getCharSequenceExtra(
194                     ConfirmDeviceCredentialBaseFragment.TITLE_TEXT);
195             if (supplementalText == null) {
196                 return;
197             }
198             if (titleText == null) {
199                 getActivity().setTitle(supplementalText);
200             } else {
201                 String accessibilityTitle =
202                         new StringBuilder(titleText).append(",").append(supplementalText).toString();
203                 getActivity().setTitle(Utils.createAccessibleSequence(titleText, accessibilityTitle));
204             }
205         }
206     }
207 
208     @Override
onPause()209     public void onPause() {
210         super.onPause();
211         if (mFingerprintHelper.isListening()) {
212             mFingerprintHelper.stopListening();
213         }
214     }
215 
216     @Override
onAuthenticated()217     public void onAuthenticated() {
218         // Check whether we are still active.
219         if (getActivity() != null && getActivity().isResumed()) {
220             TrustManager trustManager =
221                 (TrustManager) getActivity().getSystemService(Context.TRUST_SERVICE);
222             trustManager.setDeviceLockedForUser(mEffectiveUserId, false);
223             authenticationSucceeded();
224             checkForPendingIntent();
225         }
226     }
227 
authenticationSucceeded()228     protected abstract void authenticationSucceeded();
229 
230     @Override
onFingerprintIconVisibilityChanged(boolean visible)231     public void onFingerprintIconVisibilityChanged(boolean visible) {
232     }
233 
prepareEnterAnimation()234     public void prepareEnterAnimation() {
235     }
236 
startEnterAnimation()237     public void startEnterAnimation() {
238     }
239 
checkForPendingIntent()240     protected void checkForPendingIntent() {
241         int taskId = getActivity().getIntent().getIntExtra(Intent.EXTRA_TASK_ID, -1);
242         if (taskId != -1) {
243             try {
244                 IActivityManager activityManager = ActivityManager.getService();
245                 final ActivityOptions options = ActivityOptions.makeBasic();
246                 options.setLaunchStackId(ActivityManager.StackId.INVALID_STACK_ID);
247                 activityManager.startActivityFromRecents(taskId, options.toBundle());
248                 return;
249             } catch (RemoteException e) {
250                 // Do nothing.
251             }
252         }
253         IntentSender intentSender = getActivity().getIntent()
254                 .getParcelableExtra(Intent.EXTRA_INTENT);
255         if (intentSender != null) {
256             try {
257                 getActivity().startIntentSenderForResult(intentSender, -1, null, 0, 0, 0);
258             } catch (IntentSender.SendIntentException e) {
259                 /* ignore */
260             }
261         }
262     }
263 
setWorkChallengeBackground(View baseView, int userId)264     private void setWorkChallengeBackground(View baseView, int userId) {
265         View mainContent = getActivity().findViewById(com.android.settings.R.id.main_content);
266         if (mainContent != null) {
267             // Remove the main content padding so that the background image is full screen.
268             mainContent.setPadding(0, 0, 0, 0);
269         }
270 
271         baseView.setBackground(
272                 new ColorDrawable(mDevicePolicyManager.getOrganizationColorForUser(userId)));
273         ImageView imageView = (ImageView) baseView.findViewById(R.id.background_image);
274         if (imageView != null) {
275             Drawable image = getResources().getDrawable(R.drawable.work_challenge_background);
276             image.setColorFilter(
277                     getResources().getColor(R.color.confirm_device_credential_transparent_black),
278                     PorterDuff.Mode.DARKEN);
279             imageView.setImageDrawable(image);
280             Point screenSize = new Point();
281             getActivity().getWindowManager().getDefaultDisplay().getSize(screenSize);
282             imageView.setLayoutParams(new FrameLayout.LayoutParams(
283                     ViewGroup.LayoutParams.MATCH_PARENT,
284                     screenSize.y));
285         }
286     }
287 
reportSuccessfulAttempt()288     protected void reportSuccessfulAttempt() {
289         mLockPatternUtils.reportSuccessfulPasswordAttempt(mEffectiveUserId);
290         if (mUserManager.isManagedProfile(mEffectiveUserId)) {
291             // Keyguard is responsible to disable StrongAuth for primary user. Disable StrongAuth
292             // for work challenge only here.
293             mLockPatternUtils.userPresent(mEffectiveUserId);
294         }
295     }
296 
reportFailedAttempt()297     protected void reportFailedAttempt() {
298         updateErrorMessage(
299                 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
300         mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
301     }
302 
updateErrorMessage(int numAttempts)303     protected void updateErrorMessage(int numAttempts) {
304         final int maxAttempts =
305                 mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId);
306         if (maxAttempts <= 0 || numAttempts <= 0) {
307             return;
308         }
309 
310         // Update the on-screen error string
311         if (mErrorTextView != null) {
312             final String message = getActivity().getString(
313                     R.string.lock_failed_attempts_before_wipe, numAttempts, maxAttempts);
314             showError(message, 0);
315         }
316 
317         // Only show popup dialog before the last attempt and before wipe
318         final int remainingAttempts = maxAttempts - numAttempts;
319         if (remainingAttempts > 1) {
320             return;
321         }
322         final FragmentManager fragmentManager = getChildFragmentManager();
323         final int userType = getUserTypeForWipe();
324         if (remainingAttempts == 1) {
325             // Last try
326             final String title = getActivity().getString(
327                     R.string.lock_last_attempt_before_wipe_warning_title);
328             final int messageId = getLastTryErrorMessage(userType);
329             LastTryDialog.show(fragmentManager, title, messageId,
330                     android.R.string.ok, false /* dismiss */);
331         } else {
332             // Device, profile, or secondary user is wiped
333             final int messageId = getWipeMessage(userType);
334             LastTryDialog.show(fragmentManager, null /* title */, messageId,
335                     R.string.lock_failed_attempts_now_wiping_dialog_dismiss, true /* dismiss */);
336         }
337     }
338 
getUserTypeForWipe()339     private int getUserTypeForWipe() {
340         final UserInfo userToBeWiped = mUserManager.getUserInfo(
341                 mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
342         if (userToBeWiped == null || userToBeWiped.isPrimary()) {
343             return USER_TYPE_PRIMARY;
344         } else if (userToBeWiped.isManagedProfile()) {
345             return USER_TYPE_MANAGED_PROFILE;
346         } else {
347             return USER_TYPE_SECONDARY;
348         }
349     }
350 
getLastTryErrorMessage(int userType)351     protected abstract int getLastTryErrorMessage(int userType);
352 
getWipeMessage(int userType)353     private int getWipeMessage(int userType) {
354         switch (userType) {
355             case USER_TYPE_PRIMARY:
356                 return R.string.lock_failed_attempts_now_wiping_device;
357             case USER_TYPE_MANAGED_PROFILE:
358                 return R.string.lock_failed_attempts_now_wiping_profile;
359             case USER_TYPE_SECONDARY:
360                 return R.string.lock_failed_attempts_now_wiping_user;
361             default:
362                 throw new IllegalArgumentException("Unrecognized user type:" + userType);
363         }
364     }
365 
366     private final Runnable mResetErrorRunnable = new Runnable() {
367         @Override
368         public void run() {
369             mErrorTextView.setText("");
370         }
371     };
372 
showError(CharSequence msg, long timeout)373     protected void showError(CharSequence msg, long timeout) {
374         mErrorTextView.setText(msg);
375         onShowError();
376         mHandler.removeCallbacks(mResetErrorRunnable);
377         if (timeout != 0) {
378             mHandler.postDelayed(mResetErrorRunnable, timeout);
379         }
380     }
381 
onShowError()382     protected abstract void onShowError();
383 
showError(int msg, long timeout)384     protected void showError(int msg, long timeout) {
385         showError(getText(msg), timeout);
386     }
387 
388     public static class LastTryDialog extends DialogFragment {
389         private static final String TAG = LastTryDialog.class.getSimpleName();
390 
391         private static final String ARG_TITLE = "title";
392         private static final String ARG_MESSAGE = "message";
393         private static final String ARG_BUTTON = "button";
394         private static final String ARG_DISMISS = "dismiss";
395 
show(FragmentManager from, String title, int message, int button, boolean dismiss)396         static boolean show(FragmentManager from, String title, int message, int button,
397                 boolean dismiss) {
398             LastTryDialog existent = (LastTryDialog) from.findFragmentByTag(TAG);
399             if (existent != null && !existent.isRemoving()) {
400                 return false;
401             }
402             Bundle args = new Bundle();
403             args.putString(ARG_TITLE, title);
404             args.putInt(ARG_MESSAGE, message);
405             args.putInt(ARG_BUTTON, button);
406             args.putBoolean(ARG_DISMISS, dismiss);
407 
408             DialogFragment dialog = new LastTryDialog();
409             dialog.setArguments(args);
410             dialog.show(from, TAG);
411             from.executePendingTransactions();
412             return true;
413         }
414 
hide(FragmentManager from)415         static void hide(FragmentManager from) {
416             LastTryDialog dialog = (LastTryDialog) from.findFragmentByTag(TAG);
417             if (dialog != null) {
418                 dialog.dismissAllowingStateLoss();
419                 from.executePendingTransactions();
420             }
421         }
422 
423         /**
424          * Dialog setup.
425          * <p>
426          * To make it less likely that the dialog is dismissed accidentally, for example if the
427          * device is malfunctioning or if the device is in a pocket, we set
428          * {@code setCanceledOnTouchOutside(false)}.
429          */
430         @Override
onCreateDialog(Bundle savedInstanceState)431         public Dialog onCreateDialog(Bundle savedInstanceState) {
432             Dialog dialog = new AlertDialog.Builder(getActivity())
433                     .setTitle(getArguments().getString(ARG_TITLE))
434                     .setMessage(getArguments().getInt(ARG_MESSAGE))
435                     .setPositiveButton(getArguments().getInt(ARG_BUTTON), null)
436                     .create();
437             dialog.setCanceledOnTouchOutside(false);
438             return dialog;
439         }
440 
441         @Override
onDismiss(final DialogInterface dialog)442         public void onDismiss(final DialogInterface dialog) {
443             super.onDismiss(dialog);
444             if (getActivity() != null && getArguments().getBoolean(ARG_DISMISS)) {
445                 getActivity().finish();
446             }
447         }
448     }
449 }
450