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