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