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