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