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