1 /* 2 * Copyright (C) 2006 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 android.text.method; 18 19 import android.os.Handler; 20 import android.os.SystemClock; 21 import android.graphics.Rect; 22 import android.view.View; 23 import android.text.Editable; 24 import android.text.GetChars; 25 import android.text.NoCopySpan; 26 import android.text.TextUtils; 27 import android.text.TextWatcher; 28 import android.text.Spanned; 29 import android.text.Spannable; 30 import android.text.style.UpdateLayout; 31 32 import java.lang.ref.WeakReference; 33 34 public class PasswordTransformationMethod 35 implements TransformationMethod, TextWatcher 36 { getTransformation(CharSequence source, View view)37 public CharSequence getTransformation(CharSequence source, View view) { 38 if (source instanceof Spannable) { 39 Spannable sp = (Spannable) source; 40 41 /* 42 * Remove any references to other views that may still be 43 * attached. This will happen when you flip the screen 44 * while a password field is showing; there will still 45 * be references to the old EditText in the text. 46 */ 47 ViewReference[] vr = sp.getSpans(0, sp.length(), 48 ViewReference.class); 49 for (int i = 0; i < vr.length; i++) { 50 sp.removeSpan(vr[i]); 51 } 52 53 removeVisibleSpans(sp); 54 55 sp.setSpan(new ViewReference(view), 0, 0, 56 Spannable.SPAN_POINT_POINT); 57 } 58 59 return new PasswordCharSequence(source); 60 } 61 getInstance()62 public static PasswordTransformationMethod getInstance() { 63 if (sInstance != null) 64 return sInstance; 65 66 sInstance = new PasswordTransformationMethod(); 67 return sInstance; 68 } 69 beforeTextChanged(CharSequence s, int start, int count, int after)70 public void beforeTextChanged(CharSequence s, int start, 71 int count, int after) { 72 // This callback isn't used. 73 } 74 onTextChanged(CharSequence s, int start, int before, int count)75 public void onTextChanged(CharSequence s, int start, 76 int before, int count) { 77 if (s instanceof Spannable) { 78 Spannable sp = (Spannable) s; 79 ViewReference[] vr = sp.getSpans(0, s.length(), 80 ViewReference.class); 81 if (vr.length == 0) { 82 return; 83 } 84 85 /* 86 * There should generally only be one ViewReference in the text, 87 * but make sure to look through all of them if necessary in case 88 * something strange is going on. (We might still end up with 89 * multiple ViewReferences if someone moves text from one password 90 * field to another.) 91 */ 92 View v = null; 93 for (int i = 0; v == null && i < vr.length; i++) { 94 v = vr[i].get(); 95 } 96 97 if (v == null) { 98 return; 99 } 100 101 int pref = TextKeyListener.getInstance().getPrefs(v.getContext()); 102 if ((pref & TextKeyListener.SHOW_PASSWORD) != 0) { 103 if (count > 0) { 104 removeVisibleSpans(sp); 105 106 if (count == 1) { 107 sp.setSpan(new Visible(sp, this), start, start + count, 108 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 109 } 110 } 111 } 112 } 113 } 114 afterTextChanged(Editable s)115 public void afterTextChanged(Editable s) { 116 // This callback isn't used. 117 } 118 onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction, Rect previouslyFocusedRect)119 public void onFocusChanged(View view, CharSequence sourceText, 120 boolean focused, int direction, 121 Rect previouslyFocusedRect) { 122 if (!focused) { 123 if (sourceText instanceof Spannable) { 124 Spannable sp = (Spannable) sourceText; 125 126 removeVisibleSpans(sp); 127 } 128 } 129 } 130 removeVisibleSpans(Spannable sp)131 private static void removeVisibleSpans(Spannable sp) { 132 Visible[] old = sp.getSpans(0, sp.length(), Visible.class); 133 for (int i = 0; i < old.length; i++) { 134 sp.removeSpan(old[i]); 135 } 136 } 137 138 private static class PasswordCharSequence 139 implements CharSequence, GetChars 140 { PasswordCharSequence(CharSequence source)141 public PasswordCharSequence(CharSequence source) { 142 mSource = source; 143 } 144 length()145 public int length() { 146 return mSource.length(); 147 } 148 charAt(int i)149 public char charAt(int i) { 150 if (mSource instanceof Spanned) { 151 Spanned sp = (Spanned) mSource; 152 153 int st = sp.getSpanStart(TextKeyListener.ACTIVE); 154 int en = sp.getSpanEnd(TextKeyListener.ACTIVE); 155 156 if (i >= st && i < en) { 157 return mSource.charAt(i); 158 } 159 160 Visible[] visible = sp.getSpans(0, sp.length(), Visible.class); 161 162 for (int a = 0; a < visible.length; a++) { 163 if (sp.getSpanStart(visible[a].mTransformer) >= 0) { 164 st = sp.getSpanStart(visible[a]); 165 en = sp.getSpanEnd(visible[a]); 166 167 if (i >= st && i < en) { 168 return mSource.charAt(i); 169 } 170 } 171 } 172 } 173 174 return DOT; 175 } 176 subSequence(int start, int end)177 public CharSequence subSequence(int start, int end) { 178 char[] buf = new char[end - start]; 179 180 getChars(start, end, buf, 0); 181 return new String(buf); 182 } 183 toString()184 public String toString() { 185 return subSequence(0, length()).toString(); 186 } 187 getChars(int start, int end, char[] dest, int off)188 public void getChars(int start, int end, char[] dest, int off) { 189 TextUtils.getChars(mSource, start, end, dest, off); 190 191 int st = -1, en = -1; 192 int nvisible = 0; 193 int[] starts = null, ends = null; 194 195 if (mSource instanceof Spanned) { 196 Spanned sp = (Spanned) mSource; 197 198 st = sp.getSpanStart(TextKeyListener.ACTIVE); 199 en = sp.getSpanEnd(TextKeyListener.ACTIVE); 200 201 Visible[] visible = sp.getSpans(0, sp.length(), Visible.class); 202 nvisible = visible.length; 203 starts = new int[nvisible]; 204 ends = new int[nvisible]; 205 206 for (int i = 0; i < nvisible; i++) { 207 if (sp.getSpanStart(visible[i].mTransformer) >= 0) { 208 starts[i] = sp.getSpanStart(visible[i]); 209 ends[i] = sp.getSpanEnd(visible[i]); 210 } 211 } 212 } 213 214 for (int i = start; i < end; i++) { 215 if (! (i >= st && i < en)) { 216 boolean visible = false; 217 218 for (int a = 0; a < nvisible; a++) { 219 if (i >= starts[a] && i < ends[a]) { 220 visible = true; 221 break; 222 } 223 } 224 225 if (!visible) { 226 dest[i - start + off] = DOT; 227 } 228 } 229 } 230 } 231 232 private CharSequence mSource; 233 } 234 235 private static class Visible 236 extends Handler 237 implements UpdateLayout, Runnable 238 { Visible(Spannable sp, PasswordTransformationMethod ptm)239 public Visible(Spannable sp, PasswordTransformationMethod ptm) { 240 mText = sp; 241 mTransformer = ptm; 242 postAtTime(this, SystemClock.uptimeMillis() + 1500); 243 } 244 run()245 public void run() { 246 mText.removeSpan(this); 247 } 248 249 private Spannable mText; 250 private PasswordTransformationMethod mTransformer; 251 } 252 253 /** 254 * Used to stash a reference back to the View in the Editable so we 255 * can use it to check the settings. 256 */ 257 private static class ViewReference extends WeakReference<View> 258 implements NoCopySpan { ViewReference(View v)259 public ViewReference(View v) { 260 super(v); 261 } 262 } 263 264 private static PasswordTransformationMethod sInstance; 265 private static char DOT = '\u2022'; 266 } 267