Home | History | Annotate | Download | only in method
      1 /*
      2  * Copyright (C) 2007 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.content.ContentResolver;
     20 import android.content.Context;
     21 import android.database.ContentObserver;
     22 import android.os.Handler;
     23 import android.provider.Settings;
     24 import android.provider.Settings.System;
     25 import android.text.*;
     26 import android.view.KeyCharacterMap;
     27 import android.view.KeyEvent;
     28 import android.view.View;
     29 import android.text.InputType;
     30 
     31 import java.lang.ref.WeakReference;
     32 
     33 /**
     34  * This is the key listener for typing normal text.  It delegates to
     35  * other key listeners appropriate to the current keyboard and language.
     36  * <p></p>
     37  * As for all implementations of {@link KeyListener}, this class is only concerned
     38  * with hardware keyboards.  Software input methods have no obligation to trigger
     39  * the methods in this class.
     40  */
     41 public class TextKeyListener extends BaseKeyListener implements SpanWatcher {
     42     private static TextKeyListener[] sInstance =
     43         new TextKeyListener[Capitalize.values().length * 2];
     44 
     45     /* package */ static final Object ACTIVE = new NoCopySpan.Concrete();
     46     /* package */ static final Object CAPPED = new NoCopySpan.Concrete();
     47     /* package */ static final Object INHIBIT_REPLACEMENT = new NoCopySpan.Concrete();
     48     /* package */ static final Object LAST_TYPED = new NoCopySpan.Concrete();
     49 
     50     private Capitalize mAutoCap;
     51     private boolean mAutoText;
     52 
     53     private int mPrefs;
     54     private boolean mPrefsInited;
     55 
     56     /* package */ static final int AUTO_CAP = 1;
     57     /* package */ static final int AUTO_TEXT = 2;
     58     /* package */ static final int AUTO_PERIOD = 4;
     59     /* package */ static final int SHOW_PASSWORD = 8;
     60     private WeakReference<ContentResolver> mResolver;
     61     private TextKeyListener.SettingsObserver mObserver;
     62 
     63     /**
     64      * Creates a new TextKeyListener with the specified capitalization
     65      * and correction properties.
     66      *
     67      * @param cap when, if ever, to automatically capitalize.
     68      * @param autotext whether to automatically do spelling corrections.
     69      */
     70     public TextKeyListener(Capitalize cap, boolean autotext) {
     71         mAutoCap = cap;
     72         mAutoText = autotext;
     73     }
     74 
     75     /**
     76      * Returns a new or existing instance with the specified capitalization
     77      * and correction properties.
     78      *
     79      * @param cap when, if ever, to automatically capitalize.
     80      * @param autotext whether to automatically do spelling corrections.
     81      */
     82     public static TextKeyListener getInstance(boolean autotext,
     83                                               Capitalize cap) {
     84         int off = cap.ordinal() * 2 + (autotext ? 1 : 0);
     85 
     86         if (sInstance[off] == null) {
     87             sInstance[off] = new TextKeyListener(cap, autotext);
     88         }
     89 
     90         return sInstance[off];
     91     }
     92 
     93     /**
     94      * Returns a new or existing instance with no automatic capitalization
     95      * or correction.
     96      */
     97     public static TextKeyListener getInstance() {
     98         return getInstance(false, Capitalize.NONE);
     99     }
    100 
    101     /**
    102      * Returns whether it makes sense to automatically capitalize at the
    103      * specified position in the specified text, with the specified rules.
    104      *
    105      * @param cap the capitalization rules to consider.
    106      * @param cs the text in which an insertion is being made.
    107      * @param off the offset into that text where the insertion is being made.
    108      *
    109      * @return whether the character being inserted should be capitalized.
    110      */
    111     public static boolean shouldCap(Capitalize cap, CharSequence cs, int off) {
    112         int i;
    113         char c;
    114 
    115         if (cap == Capitalize.NONE) {
    116             return false;
    117         }
    118         if (cap == Capitalize.CHARACTERS) {
    119             return true;
    120         }
    121 
    122         return TextUtils.getCapsMode(cs, off, cap == Capitalize.WORDS
    123                 ? TextUtils.CAP_MODE_WORDS : TextUtils.CAP_MODE_SENTENCES)
    124                 != 0;
    125     }
    126 
    127     public int getInputType() {
    128         return makeTextContentType(mAutoCap, mAutoText);
    129     }
    130 
    131     @Override
    132     public boolean onKeyDown(View view, Editable content,
    133                              int keyCode, KeyEvent event) {
    134         KeyListener im = getKeyListener(event);
    135 
    136         return im.onKeyDown(view, content, keyCode, event);
    137     }
    138 
    139     @Override
    140     public boolean onKeyUp(View view, Editable content,
    141                            int keyCode, KeyEvent event) {
    142         KeyListener im = getKeyListener(event);
    143 
    144         return im.onKeyUp(view, content, keyCode, event);
    145     }
    146 
    147     @Override
    148     public boolean onKeyOther(View view, Editable content, KeyEvent event) {
    149         KeyListener im = getKeyListener(event);
    150 
    151         return im.onKeyOther(view, content, event);
    152     }
    153 
    154     /**
    155      * Clear all the input state (autotext, autocap, multitap, undo)
    156      * from the specified Editable, going beyond Editable.clear(), which
    157      * just clears the text but not the input state.
    158      *
    159      * @param e the buffer whose text and state are to be cleared.
    160      */
    161     public static void clear(Editable e) {
    162         e.clear();
    163         e.removeSpan(ACTIVE);
    164         e.removeSpan(CAPPED);
    165         e.removeSpan(INHIBIT_REPLACEMENT);
    166         e.removeSpan(LAST_TYPED);
    167 
    168         QwertyKeyListener.Replaced[] repl = e.getSpans(0, e.length(),
    169                                    QwertyKeyListener.Replaced.class);
    170         final int count = repl.length;
    171         for (int i = 0; i < count; i++) {
    172             e.removeSpan(repl[i]);
    173         }
    174     }
    175 
    176     public void onSpanAdded(Spannable s, Object what, int start, int end) { }
    177     public void onSpanRemoved(Spannable s, Object what, int start, int end) { }
    178 
    179     public void onSpanChanged(Spannable s, Object what, int start, int end,
    180                               int st, int en) {
    181         if (what == Selection.SELECTION_END) {
    182             s.removeSpan(ACTIVE);
    183         }
    184     }
    185 
    186     private KeyListener getKeyListener(KeyEvent event) {
    187         KeyCharacterMap kmap = event.getKeyCharacterMap();
    188         int kind = kmap.getKeyboardType();
    189 
    190         if (kind == KeyCharacterMap.ALPHA) {
    191             return QwertyKeyListener.getInstance(mAutoText, mAutoCap);
    192         } else if (kind == KeyCharacterMap.NUMERIC) {
    193             return MultiTapKeyListener.getInstance(mAutoText, mAutoCap);
    194         } else if (kind == KeyCharacterMap.FULL
    195                 || kind == KeyCharacterMap.SPECIAL_FUNCTION) {
    196             // We consider special function keyboards full keyboards as a workaround for
    197             // devices that do not have built-in keyboards.  Applications may try to inject
    198             // key events using the built-in keyboard device id which may be configured as
    199             // a special function keyboard using a default key map.  Ideally, as of Honeycomb,
    200             // these applications should be modified to use KeyCharacterMap.VIRTUAL_KEYBOARD.
    201             return QwertyKeyListener.getInstanceForFullKeyboard();
    202         }
    203 
    204         return NullKeyListener.getInstance();
    205     }
    206 
    207     public enum Capitalize {
    208         NONE, SENTENCES, WORDS, CHARACTERS,
    209     }
    210 
    211     private static class NullKeyListener implements KeyListener
    212     {
    213         public int getInputType() {
    214             return InputType.TYPE_NULL;
    215         }
    216 
    217         public boolean onKeyDown(View view, Editable content,
    218                                  int keyCode, KeyEvent event) {
    219             return false;
    220         }
    221 
    222         public boolean onKeyUp(View view, Editable content, int keyCode,
    223                                         KeyEvent event) {
    224             return false;
    225         }
    226 
    227         public boolean onKeyOther(View view, Editable content, KeyEvent event) {
    228             return false;
    229         }
    230 
    231         public void clearMetaKeyState(View view, Editable content, int states) {
    232         }
    233 
    234         public static NullKeyListener getInstance() {
    235             if (sInstance != null)
    236                 return sInstance;
    237 
    238             sInstance = new NullKeyListener();
    239             return sInstance;
    240         }
    241 
    242         private static NullKeyListener sInstance;
    243     }
    244 
    245     public void release() {
    246         if (mResolver != null) {
    247             final ContentResolver contentResolver = mResolver.get();
    248             if (contentResolver != null) {
    249                 contentResolver.unregisterContentObserver(mObserver);
    250                 mResolver.clear();
    251             }
    252             mObserver = null;
    253             mResolver = null;
    254             mPrefsInited = false;
    255         }
    256     }
    257 
    258     private void initPrefs(Context context) {
    259         final ContentResolver contentResolver = context.getContentResolver();
    260         mResolver = new WeakReference<ContentResolver>(contentResolver);
    261         if (mObserver == null) {
    262             mObserver = new SettingsObserver();
    263             contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver);
    264         }
    265 
    266         updatePrefs(contentResolver);
    267         mPrefsInited = true;
    268     }
    269 
    270     private class SettingsObserver extends ContentObserver {
    271         public SettingsObserver() {
    272             super(new Handler());
    273         }
    274 
    275         @Override
    276         public void onChange(boolean selfChange) {
    277             if (mResolver != null) {
    278                 final ContentResolver contentResolver = mResolver.get();
    279                 if (contentResolver == null) {
    280                     mPrefsInited = false;
    281                 } else {
    282                     updatePrefs(contentResolver);
    283                 }
    284             } else {
    285                 mPrefsInited = false;
    286             }
    287         }
    288     }
    289 
    290     private void updatePrefs(ContentResolver resolver) {
    291         boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;
    292         boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;
    293         boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;
    294         boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;
    295 
    296         mPrefs = (cap ? AUTO_CAP : 0) |
    297                  (text ? AUTO_TEXT : 0) |
    298                  (period ? AUTO_PERIOD : 0) |
    299                  (pw ? SHOW_PASSWORD : 0);
    300     }
    301 
    302     /* package */ int getPrefs(Context context) {
    303         synchronized (this) {
    304             if (!mPrefsInited || mResolver.get() == null) {
    305                 initPrefs(context);
    306             }
    307         }
    308 
    309         return mPrefs;
    310     }
    311 }
    312