/*
|
* 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.
|
* <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.
|
*
|
* @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>(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;
|
}
|
}
|