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