/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.text.method; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.os.Handler; import android.provider.Settings; import android.provider.Settings.System; import android.text.*; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.View; import android.text.InputType; import java.lang.ref.WeakReference; /** * This is the key listener for typing normal text. It delegates to * other key listeners appropriate to the current keyboard and language. *

* As for all implementations of {@link KeyListener}, this class is only concerned * with hardware keyboards. Software input methods have no obligation to trigger * the methods in this class. */ public class TextKeyListener extends BaseKeyListener implements SpanWatcher { private static TextKeyListener[] sInstance = new TextKeyListener[Capitalize.values().length * 2]; /* package */ static final Object ACTIVE = new NoCopySpan.Concrete(); /* package */ static final Object CAPPED = new NoCopySpan.Concrete(); /* package */ static final Object INHIBIT_REPLACEMENT = new NoCopySpan.Concrete(); /* package */ static final Object LAST_TYPED = new NoCopySpan.Concrete(); private Capitalize mAutoCap; private boolean mAutoText; private int mPrefs; private boolean mPrefsInited; /* package */ static final int AUTO_CAP = 1; /* package */ static final int AUTO_TEXT = 2; /* package */ static final int AUTO_PERIOD = 4; /* package */ static final int SHOW_PASSWORD = 8; private WeakReference mResolver; private TextKeyListener.SettingsObserver mObserver; /** * Creates a new TextKeyListener with the specified capitalization * and correction properties. * * @param cap when, if ever, to automatically capitalize. * @param autotext whether to automatically do spelling corrections. */ public TextKeyListener(Capitalize cap, boolean autotext) { mAutoCap = cap; mAutoText = autotext; } /** * Returns a new or existing instance with the specified capitalization * and correction properties. * * @param cap when, if ever, to automatically capitalize. * @param autotext whether to automatically do spelling corrections. */ public static TextKeyListener getInstance(boolean autotext, Capitalize cap) { int off = cap.ordinal() * 2 + (autotext ? 1 : 0); if (sInstance[off] == null) { sInstance[off] = new TextKeyListener(cap, autotext); } return sInstance[off]; } /** * Returns a new or existing instance with no automatic capitalization * or correction. */ public static TextKeyListener getInstance() { return getInstance(false, Capitalize.NONE); } /** * Returns whether it makes sense to automatically capitalize at the * specified position in the specified text, with the specified rules. * * @param cap the capitalization rules to consider. * @param cs the text in which an insertion is being made. * @param off the offset into that text where the insertion is being made. * * @return whether the character being inserted should be capitalized. */ public static boolean shouldCap(Capitalize cap, CharSequence cs, int off) { int i; char c; if (cap == Capitalize.NONE) { return false; } if (cap == Capitalize.CHARACTERS) { return true; } return TextUtils.getCapsMode(cs, off, cap == Capitalize.WORDS ? TextUtils.CAP_MODE_WORDS : TextUtils.CAP_MODE_SENTENCES) != 0; } public int getInputType() { return makeTextContentType(mAutoCap, mAutoText); } @Override public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) { KeyListener im = getKeyListener(event); return im.onKeyDown(view, content, keyCode, event); } @Override public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) { KeyListener im = getKeyListener(event); return im.onKeyUp(view, content, keyCode, event); } @Override public boolean onKeyOther(View view, Editable content, KeyEvent event) { KeyListener im = getKeyListener(event); return im.onKeyOther(view, content, event); } /** * Clear all the input state (autotext, autocap, multitap, undo) * from the specified Editable, going beyond Editable.clear(), which * just clears the text but not the input state. * * @param e the buffer whose text and state are to be cleared. */ public static void clear(Editable e) { e.clear(); e.removeSpan(ACTIVE); e.removeSpan(CAPPED); e.removeSpan(INHIBIT_REPLACEMENT); e.removeSpan(LAST_TYPED); QwertyKeyListener.Replaced[] repl = e.getSpans(0, e.length(), QwertyKeyListener.Replaced.class); final int count = repl.length; for (int i = 0; i < count; i++) { e.removeSpan(repl[i]); } } public void onSpanAdded(Spannable s, Object what, int start, int end) { } public void onSpanRemoved(Spannable s, Object what, int start, int end) { } public void onSpanChanged(Spannable s, Object what, int start, int end, int st, int en) { if (what == Selection.SELECTION_END) { s.removeSpan(ACTIVE); } } private KeyListener getKeyListener(KeyEvent event) { KeyCharacterMap kmap = event.getKeyCharacterMap(); int kind = kmap.getKeyboardType(); if (kind == KeyCharacterMap.ALPHA) { return QwertyKeyListener.getInstance(mAutoText, mAutoCap); } else if (kind == KeyCharacterMap.NUMERIC) { return MultiTapKeyListener.getInstance(mAutoText, mAutoCap); } else if (kind == KeyCharacterMap.FULL || kind == KeyCharacterMap.SPECIAL_FUNCTION) { // We consider special function keyboards full keyboards as a workaround for // devices that do not have built-in keyboards. Applications may try to inject // key events using the built-in keyboard device id which may be configured as // a special function keyboard using a default key map. Ideally, as of Honeycomb, // these applications should be modified to use KeyCharacterMap.VIRTUAL_KEYBOARD. return QwertyKeyListener.getInstanceForFullKeyboard(); } return NullKeyListener.getInstance(); } public enum Capitalize { NONE, SENTENCES, WORDS, CHARACTERS, } private static class NullKeyListener implements KeyListener { public int getInputType() { return InputType.TYPE_NULL; } public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) { return false; } public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) { return false; } public boolean onKeyOther(View view, Editable content, KeyEvent event) { return false; } public void clearMetaKeyState(View view, Editable content, int states) { } public static NullKeyListener getInstance() { if (sInstance != null) return sInstance; sInstance = new NullKeyListener(); return sInstance; } private static NullKeyListener sInstance; } public void release() { if (mResolver != null) { final ContentResolver contentResolver = mResolver.get(); if (contentResolver != null) { contentResolver.unregisterContentObserver(mObserver); mResolver.clear(); } mObserver = null; mResolver = null; mPrefsInited = false; } } private void initPrefs(Context context) { final ContentResolver contentResolver = context.getContentResolver(); mResolver = new WeakReference(contentResolver); if (mObserver == null) { mObserver = new SettingsObserver(); contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver); } updatePrefs(contentResolver); mPrefsInited = true; } private class SettingsObserver extends ContentObserver { public SettingsObserver() { super(new Handler()); } @Override public void onChange(boolean selfChange) { if (mResolver != null) { final ContentResolver contentResolver = mResolver.get(); if (contentResolver == null) { mPrefsInited = false; } else { updatePrefs(contentResolver); } } else { mPrefsInited = false; } } } private void updatePrefs(ContentResolver resolver) { boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0; boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0; boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0; boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0; mPrefs = (cap ? AUTO_CAP : 0) | (text ? AUTO_TEXT : 0) | (period ? AUTO_PERIOD : 0) | (pw ? SHOW_PASSWORD : 0); } /* package */ int getPrefs(Context context) { synchronized (this) { if (!mPrefsInited || mResolver.get() == null) { initPrefs(context); } } return mPrefs; } }