1 /*
2  * Copyright (C) 2014 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 package com.android.keyguard;
17 
18 import android.app.Activity;
19 import android.app.AlertDialog;
20 import android.app.admin.DevicePolicyManager;
21 import android.content.Context;
22 import android.os.UserHandle;
23 import android.util.AttributeSet;
24 import android.util.Log;
25 import android.util.Slog;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.WindowManager;
29 import android.widget.FrameLayout;
30 
31 import com.android.internal.widget.LockPatternUtils;
32 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
33 
34 public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView {
35     private static final boolean DEBUG = KeyguardConstants.DEBUG;
36     private static final String TAG = "KeyguardSecurityView";
37 
38     private static final int USER_TYPE_PRIMARY = 1;
39     private static final int USER_TYPE_WORK_PROFILE = 2;
40     private static final int USER_TYPE_SECONDARY_USER = 3;
41 
42     private KeyguardSecurityModel mSecurityModel;
43     private LockPatternUtils mLockPatternUtils;
44 
45     private KeyguardSecurityViewFlipper mSecurityViewFlipper;
46     private boolean mIsVerifyUnlockOnly;
47     private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid;
48     private SecurityCallback mSecurityCallback;
49 
50     private final KeyguardUpdateMonitor mUpdateMonitor;
51 
52     // Used to notify the container when something interesting happens.
53     public interface SecurityCallback {
dismiss(boolean authenticated)54         public boolean dismiss(boolean authenticated);
userActivity()55         public void userActivity();
onSecurityModeChanged(SecurityMode securityMode, boolean needsInput)56         public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
finish()57         public void finish();
reset()58         public void reset();
59     }
60 
KeyguardSecurityContainer(Context context, AttributeSet attrs)61     public KeyguardSecurityContainer(Context context, AttributeSet attrs) {
62         this(context, attrs, 0);
63     }
64 
KeyguardSecurityContainer(Context context)65     public KeyguardSecurityContainer(Context context) {
66         this(context, null, 0);
67     }
68 
KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle)69     public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) {
70         super(context, attrs, defStyle);
71         mSecurityModel = new KeyguardSecurityModel(context);
72         mLockPatternUtils = new LockPatternUtils(context);
73         mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
74     }
75 
setSecurityCallback(SecurityCallback callback)76     public void setSecurityCallback(SecurityCallback callback) {
77         mSecurityCallback = callback;
78     }
79 
80     @Override
onResume(int reason)81     public void onResume(int reason) {
82         if (mCurrentSecuritySelection != SecurityMode.None) {
83             getSecurityView(mCurrentSecuritySelection).onResume(reason);
84         }
85     }
86 
87     @Override
onPause()88     public void onPause() {
89         if (mCurrentSecuritySelection != SecurityMode.None) {
90             getSecurityView(mCurrentSecuritySelection).onPause();
91         }
92     }
93 
startAppearAnimation()94     public void startAppearAnimation() {
95         if (mCurrentSecuritySelection != SecurityMode.None) {
96             getSecurityView(mCurrentSecuritySelection).startAppearAnimation();
97         }
98     }
99 
startDisappearAnimation(Runnable onFinishRunnable)100     public boolean startDisappearAnimation(Runnable onFinishRunnable) {
101         if (mCurrentSecuritySelection != SecurityMode.None) {
102             return getSecurityView(mCurrentSecuritySelection).startDisappearAnimation(
103                     onFinishRunnable);
104         }
105         return false;
106     }
107 
announceCurrentSecurityMethod()108     public void announceCurrentSecurityMethod() {
109         View v = (View) getSecurityView(mCurrentSecuritySelection);
110         if (v != null) {
111             v.announceForAccessibility(v.getContentDescription());
112         }
113     }
114 
getCurrentSecurityModeContentDescription()115     public CharSequence getCurrentSecurityModeContentDescription() {
116         View v = (View) getSecurityView(mCurrentSecuritySelection);
117         if (v != null) {
118             return v.getContentDescription();
119         }
120         return "";
121     }
122 
getSecurityView(SecurityMode securityMode)123     private KeyguardSecurityView getSecurityView(SecurityMode securityMode) {
124         final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
125         KeyguardSecurityView view = null;
126         final int children = mSecurityViewFlipper.getChildCount();
127         for (int child = 0; child < children; child++) {
128             if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) {
129                 view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child));
130                 break;
131             }
132         }
133         int layoutId = getLayoutIdFor(securityMode);
134         if (view == null && layoutId != 0) {
135             final LayoutInflater inflater = LayoutInflater.from(mContext);
136             if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
137             View v = inflater.inflate(layoutId, mSecurityViewFlipper, false);
138             mSecurityViewFlipper.addView(v);
139             updateSecurityView(v);
140             view = (KeyguardSecurityView)v;
141         }
142 
143         return view;
144     }
145 
updateSecurityView(View view)146     private void updateSecurityView(View view) {
147         if (view instanceof KeyguardSecurityView) {
148             KeyguardSecurityView ksv = (KeyguardSecurityView) view;
149             ksv.setKeyguardCallback(mCallback);
150             ksv.setLockPatternUtils(mLockPatternUtils);
151         } else {
152             Log.w(TAG, "View " + view + " is not a KeyguardSecurityView");
153         }
154     }
155 
onFinishInflate()156     protected void onFinishInflate() {
157         mSecurityViewFlipper = (KeyguardSecurityViewFlipper) findViewById(R.id.view_flipper);
158         mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
159     }
160 
setLockPatternUtils(LockPatternUtils utils)161     public void setLockPatternUtils(LockPatternUtils utils) {
162         mLockPatternUtils = utils;
163         mSecurityModel.setLockPatternUtils(utils);
164         mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
165     }
166 
showDialog(String title, String message)167     private void showDialog(String title, String message) {
168         final AlertDialog dialog = new AlertDialog.Builder(mContext)
169             .setTitle(title)
170             .setMessage(message)
171             .setNeutralButton(R.string.ok, null)
172             .create();
173         if (!(mContext instanceof Activity)) {
174             dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
175         }
176         dialog.show();
177     }
178 
showTimeoutDialog(int timeoutMs)179     private void showTimeoutDialog(int timeoutMs) {
180         int timeoutInSeconds = (int) timeoutMs / 1000;
181         int messageId = 0;
182 
183         switch (mSecurityModel.getSecurityMode()) {
184             case Pattern:
185                 messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message;
186                 break;
187             case PIN:
188                 messageId = R.string.kg_too_many_failed_pin_attempts_dialog_message;
189                 break;
190             case Password:
191                 messageId = R.string.kg_too_many_failed_password_attempts_dialog_message;
192                 break;
193             // These don't have timeout dialogs.
194             case Invalid:
195             case None:
196             case SimPin:
197             case SimPuk:
198                 break;
199         }
200 
201         if (messageId != 0) {
202             final String message = mContext.getString(messageId,
203                     KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(),
204                     timeoutInSeconds);
205             showDialog(null, message);
206         }
207     }
208 
showAlmostAtWipeDialog(int attempts, int remaining, int userType)209     private void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
210         String message = null;
211         switch (userType) {
212             case USER_TYPE_PRIMARY:
213                 message = mContext.getString(R.string.kg_failed_attempts_almost_at_wipe,
214                         attempts, remaining);
215                 break;
216             case USER_TYPE_SECONDARY_USER:
217                 message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_user,
218                         attempts, remaining);
219                 break;
220             case USER_TYPE_WORK_PROFILE:
221                 message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_profile,
222                         attempts, remaining);
223                 break;
224         }
225         showDialog(null, message);
226     }
227 
showWipeDialog(int attempts, int userType)228     private void showWipeDialog(int attempts, int userType) {
229         String message = null;
230         switch (userType) {
231             case USER_TYPE_PRIMARY:
232                 message = mContext.getString(R.string.kg_failed_attempts_now_wiping,
233                         attempts);
234                 break;
235             case USER_TYPE_SECONDARY_USER:
236                 message = mContext.getString(R.string.kg_failed_attempts_now_erasing_user,
237                         attempts);
238                 break;
239             case USER_TYPE_WORK_PROFILE:
240                 message = mContext.getString(R.string.kg_failed_attempts_now_erasing_profile,
241                         attempts);
242                 break;
243         }
244         showDialog(null, message);
245     }
246 
reportFailedUnlockAttempt(int timeoutMs)247     private void reportFailedUnlockAttempt(int timeoutMs) {
248         final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
249         final int failedAttempts = monitor.getFailedUnlockAttempts() + 1; // +1 for this time
250 
251         if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
252 
253         SecurityMode mode = mSecurityModel.getSecurityMode();
254         final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
255         final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
256         final int failedAttemptsBeforeWipe =
257                 dpm.getMaximumFailedPasswordsForWipe(null, currentUser);
258 
259         final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ?
260                 (failedAttemptsBeforeWipe - failedAttempts)
261                 : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
262         boolean showTimeout = false;
263         if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
264             // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
265             // N attempts. Once we get below the grace period, we post this dialog every time as a
266             // clear warning until the deletion fires.
267             // Check which profile has the strictest policy for failed password attempts
268             final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(currentUser);
269             int userType = USER_TYPE_PRIMARY;
270             if (expiringUser == currentUser) {
271                 if (expiringUser != UserHandle.USER_OWNER) {
272                     userType = USER_TYPE_SECONDARY_USER;
273                 }
274             } else if (expiringUser != UserHandle.USER_NULL) {
275                 userType = USER_TYPE_WORK_PROFILE;
276             } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
277             if (remainingBeforeWipe > 0) {
278                 showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
279             } else {
280                 // Too many attempts. The device will be wiped shortly.
281                 Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
282                 showWipeDialog(failedAttempts, userType);
283             }
284         }
285         monitor.reportFailedUnlockAttempt();
286         mLockPatternUtils.reportFailedPasswordAttempt(KeyguardUpdateMonitor.getCurrentUser());
287         if (timeoutMs > 0) {
288             showTimeoutDialog(timeoutMs);
289         }
290     }
291 
292     /**
293      * Shows the primary security screen for the user. This will be either the multi-selector
294      * or the user's security method.
295      * @param turningOff true if the device is being turned off
296      */
showPrimarySecurityScreen(boolean turningOff)297     void showPrimarySecurityScreen(boolean turningOff) {
298         SecurityMode securityMode = mSecurityModel.getSecurityMode();
299         if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")");
300         showSecurityScreen(securityMode);
301     }
302 
303     /**
304      * Shows the next security screen if there is one.
305      * @param authenticated true if the user entered the correct authentication
306      * @return true if keyguard is done
307      */
showNextSecurityScreenOrFinish(boolean authenticated)308     boolean showNextSecurityScreenOrFinish(boolean authenticated) {
309         if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
310         boolean finish = false;
311         if (mUpdateMonitor.getUserCanSkipBouncer(
312                 KeyguardUpdateMonitor.getCurrentUser())) {
313             finish = true;
314         } else if (SecurityMode.None == mCurrentSecuritySelection) {
315             SecurityMode securityMode = mSecurityModel.getSecurityMode();
316             if (SecurityMode.None == securityMode) {
317                 finish = true; // no security required
318             } else {
319                 showSecurityScreen(securityMode); // switch to the alternate security view
320             }
321         } else if (authenticated) {
322             switch (mCurrentSecuritySelection) {
323                 case Pattern:
324                 case Password:
325                 case PIN:
326                     finish = true;
327                     break;
328 
329                 case SimPin:
330                 case SimPuk:
331                     // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
332                     SecurityMode securityMode = mSecurityModel.getSecurityMode();
333                     if (securityMode != SecurityMode.None
334                             || !mLockPatternUtils.isLockScreenDisabled(
335                             KeyguardUpdateMonitor.getCurrentUser())) {
336                         showSecurityScreen(securityMode);
337                     } else {
338                         finish = true;
339                     }
340                     break;
341 
342                 default:
343                     Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe");
344                     showPrimarySecurityScreen(false);
345                     break;
346             }
347         }
348         if (finish) {
349             mSecurityCallback.finish();
350         }
351         return finish;
352     }
353 
354     /**
355      * Switches to the given security view unless it's already being shown, in which case
356      * this is a no-op.
357      *
358      * @param securityMode
359      */
showSecurityScreen(SecurityMode securityMode)360     private void showSecurityScreen(SecurityMode securityMode) {
361         if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")");
362 
363         if (securityMode == mCurrentSecuritySelection) return;
364 
365         KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection);
366         KeyguardSecurityView newView = getSecurityView(securityMode);
367 
368         // Emulate Activity life cycle
369         if (oldView != null) {
370             oldView.onPause();
371             oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view
372         }
373         if (securityMode != SecurityMode.None) {
374             newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
375             newView.setKeyguardCallback(mCallback);
376         }
377 
378         // Find and show this child.
379         final int childCount = mSecurityViewFlipper.getChildCount();
380 
381         final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
382         for (int i = 0; i < childCount; i++) {
383             if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) {
384                 mSecurityViewFlipper.setDisplayedChild(i);
385                 break;
386             }
387         }
388 
389         mCurrentSecuritySelection = securityMode;
390         mSecurityCallback.onSecurityModeChanged(securityMode,
391                 securityMode != SecurityMode.None && newView.needsInput());
392     }
393 
getFlipper()394     private KeyguardSecurityViewFlipper getFlipper() {
395         for (int i = 0; i < getChildCount(); i++) {
396             View child = getChildAt(i);
397             if (child instanceof KeyguardSecurityViewFlipper) {
398                 return (KeyguardSecurityViewFlipper) child;
399             }
400         }
401         return null;
402     }
403 
404     private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() {
405         public void userActivity() {
406             if (mSecurityCallback != null) {
407                 mSecurityCallback.userActivity();
408             }
409         }
410 
411         public void dismiss(boolean authenticated) {
412             mSecurityCallback.dismiss(authenticated);
413         }
414 
415         public boolean isVerifyUnlockOnly() {
416             return mIsVerifyUnlockOnly;
417         }
418 
419         public void reportUnlockAttempt(boolean success, int timeoutMs) {
420             KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
421             if (success) {
422                 monitor.clearFailedUnlockAttempts();
423                 mLockPatternUtils.reportSuccessfulPasswordAttempt(
424                         KeyguardUpdateMonitor.getCurrentUser());
425             } else {
426                 KeyguardSecurityContainer.this.reportFailedUnlockAttempt(timeoutMs);
427             }
428         }
429 
430         public void reset() {
431             mSecurityCallback.reset();
432         }
433     };
434 
435     // The following is used to ignore callbacks from SecurityViews that are no longer current
436     // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
437     // state for the current security method.
438     private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {
439         @Override
440         public void userActivity() { }
441         @Override
442         public void reportUnlockAttempt(boolean success, int timeoutMs) { }
443         @Override
444         public boolean isVerifyUnlockOnly() { return false; }
445         @Override
446         public void dismiss(boolean securityVerified) { }
447         @Override
448         public void reset() {}
449     };
450 
getSecurityViewIdForMode(SecurityMode securityMode)451     private int getSecurityViewIdForMode(SecurityMode securityMode) {
452         switch (securityMode) {
453             case Pattern: return R.id.keyguard_pattern_view;
454             case PIN: return R.id.keyguard_pin_view;
455             case Password: return R.id.keyguard_password_view;
456             case SimPin: return R.id.keyguard_sim_pin_view;
457             case SimPuk: return R.id.keyguard_sim_puk_view;
458         }
459         return 0;
460     }
461 
getLayoutIdFor(SecurityMode securityMode)462     private int getLayoutIdFor(SecurityMode securityMode) {
463         switch (securityMode) {
464             case Pattern: return R.layout.keyguard_pattern_view;
465             case PIN: return R.layout.keyguard_pin_view;
466             case Password: return R.layout.keyguard_password_view;
467             case SimPin: return R.layout.keyguard_sim_pin_view;
468             case SimPuk: return R.layout.keyguard_sim_puk_view;
469             default:
470                 return 0;
471         }
472     }
473 
getSecurityMode()474     public SecurityMode getSecurityMode() {
475         return mSecurityModel.getSecurityMode();
476     }
477 
getCurrentSecurityMode()478     public SecurityMode getCurrentSecurityMode() {
479         return mCurrentSecuritySelection;
480     }
481 
verifyUnlock()482     public void verifyUnlock() {
483         mIsVerifyUnlockOnly = true;
484         showSecurityScreen(getSecurityMode());
485     }
486 
getCurrentSecuritySelection()487     public SecurityMode getCurrentSecuritySelection() {
488         return mCurrentSecuritySelection;
489     }
490 
dismiss(boolean authenticated)491     public void dismiss(boolean authenticated) {
492         mCallback.dismiss(authenticated);
493     }
494 
needsInput()495     public boolean needsInput() {
496         return mSecurityViewFlipper.needsInput();
497     }
498 
499     @Override
setKeyguardCallback(KeyguardSecurityCallback callback)500     public void setKeyguardCallback(KeyguardSecurityCallback callback) {
501         mSecurityViewFlipper.setKeyguardCallback(callback);
502     }
503 
504     @Override
reset()505     public void reset() {
506         mSecurityViewFlipper.reset();
507     }
508 
509     @Override
getCallback()510     public KeyguardSecurityCallback getCallback() {
511         return mSecurityViewFlipper.getCallback();
512     }
513 
514     @Override
showPromptReason(int reason)515     public void showPromptReason(int reason) {
516         if (mCurrentSecuritySelection != SecurityMode.None) {
517             getSecurityView(mCurrentSecuritySelection).showPromptReason(reason);
518         }
519     }
520 
521     @Override
showUsabilityHint()522     public void showUsabilityHint() {
523         mSecurityViewFlipper.showUsabilityHint();
524     }
525 
526 }
527 
528