Home | History | Annotate | Download | only in method
      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 {
     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 
     63     public static PasswordTransformationMethod getInstance() {
     64         if (sInstance != null)
     65             return sInstance;
     66 
     67         sInstance = new PasswordTransformationMethod();
     68         return sInstance;
     69     }
     70 
     71     public void beforeTextChanged(CharSequence s, int start,
     72                                   int count, int after) {
     73         // This callback isn't used.
     74     }
     75 
     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 
    116     public void afterTextChanged(Editable s) {
    117         // This callback isn't used.
    118     }
    119 
    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 
    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     {
    142         public PasswordCharSequence(CharSequence source) {
    143             mSource = source;
    144         }
    145 
    146         public int length() {
    147             return mSource.length();
    148         }
    149 
    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 
    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 
    185         public String toString() {
    186             return subSequence(0, length()).toString();
    187         }
    188 
    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     {
    240         public Visible(Spannable sp, PasswordTransformationMethod ptm) {
    241             mText = sp;
    242             mTransformer = ptm;
    243             postAtTime(this, SystemClock.uptimeMillis() + 1500);
    244         }
    245 
    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 {
    260         public ViewReference(View v) {
    261             super(v);
    262         }
    263     }
    264 
    265     private static PasswordTransformationMethod sInstance;
    266     private static char DOT = '\u2022';
    267 }
    268