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                 if (isShown()) {
     94                     mPasswordEntry.requestFocus();
     95                     if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
     96                         mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
     97                     }
     98                 }
     99             }
    100         });
    101     }
    102 
    103     @Override
    104     public void onPause() {
    105         super.onPause();
    106         mImm.hideSoftInputFromWindow(getWindowToken(), 0);
    107     }
    108 
    109     @Override
    110     public void reset() {
    111         super.reset();
    112         mPasswordEntry.requestFocus();
    113     }
    114 
    115     @Override
    116     protected void onFinishInflate() {
    117         super.onFinishInflate();
    118 
    119         boolean imeOrDeleteButtonVisible = false;
    120 
    121         mImm = (InputMethodManager) getContext().getSystemService(
    122                 Context.INPUT_METHOD_SERVICE);
    123 
    124         mPasswordEntry = (TextView) findViewById(getPasswordTextViewId());
    125         mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
    126         mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
    127                 | InputType.TYPE_TEXT_VARIATION_PASSWORD);
    128         mPasswordEntry.setOnEditorActionListener(this);
    129         mPasswordEntry.addTextChangedListener(this);
    130 
    131         // Poke the wakelock any time the text is selected or modified
    132         mPasswordEntry.setOnClickListener(new OnClickListener() {
    133             public void onClick(View v) {
    134                 mCallback.userActivity();
    135             }
    136         });
    137 
    138         // Set selected property on so the view can send accessibility events.
    139         mPasswordEntry.setSelected(true);
    140 
    141         mPasswordEntry.addTextChangedListener(new TextWatcher() {
    142             public void onTextChanged(CharSequence s, int start, int before, int count) {
    143             }
    144 
    145             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    146             }
    147 
    148             public void afterTextChanged(Editable s) {
    149                 if (mCallback != null) {
    150                     mCallback.userActivity();
    151                 }
    152             }
    153         });
    154 
    155         mPasswordEntry.requestFocus();
    156 
    157         // If there's more than one IME, enable the IME switcher button
    158         View switchImeButton = findViewById(R.id.switch_ime_button);
    159         if (switchImeButton != null && hasMultipleEnabledIMEsOrSubtypes(mImm, false)) {
    160             switchImeButton.setVisibility(View.VISIBLE);
    161             imeOrDeleteButtonVisible = true;
    162             switchImeButton.setOnClickListener(new OnClickListener() {
    163                 public void onClick(View v) {
    164                     mCallback.userActivity(); // Leave the screen on a bit longer
    165                     mImm.showInputMethodPicker();
    166                 }
    167             });
    168         }
    169 
    170         // If no icon is visible, reset the start margin on the password field so the text is
    171         // still centered.
    172         if (!imeOrDeleteButtonVisible) {
    173             android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
    174             if (params instanceof MarginLayoutParams) {
    175                 final MarginLayoutParams mlp = (MarginLayoutParams) params;
    176                 mlp.setMarginStart(0);
    177                 mPasswordEntry.setLayoutParams(params);
    178             }
    179         }
    180     }
    181 
    182     @Override
    183     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
    184         // send focus to the password field
    185         return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
    186     }
    187 
    188     @Override
    189     protected void resetPasswordText(boolean animate) {
    190         mPasswordEntry.setText("");
    191     }
    192 
    193     @Override
    194     protected String getPasswordText() {
    195         return mPasswordEntry.getText().toString();
    196     }
    197 
    198     @Override
    199     protected void setPasswordEntryEnabled(boolean enabled) {
    200         mPasswordEntry.setEnabled(enabled);
    201     }
    202 
    203     /**
    204      * Method adapted from com.android.inputmethod.latin.Utils
    205      *
    206      * @param imm The input method manager
    207      * @param shouldIncludeAuxiliarySubtypes
    208      * @return true if we have multiple IMEs to choose from
    209      */
    210     private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
    211             final boolean shouldIncludeAuxiliarySubtypes) {
    212         final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
    213 
    214         // Number of the filtered IMEs
    215         int filteredImisCount = 0;
    216 
    217         for (InputMethodInfo imi : enabledImis) {
    218             // We can return true immediately after we find two or more filtered IMEs.
    219             if (filteredImisCount > 1) return true;
    220             final List<InputMethodSubtype> subtypes =
    221                     imm.getEnabledInputMethodSubtypeList(imi, true);
    222             // IMEs that have no subtypes should be counted.
    223             if (subtypes.isEmpty()) {
    224                 ++filteredImisCount;
    225                 continue;
    226             }
    227 
    228             int auxCount = 0;
    229             for (InputMethodSubtype subtype : subtypes) {
    230                 if (subtype.isAuxiliary()) {
    231                     ++auxCount;
    232                 }
    233             }
    234             final int nonAuxCount = subtypes.size() - auxCount;
    235 
    236             // IMEs that have one or more non-auxiliary subtypes should be counted.
    237             // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
    238             // subtypes should be counted as well.
    239             if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
    240                 ++filteredImisCount;
    241                 continue;
    242             }
    243         }
    244 
    245         return filteredImisCount > 1
    246         // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
    247         // input method subtype (The current IME should be LatinIME.)
    248                 || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
    249     }
    250 
    251     @Override
    252     public void showUsabilityHint() {
    253     }
    254 
    255     @Override
    256     public int getWrongPasswordStringId() {
    257         return R.string.kg_wrong_password;
    258     }
    259 
    260     @Override
    261     public void startAppearAnimation() {
    262         setAlpha(0f);
    263         setTranslationY(0f);
    264         animate()
    265                 .alpha(1)
    266                 .withLayer()
    267                 .setDuration(300)
    268                 .setInterpolator(mLinearOutSlowInInterpolator);
    269     }
    270 
    271     @Override
    272     public boolean startDisappearAnimation(Runnable finishRunnable) {
    273         animate()
    274                 .alpha(0f)
    275                 .translationY(mDisappearYTranslation)
    276                 .setInterpolator(mFastOutLinearInInterpolator)
    277                 .setDuration(100)
    278                 .withEndAction(finishRunnable);
    279         return true;
    280     }
    281 
    282     @Override
    283     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    284         if (mCallback != null) {
    285             mCallback.userActivity();
    286         }
    287     }
    288 
    289     @Override
    290     public void onTextChanged(CharSequence s, int start, int before, int count) {
    291     }
    292 
    293     @Override
    294     public void afterTextChanged(Editable s) {
    295     }
    296 
    297     @Override
    298     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    299         // Check if this was the result of hitting the enter key
    300         final boolean isSoftImeEvent = event == null
    301                 && (actionId == EditorInfo.IME_NULL
    302                 || actionId == EditorInfo.IME_ACTION_DONE
    303                 || actionId == EditorInfo.IME_ACTION_NEXT);
    304         final boolean isKeyboardEnterKey = event != null
    305                 && KeyEvent.isConfirmKey(event.getKeyCode())
    306                 && event.getAction() == KeyEvent.ACTION_DOWN;
    307         if (isSoftImeEvent || isKeyboardEnterKey) {
    308             verifyPasswordAndUnlock();
    309             return true;
    310         }
    311         return false;
    312     }
    313 }
    314