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