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