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