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