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