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