Settings 中 显示密码 选项是怎样工作的

2015-03-21 Roger 更多博文 » 博客 » GitHub »

Android Framework

原文链接 http://www.rogerblog.cn/2015/03/20/e9-94-81-e5-b1-8f-e5-af-86-e7-a0-81-e4-b8-ad-e6-98-be-e7-a4-ba-e5-af-86-e7-a0-81-e9-80-89-e9-a1-b9-e6-98-af-e6-80-8e-e6-a0-b7-e5-b7-a5-e4-bd-9c-e7-9a-84/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


最近由于工作上的需要,研究了一下framework层面的东西。收获良多,感受颇深啊。

在 设置->安全 中选择屏幕锁定,选择屏幕锁定方式为密码,然后勾选显示密码选项,此时应该在输入密码时,先显示输入的密码,过1.5S后将变为小圆点,若取消显示密码则输入直接为小圆点。

而由于“前人”对锁屏的改动造成取消显示密码后,还是先显示密码才跳为小圆点,这是我要解决的BUG。

研究后发现“前人”将 Keyguard 中 com.android.keyguard.KeyguardPasswordView 类 106 行改变了Textview的inputtype,将其恢复后BUG得以解决。

Why?我一头雾水,甚至都不知道是在哪把输入的字符变成小圆点的!接下来就开始了我的求知之旅。

首先找到Settings中对应显示密码的代码。位于 com.android.settings.SecuritySettings ,找到575行

Settings.System.putInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD,mShowPassword.isChecked() ? 1 : 0);

Settings.System.TEXT_SHOW_PASSWORD,这是存储按钮是否选中的key,于是在Keyguard工程中全局搜索,居然找不到~顿时语塞。

于是回到锁屏页面,发现输入框的EditView调用了一个 setKeyListener(TextKeyListener.getInstance()); 方法,来到 TextKeyListener 末尾看到两个方法

<!--more-->

private void updatePrefs(ContentResolver resolver) { boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0; boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0; boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0; boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;

     mPrefs = (cap ? AUTO_CAP : 0) |
              (text ? AUTO_TEXT : 0) |
              (period ? AUTO_PERIOD : 0) |
              (pw ? SHOW_PASSWORD : 0);}

/* package */ int getPrefs(Context context) { synchronized (this) { if (!mPrefsInited || mResolver.get() == null) { initPrefs(context); } } return mPrefs; }

可以看到 mPrefs 的值是会被 System.TEXT_SHOW_PASSWORD 影响的。看来只能到TextView源码中看了。在Textview源码中搜索 mPrefs 无果,再搜索 System.TEXT_SHOW_PASSWORD 还是无果。

最后只能在网站 http://androidxref.com/ 中搜索 mPrefs ,定位到类 PasswordTransformationMethod 中。看了老半天,结合好几个类,大概查出了点思路。

在setting中勾选显示密码后,Settings.System.TEXT_SHOW_PASSWORD 的值将变为1。

回到Textview中,若我们设置 android:inputType=”textPassword” 或在代码中设置 setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);后

来到TextView的3987行

public void setInputType(int type) { final boolean wasPassword = isPasswordInputType(getInputType()); final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); setInputType(type, false); final boolean isPassword = isPasswordInputType(type); final boolean isVisiblePassword = isVisiblePasswordInputType(type); boolean forceUpdate = false; if (isPassword) { setTransformationMethod(PasswordTransformationMethod.getInstance()); setTypefaceFromAttrs(null /* fontFamily /, MONOSPACE, 0); } else if (isVisiblePassword) { if (mTransformation == PasswordTransformationMethod.getInstance()) { forceUpdate = true; } setTypefaceFromAttrs(null / fontFamily /, MONOSPACE, 0); } else if (wasPassword || wasVisiblePassword) { // not in password mode, clean up typeface and transformation setTypefaceFromAttrs(null / fontFamily */, -1, -1); if (mTransformation == PasswordTransformationMethod.getInstance()) { forceUpdate = true; } }

     boolean singleLine = !isMultilineInputType(type);

// We need to update the single line mode if it has changed or we // were previously in password mode. if (mSingleLine != singleLine || forceUpdate) { // Change single line mode, but only change the transformation if // we are not in password mode. applySingleLine(singleLine, !isPassword, true); }

     if (!isSuggestionsEnabled()) {
              mText = removeSuggestionSpans(mText);
     }

     InputMethodManager imm = InputMethodManager.peekInstance();
              if (imm != null) imm.restartInput(this);

}

将判断isPassword为true,于是设置 setTransformationMethod(PasswordTransformationMethod.getInstance()); 看到1702行setTransformationMethod方法,其实是将其赋给mTransformation这个变量。

重点来了,看到setText方法中,类的第3771行起

if (mTransformation == null) { mTransformed = text; } else { mTransformed = mTransformation.getTransformation(text, this); }

我们可以看到 mTransformation将输入的text转换了一遍,那么这个方法在PasswordTransformationMethod是怎样的呢,源码如下:

public CharSequence getTransformation(CharSequence source, View view) { if (source instanceof Spannable) { Spannable sp = (Spannable) source;

     /*
     * Remove any references to other views that may still be
     * attached. This will happen when you flip the screen
     * while a password field is showing; there will still
     * be references to the old EditText in the text.
     */
              ViewReference[] vr = sp.getSpans(0, sp.length(),
              ViewReference.class);
              for (int i = 0; i &lt; vr.length; i++) {
                       sp.removeSpan(vr[i]);
              }

              removeVisibleSpans(sp);

              sp.setSpan(new ViewReference(view), 0, 0,
              Spannable.SPAN_POINT_POINT);
     }

     return new PasswordCharSequence(source);

}

最后返回了一个 PasswordCharSequence(source); 进一步跟踪PasswordCharSequence类,方法charAt源码:

public char charAt(int i) { if (mSource instanceof Spanned) { Spanned sp = (Spanned) mSource; int st = sp.getSpanStart(TextKeyListener.ACTIVE); int en = sp.getSpanEnd(TextKeyListener.ACTIVE); if (i >= st && i < en) { return mSource.charAt(i); } Visible[] visible = sp.getSpans(0, sp.length(), Visible.class); for (int a = 0; a < visible.length; a++) { if (sp.getSpanStart(visible[a].mTransformer) >= 0) { st = sp.getSpanStart(visible[a]); en = sp.getSpanEnd(visible[a]); if (i >= st && i < en) { return mSource.charAt(i); } } } } return DOT; }

发现了没!原来圆点是从这里来的!如果判断Textview的方式为Password时,在SetText方法中就通过PasswordTransformationMethod将其转换为小圆点。

那显示密码过1.5S后变为圆点又是如何实现的呢!?

看到 PasswordTransformationMethod 中的 onTextChanged 方法,重要代码如下:

int pref = TextKeyListener.getInstance().getPrefs(v.getContext()); if ((pref & TextKeyListener.SHOW_PASSWORD) != 0) { if (count > 0) { removeVisibleSpans(sp); if (count == 1) { sp.setSpan(new Visible(sp, this), start, start + count, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } }

每次输入时都会调用onTextChanged 方法,并判断显示密码是否选中,若为选中状态,则 removeVisibleSpans ,将之前没有变成点的字符变成点,然后将Visible作为Span添加到末尾,我们来看下Visible是怎么写的

private static class Visible extends Handler implements UpdateLayout, Runnable{ public Visible(Spannable sp, PasswordTransformationMethod ptm) { mText = sp; mTransformer = ptm; postAtTime(this, SystemClock.uptimeMillis() + 1500); }

     public void run() {
              mText.removeSpan(this);
     }
     private Spannable mText;
     private PasswordTransformationMethod mTransformer;

}

一切如此明了,Visible将添加的字符显示出来,并在1.5S后将其remvoe,变回原来设置的小圆点!

至此,锁屏密码中“显示密码”选项是怎样工作的 已经能摸出个大概了。其中还有很多猜测和不了解深入的地方,还请大神多多拍砖指正。

第一次写解析文章,水平有限大家还请见谅~不过能看到这还是很感谢大家的耐心,如有什么问题请留言~人多力量大!