/*
 * 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.Editable;
import android.text.InputType;
import android.text.NoCopySpan;
import android.text.Selection;
import android.text.SpanWatcher;
import android.text.Spannable;
import android.text.TextUtils;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;

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 KeyListener, this class is only concerned with hardware keyboards. Software input methods have no obligation to trigger the methods in this class.
/** * This is the key listener for typing normal text. It delegates to * other key listeners appropriate to the current keyboard and language. * <p></p> * 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<ContentResolver> mResolver; private TextKeyListener.SettingsObserver mObserver;
Creates a new TextKeyListener with the specified capitalization and correction properties.
Params:
  • cap – when, if ever, to automatically capitalize.
  • autotext – whether to automatically do spelling corrections.
/** * 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.
Params:
  • cap – when, if ever, to automatically capitalize.
  • autotext – whether to automatically do spelling corrections.
/** * 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.
/** * 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.
Params:
  • cap – the capitalization rules to consider.
  • cs – the text in which an insertion is being made.
  • off – the offset into that text where the insertion is being made.
Returns:whether the character being inserted should be capitalized.
/** * 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.
Params:
  • e – the buffer whose text and state are to be cleared.
/** * 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>(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; } }