1/*
2 * Copyright (C) 2010 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
17package com.android.settings;
18
19import android.text.TextUtils;
20import com.android.internal.logging.MetricsLogger;
21import com.android.internal.widget.LockPatternChecker;
22import com.android.internal.widget.LockPatternUtils;
23import com.android.internal.widget.TextViewInputDisabler;
24import com.android.settingslib.animation.AppearAnimationUtils;
25import com.android.settingslib.animation.DisappearAnimationUtils;
26
27import android.app.Fragment;
28import android.app.admin.DevicePolicyManager;
29import android.content.Intent;
30import android.content.Context;
31import android.os.AsyncTask;
32import android.os.Bundle;
33import android.os.CountDownTimer;
34import android.os.Handler;
35import android.os.SystemClock;
36import android.os.storage.StorageManager;
37import android.text.InputType;
38import android.view.KeyEvent;
39import android.view.LayoutInflater;
40import android.view.View;
41import android.view.View.OnClickListener;
42import android.view.ViewGroup;
43import android.view.animation.AnimationUtils;
44import android.view.inputmethod.EditorInfo;
45import android.view.inputmethod.InputMethodManager;
46import android.widget.TextView;
47import android.widget.TextView.OnEditorActionListener;
48
49import java.util.ArrayList;
50
51public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
52
53    public static class InternalActivity extends ConfirmLockPassword {
54    }
55
56    @Override
57    public Intent getIntent() {
58        Intent modIntent = new Intent(super.getIntent());
59        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPasswordFragment.class.getName());
60        return modIntent;
61    }
62
63    @Override
64    protected boolean isValidFragment(String fragmentName) {
65        if (ConfirmLockPasswordFragment.class.getName().equals(fragmentName)) return true;
66        return false;
67    }
68
69    @Override
70    public void onWindowFocusChanged(boolean hasFocus) {
71        super.onWindowFocusChanged(hasFocus);
72        Fragment fragment = getFragmentManager().findFragmentById(R.id.main_content);
73        if (fragment != null && fragment instanceof ConfirmLockPasswordFragment) {
74            ((ConfirmLockPasswordFragment)fragment).onWindowFocusChanged(hasFocus);
75        }
76    }
77
78    public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment
79            implements OnClickListener, OnEditorActionListener,
80            CredentialCheckResultTracker.Listener {
81        private static final long ERROR_MESSAGE_TIMEOUT = 3000;
82        private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
83        private TextView mPasswordEntry;
84        private TextViewInputDisabler mPasswordEntryInputDisabler;
85        private LockPatternUtils mLockPatternUtils;
86        private AsyncTask<?, ?, ?> mPendingLockCheck;
87        private CredentialCheckResultTracker mCredentialCheckResultTracker;
88        private boolean mDisappearing = false;
89        private TextView mHeaderTextView;
90        private TextView mDetailsTextView;
91        private TextView mErrorTextView;
92        private Handler mHandler = new Handler();
93        private CountDownTimer mCountdownTimer;
94        private boolean mIsAlpha;
95        private InputMethodManager mImm;
96        private boolean mUsingFingerprint = false;
97        private AppearAnimationUtils mAppearAnimationUtils;
98        private DisappearAnimationUtils mDisappearAnimationUtils;
99        private boolean mBlockImm;
100        private int mEffectiveUserId;
101
102        // required constructor for fragments
103        public ConfirmLockPasswordFragment() {
104
105        }
106
107        @Override
108        public void onCreate(Bundle savedInstanceState) {
109            super.onCreate(savedInstanceState);
110            mLockPatternUtils = new LockPatternUtils(getActivity());
111            mEffectiveUserId = Utils.getEffectiveUserId(getActivity());
112        }
113
114        @Override
115        public View onCreateView(LayoutInflater inflater, ViewGroup container,
116                Bundle savedInstanceState) {
117            final int storedQuality = mLockPatternUtils.getKeyguardStoredPasswordQuality(
118                    mEffectiveUserId);
119            View view = inflater.inflate(R.layout.confirm_lock_password, null);
120
121            mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
122            mPasswordEntry.setOnEditorActionListener(this);
123            mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
124
125            mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
126            mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
127            mErrorTextView = (TextView) view.findViewById(R.id.errorText);
128            mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality
129                    || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality
130                    || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == storedQuality;
131
132            mImm = (InputMethodManager) getActivity().getSystemService(
133                    Context.INPUT_METHOD_SERVICE);
134
135            Intent intent = getActivity().getIntent();
136            if (intent != null) {
137                CharSequence headerMessage = intent.getCharSequenceExtra(
138                        ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
139                CharSequence detailsMessage = intent.getCharSequenceExtra(
140                        ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
141                if (TextUtils.isEmpty(headerMessage)) {
142                    headerMessage = getString(getDefaultHeader());
143                }
144                if (TextUtils.isEmpty(detailsMessage)) {
145                    detailsMessage = getString(getDefaultDetails());
146                }
147                mHeaderTextView.setText(headerMessage);
148                mDetailsTextView.setText(detailsMessage);
149            }
150            int currentType = mPasswordEntry.getInputType();
151            mPasswordEntry.setInputType(mIsAlpha ? currentType
152                    : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
153            mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
154                    220, 2f /* translationScale */, 1f /* delayScale*/,
155                    AnimationUtils.loadInterpolator(getContext(),
156                            android.R.interpolator.linear_out_slow_in));
157            mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
158                    110, 1f /* translationScale */,
159                    0.5f /* delayScale */, AnimationUtils.loadInterpolator(
160                            getContext(), android.R.interpolator.fast_out_linear_in));
161            setAccessibilityTitle(mHeaderTextView.getText());
162
163            mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
164                    .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
165            if (mCredentialCheckResultTracker == null) {
166                mCredentialCheckResultTracker = new CredentialCheckResultTracker();
167                getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
168                        FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
169            }
170
171            return view;
172        }
173
174        private int getDefaultHeader() {
175            return mIsAlpha ? R.string.lockpassword_confirm_your_password_header
176                    : R.string.lockpassword_confirm_your_pin_header;
177        }
178
179        private int getDefaultDetails() {
180            return mIsAlpha ? R.string.lockpassword_confirm_your_password_generic
181                    : R.string.lockpassword_confirm_your_pin_generic;
182        }
183
184        private int getErrorMessage() {
185            return mIsAlpha ? R.string.lockpassword_invalid_password
186                    : R.string.lockpassword_invalid_pin;
187        }
188
189        @Override
190        public void prepareEnterAnimation() {
191            super.prepareEnterAnimation();
192            mHeaderTextView.setAlpha(0f);
193            mDetailsTextView.setAlpha(0f);
194            mCancelButton.setAlpha(0f);
195            mPasswordEntry.setAlpha(0f);
196            mFingerprintIcon.setAlpha(0f);
197            mBlockImm = true;
198        }
199
200        private View[] getActiveViews() {
201            ArrayList<View> result = new ArrayList<>();
202            result.add(mHeaderTextView);
203            result.add(mDetailsTextView);
204            if (mCancelButton.getVisibility() == View.VISIBLE) {
205                result.add(mCancelButton);
206            }
207            result.add(mPasswordEntry);
208            if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
209                result.add(mFingerprintIcon);
210            }
211            return result.toArray(new View[] {});
212        }
213
214        @Override
215        public void startEnterAnimation() {
216            super.startEnterAnimation();
217            mAppearAnimationUtils.startAnimation(getActiveViews(), new Runnable() {
218                @Override
219                public void run() {
220                    mBlockImm = false;
221                    resetState();
222                }
223            });
224        }
225
226        @Override
227        public void onPause() {
228            super.onPause();
229            if (mCountdownTimer != null) {
230                mCountdownTimer.cancel();
231                mCountdownTimer = null;
232            }
233            mCredentialCheckResultTracker.setListener(null);
234        }
235
236        @Override
237        protected int getMetricsCategory() {
238            return MetricsLogger.CONFIRM_LOCK_PASSWORD;
239        }
240
241        @Override
242        public void onResume() {
243            super.onResume();
244            long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
245            if (deadline != 0) {
246                mCredentialCheckResultTracker.clearResult();
247                handleAttemptLockout(deadline);
248            } else {
249                resetState();
250            }
251            mCredentialCheckResultTracker.setListener(this);
252        }
253
254        @Override
255        protected void authenticationSucceeded() {
256            mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
257        }
258
259        @Override
260        public void onFingerprintIconVisibilityChanged(boolean visible) {
261            mUsingFingerprint = visible;
262        }
263
264        private void resetState() {
265            if (mBlockImm) return;
266            mPasswordEntry.setEnabled(true);
267            mPasswordEntryInputDisabler.setInputEnabled(true);
268            if (shouldAutoShowSoftKeyboard()) {
269                mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
270            }
271        }
272
273        private boolean shouldAutoShowSoftKeyboard() {
274            return mPasswordEntry.isEnabled() && !mUsingFingerprint;
275        }
276
277        public void onWindowFocusChanged(boolean hasFocus) {
278            if (!hasFocus || mBlockImm) {
279                return;
280            }
281            // Post to let window focus logic to finish to allow soft input show/hide properly.
282            mPasswordEntry.post(new Runnable() {
283                @Override
284                public void run() {
285                    if (shouldAutoShowSoftKeyboard()) {
286                        resetState();
287                        return;
288                    }
289
290                    mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(),
291                            InputMethodManager.HIDE_IMPLICIT_ONLY);
292                }
293            });
294        }
295
296        private void handleNext() {
297            if (mPendingLockCheck != null || mDisappearing) {
298                return;
299            }
300
301            mPasswordEntryInputDisabler.setInputEnabled(false);
302
303            final String pin = mPasswordEntry.getText().toString();
304            final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
305                    ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
306            Intent intent = new Intent();
307            if (verifyChallenge)  {
308                if (isInternalActivity()) {
309                    startVerifyPassword(pin, intent);
310                    return;
311                }
312            } else {
313                startCheckPassword(pin, intent);
314                return;
315            }
316
317            mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
318        }
319
320        private boolean isInternalActivity() {
321            return getActivity() instanceof ConfirmLockPassword.InternalActivity;
322        }
323
324        private void startVerifyPassword(final String pin, final Intent intent) {
325            long challenge = getActivity().getIntent().getLongExtra(
326                    ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
327            final int localEffectiveUserId = mEffectiveUserId;
328            mPendingLockCheck = LockPatternChecker.verifyPassword(
329                    mLockPatternUtils,
330                    pin,
331                    challenge,
332                    localEffectiveUserId,
333                    new LockPatternChecker.OnVerifyCallback() {
334                        @Override
335                        public void onVerified(byte[] token, int timeoutMs) {
336                            mPendingLockCheck = null;
337                            boolean matched = false;
338                            if (token != null) {
339                                matched = true;
340                                intent.putExtra(
341                                        ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
342                                        token);
343                            }
344                            mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
345                                    localEffectiveUserId);
346                        }
347                    });
348        }
349
350        private void startCheckPassword(final String pin, final Intent intent) {
351            final int localEffectiveUserId = mEffectiveUserId;
352            mPendingLockCheck = LockPatternChecker.checkPassword(
353                    mLockPatternUtils,
354                    pin,
355                    localEffectiveUserId,
356                    new LockPatternChecker.OnCheckCallback() {
357                        @Override
358                        public void onChecked(boolean matched, int timeoutMs) {
359                            mPendingLockCheck = null;
360                            if (matched && isInternalActivity()) {
361                                intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
362                                                mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD
363                                                         : StorageManager.CRYPT_TYPE_PIN);
364                                intent.putExtra(
365                                        ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
366                            }
367                            mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
368                                    localEffectiveUserId);
369                        }
370                    });
371        }
372
373        private void startDisappearAnimation(final Intent intent) {
374            if (mDisappearing) {
375                return;
376            }
377            mDisappearing = true;
378
379            if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
380                mDisappearAnimationUtils.startAnimation(getActiveViews(), new Runnable() {
381                    @Override
382                    public void run() {
383                        // Bail if there is no active activity.
384                        if (getActivity() == null || getActivity().isFinishing()) {
385                            return;
386                        }
387
388                        getActivity().setResult(RESULT_OK, intent);
389                        getActivity().finish();
390                        getActivity().overridePendingTransition(
391                                R.anim.confirm_credential_close_enter,
392                                R.anim.confirm_credential_close_exit);
393                    }
394                });
395            } else {
396                getActivity().setResult(RESULT_OK, intent);
397                getActivity().finish();
398            }
399        }
400
401        private void onPasswordChecked(boolean matched, Intent intent, int timeoutMs,
402                int effectiveUserId) {
403            mPasswordEntryInputDisabler.setInputEnabled(true);
404            if (matched) {
405                startDisappearAnimation(intent);
406            } else {
407                if (timeoutMs > 0) {
408                    long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
409                            effectiveUserId, timeoutMs);
410                    handleAttemptLockout(deadline);
411                } else {
412                    showError(getErrorMessage());
413                }
414            }
415        }
416
417        @Override
418        public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
419                int effectiveUserId) {
420            onPasswordChecked(matched, intent, timeoutMs, effectiveUserId);
421        }
422
423        private void handleAttemptLockout(long elapsedRealtimeDeadline) {
424            long elapsedRealtime = SystemClock.elapsedRealtime();
425            mPasswordEntry.setEnabled(false);
426            mCountdownTimer = new CountDownTimer(
427                    elapsedRealtimeDeadline - elapsedRealtime,
428                    LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
429
430                @Override
431                public void onTick(long millisUntilFinished) {
432                    final int secondsCountdown = (int) (millisUntilFinished / 1000);
433                    showError(getString(
434                            R.string.lockpattern_too_many_failed_confirmation_attempts,
435                            secondsCountdown), 0);
436                }
437
438                @Override
439                public void onFinish() {
440                    resetState();
441                    mErrorTextView.setText("");
442                }
443            }.start();
444        }
445
446        public void onClick(View v) {
447            switch (v.getId()) {
448                case R.id.next_button:
449                    handleNext();
450                    break;
451
452                case R.id.cancel_button:
453                    getActivity().setResult(RESULT_CANCELED);
454                    getActivity().finish();
455                    break;
456            }
457        }
458
459        private void showError(int msg) {
460            showError(msg, ERROR_MESSAGE_TIMEOUT);
461        }
462
463        private final Runnable mResetErrorRunnable = new Runnable() {
464            public void run() {
465                mErrorTextView.setText("");
466            }
467        };
468
469        private void showError(CharSequence msg, long timeout) {
470            mErrorTextView.setText(msg);
471            mPasswordEntry.setText(null);
472            mHandler.removeCallbacks(mResetErrorRunnable);
473            if (timeout != 0) {
474                mHandler.postDelayed(mResetErrorRunnable, timeout);
475            }
476        }
477
478        private void showError(int msg, long timeout) {
479            showError(getText(msg), timeout);
480        }
481
482        // {@link OnEditorActionListener} methods.
483        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
484            // Check if this was the result of hitting the enter or "done" key
485            if (actionId == EditorInfo.IME_NULL
486                    || actionId == EditorInfo.IME_ACTION_DONE
487                    || actionId == EditorInfo.IME_ACTION_NEXT) {
488                handleNext();
489                return true;
490            }
491            return false;
492        }
493    }
494}
495