Home | History | Annotate | Download | only in keyguard
      1 /*
      2  * Copyright (C) 2012 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.keyguard;
     18 
     19 import android.content.Context;
     20 import android.graphics.Rect;
     21 import android.text.Editable;
     22 import android.text.InputType;
     23 import android.text.TextWatcher;
     24 import android.text.method.TextKeyListener;
     25 import android.util.AttributeSet;
     26 import android.view.KeyEvent;
     27 import android.view.View;
     28 import android.view.animation.AnimationUtils;
     29 import android.view.animation.Interpolator;
     30 import android.view.inputmethod.EditorInfo;
     31 import android.view.inputmethod.InputMethodInfo;
     32 import android.view.inputmethod.InputMethodManager;
     33 import android.view.inputmethod.InputMethodSubtype;
     34 import android.widget.TextView;
     35 import android.widget.TextView.OnEditorActionListener;
     36 
     37 import java.util.List;
     38 /**
     39  * Displays an alphanumeric (latin-1) key entry for the user to enter
     40  * an unlock password
     41  */
     42 
     43 public class KeyguardPasswordView extends KeyguardAbsKeyInputView
     44         implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
     45 
     46     private final boolean mShowImeAtScreenOn;
     47     private final int mDisappearYTranslation;
     48 
     49     InputMethodManager mImm;
     50     private TextView mPasswordEntry;
     51     private Interpolator mLinearOutSlowInInterpolator;
     52     private Interpolator mFastOutLinearInInterpolator;
     53 
     54     public KeyguardPasswordView(Context context) {
     55         this(context, null);
     56     }
     57 
     58     public KeyguardPasswordView(Context context, AttributeSet attrs) {
     59         super(context, attrs);
     60         mShowImeAtScreenOn = context.getResources().
     61                 getBoolean(R.bool.kg_show_ime_at_screen_on);
     62         mDisappearYTranslation = getResources().getDimensionPixelSize(
     63                 R.dimen.disappear_y_translation);
     64         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
     65                 context, android.R.interpolator.linear_out_slow_in);
     66         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
     67                 context, android.R.interpolator.fast_out_linear_in);
     68     }
     69 
     70     protected void resetState() {
     71         mSecurityMessageDisplay.setMessage(R.string.kg_password_instructions, false);
     72         mPasswordEntry.setEnabled(true);
     73     }
     74 
     75     @Override
     76     protected int getPasswordTextViewId() {
     77         return R.id.passwordEntry;
     78     }
     79 
     80     @Override
     81     public boolean needsInput() {
     82         return true;
     83     }
     84 
     85     @Override
     86     public void onResume(final int reason) {
     87         super.onResume(reason);
     88 
     89         // Wait a bit to focus the field so the focusable flag on the window is already set then.
     90         post(new Runnable() {
     91             @Override
     92             public void run() {
     93                 mPasswordEntry.requestFocus();
     94                 if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
     95                     mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
     96                 }
     97             }
     98         });
     99     }
    100 
    101     @Override
    102     public void onPause() {
    103         super.onPause();
    104         mImm.hideSoftInputFromWindow(getWindowToken(), 0);
    105     }
    106 
    107     @Override
    108     public void reset() {
    109         super.reset();
    110         mPasswordEntry.requestFocus();
    111     }
    112 
    113     @Override
    114     protected void onFinishInflate() {
    115         super.onFinishInflate();
    116 
    117         boolean imeOrDeleteButtonVisible = false;
    118 
    119         mImm = (InputMethodManager) getContext().getSystemService(
    120                 Context.INPUT_METHOD_SERVICE);
    121 
    122         mPasswordEntry = (TextView) findViewById(getPasswordTextViewId());
    123         mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
    124         mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
    125                 | InputType.TYPE_TEXT_VARIATION_PASSWORD);
    126         mPasswordEntry.setOnEditorActionListener(this);
    127         mPasswordEntry.addTextChangedListener(this);
    128 
    129         // Poke the wakelock any time the text is selected or modified
    130         mPasswordEntry.setOnClickListener(new OnClickListener() {
    131             public void onClick(View v) {
    132                 mCallback.userActivity();
    133             }
    134         });
    135 
    136         // Set selected property on so the view can send accessibility events.
    137         mPasswordEntry.setSelected(true);
    138 
    139         mPasswordEntry.addTextChangedListener(new TextWatcher() {
    140             public void onTextChanged(CharSequence s, int start, int before, int count) {
    141             }
    142 
    143             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    144             }
    145 
    146             public void afterTextChanged(Editable s) {
    147                 if (mCallback != null) {
    148                     mCallback.userActivity();
    149                 }
    150             }
    151         });
    152 
    153         mPasswordEntry.requestFocus();
    154 
    155         // If there's more than one IME, enable the IME switcher button
    156         View switchImeButton = findViewById(R.id.switch_ime_button);
    157         if (switchImeButton != null && hasMultipleEnabledIMEsOrSubtypes(mImm, false)) {
    158             switchImeButton.setVisibility(View.VISIBLE);
    159             imeOrDeleteButtonVisible = true;
    160             switchImeButton.setOnClickListener(new OnClickListener() {
    161                 public void onClick(View v) {
    162                     mCallback.userActivity(); // Leave the screen on a bit longer
    163                     mImm.showInputMethodPicker();
    164                 }
    165             });
    166         }
    167 
    168         // If no icon is visible, reset the start margin on the password field so the text is
    169         // still centered.
    170         if (!imeOrDeleteButtonVisible) {
    171             android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
    172             if (params instanceof MarginLayoutParams) {
    173                 final MarginLayoutParams mlp = (MarginLayoutParams) params;
    174                 mlp.setMarginStart(0);
    175                 mPasswordEntry.setLayoutParams(params);
    176             }
    177         }
    178     }
    179 
    180     @Override
    181     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
    182         // send focus to the password field
    183         return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
    184     }
    185 
    186     @Override
    187     protected void resetPasswordText(boolean animate) {
    188         mPasswordEntry.setText("");
    189     }
    190 
    191     @Override
    192     protected String getPasswordText() {
    193         return mPasswordEntry.getText().toString();
    194     }
    195 
    196     @Override
    197     protected void setPasswordEntryEnabled(boolean enabled) {
    198         mPasswordEntry.setEnabled(enabled);
    199     }
    200 
    201     /**
    202      * Method adapted from com.android.inputmethod.latin.Utils
    203      *
    204      * @param imm The input method manager
    205      * @param shouldIncludeAuxiliarySubtypes
    206      * @return true if we have multiple IMEs to choose from
    207      */
    208     private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
    209             final boolean shouldIncludeAuxiliarySubtypes) {
    210         final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
    211 
    212         // Number of the filtered IMEs
    213         int filteredImisCount = 0;
    214 
    215         for (InputMethodInfo imi : enabledImis) {
    216             // We can return true immediately after we find two or more filtered IMEs.
    217             if (filteredImisCount > 1) return true;
    218             final List<InputMethodSubtype> subtypes =
    219                     imm.getEnabledInputMethodSubtypeList(imi, true);
    220             // IMEs that have no subtypes should be counted.
    221             if (subtypes.isEmpty()) {
    222                 ++filteredImisCount;
    223                 continue;
    224             }
    225 
    226             int auxCount = 0;
    227             for (InputMethodSubtype subtype : subtypes) {
    228                 if (subtype.isAuxiliary()) {
    229                     ++auxCount;
    230                 }
    231             }
    232             final int nonAuxCount = subtypes.size() - auxCount;
    233 
    234             // IMEs that have one or more non-auxiliary subtypes should be counted.
    235             // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
    236             // subtypes should be counted as well.
    237             if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
    238                 ++filteredImisCount;
    239                 continue;
    240             }
    241         }
    242 
    243         return filteredImisCount > 1
    244         // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
    245         // input method subtype (The current IME should be LatinIME.)
    246                 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
    247     }
    248 
    249     @Override
    250     public void showUsabilityHint() {
    251     }
    252 
    253     @Override
    254     public int getWrongPasswordStringId() {
    255         return R.string.kg_wrong_password;
    256     }
    257 
    258     @Override
    259     public void startAppearAnimation() {
    260         setAlpha(0f);
    261         setTranslationY(0f);
    262         animate()
    263                 .alpha(1)
    264                 .withLayer()
    265                 .setDuration(300)
    266                 .setInterpolator(mLinearOutSlowInInterpolator);
    267     }
    268 
    269     @Override
    270     public boolean startDisappearAnimation(Runnable finishRunnable) {
    271         animate()
    272                 .alpha(0f)
    273                 .translationY(mDisappearYTranslation)
    274                 .setInterpolator(mFastOutLinearInInterpolator)
    275                 .setDuration(100)
    276                 .withEndAction(finishRunnable);
    277         return true;
    278     }
    279 
    280     @Override
    281     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    282         if (mCallback != null) {
    283             mCallback.userActivity();
    284         }
    285     }
    286 
    287     @Override
    288     public void onTextChanged(CharSequence s, int start, int before, int count) {
    289     }
    290 
    291     @Override
    292     public void afterTextChanged(Editable s) {
    293     }
    294 
    295     @Override
    296     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    297         // Check if this was the result of hitting the enter key
    298         final boolean isSoftImeEvent = event == null
    299                 && (actionId == EditorInfo.IME_NULL
    300                 || actionId == EditorInfo.IME_ACTION_DONE
    301                 || actionId == EditorInfo.IME_ACTION_NEXT);
    302         final boolean isKeyboardEnterKey = event != null
    303                 && KeyEvent.isConfirmKey(event.getKeyCode())
    304                 && event.getAction() == KeyEvent.ACTION_DOWN;
    305         if (isSoftImeEvent || isKeyboardEnterKey) {
    306             verifyPasswordAndUnlock();
    307             return true;
    308         }
    309         return false;
    310     }
    311 }
    312