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.view.KeyEvent;
     20 import android.view.View;
     21 import android.os.Handler;
     22 import android.os.SystemClock;
     23 import android.text.*;
     24 import android.text.method.TextKeyListener.Capitalize;
     25 import android.util.SparseArray;
     26 
     27 /**
     28  * This is the standard key listener for alphabetic input on 12-key
     29  * keyboards.  You should generally not need to instantiate this yourself;
     30  * TextKeyListener will do it for you.
     31  */
     32 public class MultiTapKeyListener extends BaseKeyListener
     33         implements SpanWatcher {
     34     private static MultiTapKeyListener[] sInstance =
     35         new MultiTapKeyListener[Capitalize.values().length * 2];
     36 
     37     private static final SparseArray<String> sRecs = new SparseArray<String>();
     38 
     39     private Capitalize mCapitalize;
     40     private boolean mAutoText;
     41 
     42     static {
     43         sRecs.put(KeyEvent.KEYCODE_1,     ".,1!@#$%^&*:/?'=()");
     44         sRecs.put(KeyEvent.KEYCODE_2,     "abc2ABC");
     45         sRecs.put(KeyEvent.KEYCODE_3,     "def3DEF");
     46         sRecs.put(KeyEvent.KEYCODE_4,     "ghi4GHI");
     47         sRecs.put(KeyEvent.KEYCODE_5,     "jkl5JKL");
     48         sRecs.put(KeyEvent.KEYCODE_6,     "mno6MNO");
     49         sRecs.put(KeyEvent.KEYCODE_7,     "pqrs7PQRS");
     50         sRecs.put(KeyEvent.KEYCODE_8,     "tuv8TUV");
     51         sRecs.put(KeyEvent.KEYCODE_9,     "wxyz9WXYZ");
     52         sRecs.put(KeyEvent.KEYCODE_0,     "0+");
     53         sRecs.put(KeyEvent.KEYCODE_POUND, " ");
     54     };
     55 
     56     public MultiTapKeyListener(Capitalize cap,
     57                                boolean autotext) {
     58         mCapitalize = cap;
     59         mAutoText = autotext;
     60     }
     61 
     62     /**
     63      * Returns a new or existing instance with the specified capitalization
     64      * and correction properties.
     65      */
     66     public static MultiTapKeyListener getInstance(boolean autotext,
     67                                                   Capitalize cap) {
     68         int off = cap.ordinal() * 2 + (autotext ? 1 : 0);
     69 
     70         if (sInstance[off] == null) {
     71             sInstance[off] = new MultiTapKeyListener(cap, autotext);
     72         }
     73 
     74         return sInstance[off];
     75     }
     76 
     77     public int getInputType() {
     78         return makeTextContentType(mCapitalize, mAutoText);
     79     }
     80 
     81     public boolean onKeyDown(View view, Editable content,
     82                              int keyCode, KeyEvent event) {
     83         int selStart, selEnd;
     84         int pref = 0;
     85 
     86         if (view != null) {
     87             pref = TextKeyListener.getInstance().getPrefs(view.getContext());
     88         }
     89 
     90         {
     91             int a = Selection.getSelectionStart(content);
     92             int b = Selection.getSelectionEnd(content);
     93 
     94             selStart = Math.min(a, b);
     95             selEnd = Math.max(a, b);
     96         }
     97 
     98         int activeStart = content.getSpanStart(TextKeyListener.ACTIVE);
     99         int activeEnd = content.getSpanEnd(TextKeyListener.ACTIVE);
    100 
    101         // now for the multitap cases...
    102 
    103         // Try to increment the character we were working on before
    104         // if we have one and it's still the same key.
    105 
    106         int rec = (content.getSpanFlags(TextKeyListener.ACTIVE)
    107                     & Spannable.SPAN_USER) >>> Spannable.SPAN_USER_SHIFT;
    108 
    109         if (activeStart == selStart && activeEnd == selEnd &&
    110             selEnd - selStart == 1 &&
    111             rec >= 0 && rec < sRecs.size()) {
    112             if (keyCode == KeyEvent.KEYCODE_STAR) {
    113                 char current = content.charAt(selStart);
    114 
    115                 if (Character.isLowerCase(current)) {
    116                     content.replace(selStart, selEnd,
    117                                     String.valueOf(current).toUpperCase());
    118                     removeTimeouts(content);
    119                     Timeout t = new Timeout(content);
    120 
    121                     return true;
    122                 }
    123                 if (Character.isUpperCase(current)) {
    124                     content.replace(selStart, selEnd,
    125                                     String.valueOf(current).toLowerCase());
    126                     removeTimeouts(content);
    127                     Timeout t = new Timeout(content);
    128 
    129                     return true;
    130                 }
    131             }
    132 
    133             if (sRecs.indexOfKey(keyCode) == rec) {
    134                 String val = sRecs.valueAt(rec);
    135                 char ch = content.charAt(selStart);
    136                 int ix = val.indexOf(ch);
    137 
    138                 if (ix >= 0) {
    139                     ix = (ix + 1) % (val.length());
    140 
    141                     content.replace(selStart, selEnd, val, ix, ix + 1);
    142                     removeTimeouts(content);
    143                     Timeout t = new Timeout(content);
    144 
    145                     return true;
    146                 }
    147             }
    148 
    149             // Is this key one we know about at all?  If so, acknowledge
    150             // that the selection is our fault but the key has changed
    151             // or the text no longer matches, so move the selection over
    152             // so that it inserts instead of replaces.
    153 
    154             rec = sRecs.indexOfKey(keyCode);
    155 
    156             if (rec >= 0) {
    157                 Selection.setSelection(content, selEnd, selEnd);
    158                 selStart = selEnd;
    159             }
    160         } else {
    161             rec = sRecs.indexOfKey(keyCode);
    162         }
    163 
    164         if (rec >= 0) {
    165             // We have a valid key.  Replace the selection or insertion point
    166             // with the first character for that key, and remember what
    167             // record it came from for next time.
    168 
    169             String val = sRecs.valueAt(rec);
    170 
    171             int off = 0;
    172             if ((pref & TextKeyListener.AUTO_CAP) != 0 &&
    173                 TextKeyListener.shouldCap(mCapitalize, content, selStart)) {
    174                 for (int i = 0; i < val.length(); i++) {
    175                     if (Character.isUpperCase(val.charAt(i))) {
    176                         off = i;
    177                         break;
    178                     }
    179                 }
    180             }
    181 
    182             if (selStart != selEnd) {
    183                 Selection.setSelection(content, selEnd);
    184             }
    185 
    186             content.setSpan(OLD_SEL_START, selStart, selStart,
    187                             Spannable.SPAN_MARK_MARK);
    188 
    189             content.replace(selStart, selEnd, val, off, off + 1);
    190 
    191             int oldStart = content.getSpanStart(OLD_SEL_START);
    192             selEnd = Selection.getSelectionEnd(content);
    193 
    194             if (selEnd != oldStart) {
    195                 Selection.setSelection(content, oldStart, selEnd);
    196 
    197                 content.setSpan(TextKeyListener.LAST_TYPED,
    198                                 oldStart, selEnd,
    199                                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    200 
    201                 content.setSpan(TextKeyListener.ACTIVE,
    202                             oldStart, selEnd,
    203                             Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
    204                             (rec << Spannable.SPAN_USER_SHIFT));
    205 
    206             }
    207 
    208             removeTimeouts(content);
    209             Timeout t = new Timeout(content);
    210 
    211             // Set up the callback so we can remove the timeout if the
    212             // cursor moves.
    213 
    214             if (content.getSpanStart(this) < 0) {
    215                 KeyListener[] methods = content.getSpans(0, content.length(),
    216                                                     KeyListener.class);
    217                 for (Object method : methods) {
    218                     content.removeSpan(method);
    219                 }
    220                 content.setSpan(this, 0, content.length(),
    221                                 Spannable.SPAN_INCLUSIVE_INCLUSIVE);
    222             }
    223 
    224             return true;
    225         }
    226 
    227         return super.onKeyDown(view, content, keyCode, event);
    228     }
    229 
    230     public void onSpanChanged(Spannable buf,
    231                               Object what, int s, int e, int start, int stop) {
    232         if (what == Selection.SELECTION_END) {
    233             buf.removeSpan(TextKeyListener.ACTIVE);
    234             removeTimeouts(buf);
    235         }
    236     }
    237 
    238     private static void removeTimeouts(Spannable buf) {
    239         Timeout[] timeout = buf.getSpans(0, buf.length(), Timeout.class);
    240 
    241         for (int i = 0; i < timeout.length; i++) {
    242             Timeout t = timeout[i];
    243 
    244             t.removeCallbacks(t);
    245             t.mBuffer = null;
    246             buf.removeSpan(t);
    247         }
    248     }
    249 
    250     private class Timeout
    251     extends Handler
    252     implements Runnable
    253     {
    254         public Timeout(Editable buffer) {
    255             mBuffer = buffer;
    256             mBuffer.setSpan(Timeout.this, 0, mBuffer.length(),
    257                             Spannable.SPAN_INCLUSIVE_INCLUSIVE);
    258 
    259             postAtTime(this, SystemClock.uptimeMillis() + 2000);
    260         }
    261 
    262         public void run() {
    263             Spannable buf = mBuffer;
    264 
    265             if (buf != null) {
    266                 int st = Selection.getSelectionStart(buf);
    267                 int en = Selection.getSelectionEnd(buf);
    268 
    269                 int start = buf.getSpanStart(TextKeyListener.ACTIVE);
    270                 int end = buf.getSpanEnd(TextKeyListener.ACTIVE);
    271 
    272                 if (st == start && en == end) {
    273                     Selection.setSelection(buf, Selection.getSelectionEnd(buf));
    274                 }
    275 
    276                 buf.removeSpan(Timeout.this);
    277             }
    278         }
    279 
    280         private Editable mBuffer;
    281     }
    282 
    283     public void onSpanAdded(Spannable s, Object what, int start, int end) { }
    284     public void onSpanRemoved(Spannable s, Object what, int start, int end) { }
    285 }
    286 
    287