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