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