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