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