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