blob: c96fc5de084559aeb2024b4904adbf2066358462 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.text.method;
18
Mathew Inwoodefeab842018-08-14 15:21:30 +010019import android.annotation.UnsupportedAppUsage;
Aurimas Liutikas4037d512016-10-11 17:20:06 -070020import android.graphics.Rect;
Mathew Inwood31755f92018-12-20 13:53:36 +000021import android.os.Build;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.os.Handler;
23import android.os.SystemClock;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.text.Editable;
25import android.text.GetChars;
26import android.text.NoCopySpan;
Aurimas Liutikas4037d512016-10-11 17:20:06 -070027import android.text.Spannable;
28import android.text.Spanned;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.text.TextUtils;
30import android.text.TextWatcher;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.text.style.UpdateLayout;
Aurimas Liutikas4037d512016-10-11 17:20:06 -070032import android.view.View;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033
34import java.lang.ref.WeakReference;
35
36public class PasswordTransformationMethod
37implements TransformationMethod, TextWatcher
38{
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
Devin Taylor8082d5d2010-03-18 15:35:11 -050055 removeVisibleSpans(sp);
56
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057 sp.setSpan(new ViewReference(view), 0, 0,
58 Spannable.SPAN_POINT_POINT);
59 }
60
61 return new PasswordCharSequence(source);
62 }
63
64 public static PasswordTransformationMethod getInstance() {
65 if (sInstance != null)
66 return sInstance;
67
68 sInstance = new PasswordTransformationMethod();
69 return sInstance;
70 }
71
72 public void beforeTextChanged(CharSequence s, int start,
73 int count, int after) {
74 // This callback isn't used.
75 }
76
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) {
Devin Taylor8082d5d2010-03-18 15:35:11 -0500106 removeVisibleSpans(sp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107
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
117 public void afterTextChanged(Editable s) {
118 // This callback isn't used.
119 }
120
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
Devin Taylor8082d5d2010-03-18 15:35:11 -0500128 removeVisibleSpans(sp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 }
130 }
131 }
132
Devin Taylor8082d5d2010-03-18 15:35:11 -0500133 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 private static class PasswordCharSequence
141 implements CharSequence, GetChars
142 {
143 public PasswordCharSequence(CharSequence source) {
144 mSource = source;
145 }
146
147 public int length() {
148 return mSource.length();
149 }
150
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
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
186 public String toString() {
187 return subSequence(0, length()).toString();
188 }
189
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 {
241 public Visible(Spannable sp, PasswordTransformationMethod ptm) {
242 mText = sp;
243 mTransformer = ptm;
244 postAtTime(this, SystemClock.uptimeMillis() + 1500);
245 }
246
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 {
261 public ViewReference(View v) {
262 super(v);
263 }
264 }
265
Mathew Inwoodefeab842018-08-14 15:21:30 +0100266 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800267 private static PasswordTransformationMethod sInstance;
Mathew Inwood31755f92018-12-20 13:53:36 +0000268 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 private static char DOT = '\u2022';
270}