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.Spanned;
29 import android.text.Spannable;
30 import android.text.style.UpdateLayout;
31 
32 import java.lang.ref.WeakReference;
33 
34 public class PasswordTransformationMethod
35 implements TransformationMethod, TextWatcher
36 {
getTransformation(CharSequence source, View view)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 
53             removeVisibleSpans(sp);
54 
55             sp.setSpan(new ViewReference(view), 0, 0,
56                        Spannable.SPAN_POINT_POINT);
57         }
58 
59         return new PasswordCharSequence(source);
60     }
61 
getInstance()62     public static PasswordTransformationMethod getInstance() {
63         if (sInstance != null)
64             return sInstance;
65 
66         sInstance = new PasswordTransformationMethod();
67         return sInstance;
68     }
69 
beforeTextChanged(CharSequence s, int start, int count, int after)70     public void beforeTextChanged(CharSequence s, int start,
71                                   int count, int after) {
72         // This callback isn't used.
73     }
74 
onTextChanged(CharSequence s, int start, int before, int count)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) {
104                     removeVisibleSpans(sp);
105 
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 
afterTextChanged(Editable s)115     public void afterTextChanged(Editable s) {
116         // This callback isn't used.
117     }
118 
onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction, Rect previouslyFocusedRect)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 
126                 removeVisibleSpans(sp);
127             }
128         }
129     }
130 
removeVisibleSpans(Spannable sp)131     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 
138     private static class PasswordCharSequence
139     implements CharSequence, GetChars
140     {
PasswordCharSequence(CharSequence source)141         public PasswordCharSequence(CharSequence source) {
142             mSource = source;
143         }
144 
length()145         public int length() {
146             return mSource.length();
147         }
148 
charAt(int i)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 
subSequence(int start, int end)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 
toString()184         public String toString() {
185             return subSequence(0, length()).toString();
186         }
187 
getChars(int start, int end, char[] dest, int off)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     {
Visible(Spannable sp, PasswordTransformationMethod ptm)239         public Visible(Spannable sp, PasswordTransformationMethod ptm) {
240             mText = sp;
241             mTransformer = ptm;
242             postAtTime(this, SystemClock.uptimeMillis() + 1500);
243         }
244 
run()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 {
ViewReference(View v)259         public ViewReference(View v) {
260             super(v);
261         }
262     }
263 
264     private static PasswordTransformationMethod sInstance;
265     private static char DOT = '\u2022';
266 }
267