/*
|
* Copyright (C) 2006 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.os.Handler;
|
import android.os.SystemClock;
|
import android.text.Editable;
|
import android.text.Selection;
|
import android.text.SpanWatcher;
|
import android.text.Spannable;
|
import android.text.method.TextKeyListener.Capitalize;
|
import android.util.SparseArray;
|
import android.view.KeyEvent;
|
import android.view.View;
|
|
/**
|
* This is the standard key listener for alphabetic input on 12-key
|
* keyboards. You should generally not need to instantiate this yourself;
|
* TextKeyListener will do it for you.
|
* <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 MultiTapKeyListener extends BaseKeyListener
|
implements SpanWatcher {
|
private static MultiTapKeyListener[] sInstance =
|
new MultiTapKeyListener[Capitalize.values().length * 2];
|
|
private static final SparseArray<String> sRecs = new SparseArray<String>();
|
|
private Capitalize mCapitalize;
|
private boolean mAutoText;
|
|
static {
|
sRecs.put(KeyEvent.KEYCODE_1, ".,1!@#$%^&*:/?'=()");
|
sRecs.put(KeyEvent.KEYCODE_2, "abc2ABC");
|
sRecs.put(KeyEvent.KEYCODE_3, "def3DEF");
|
sRecs.put(KeyEvent.KEYCODE_4, "ghi4GHI");
|
sRecs.put(KeyEvent.KEYCODE_5, "jkl5JKL");
|
sRecs.put(KeyEvent.KEYCODE_6, "mno6MNO");
|
sRecs.put(KeyEvent.KEYCODE_7, "pqrs7PQRS");
|
sRecs.put(KeyEvent.KEYCODE_8, "tuv8TUV");
|
sRecs.put(KeyEvent.KEYCODE_9, "wxyz9WXYZ");
|
sRecs.put(KeyEvent.KEYCODE_0, "0+");
|
sRecs.put(KeyEvent.KEYCODE_POUND, " ");
|
};
|
|
public MultiTapKeyListener(Capitalize cap,
|
boolean autotext) {
|
mCapitalize = cap;
|
mAutoText = autotext;
|
}
|
|
/**
|
* Returns a new or existing instance with the specified capitalization
|
* and correction properties.
|
*/
|
public static MultiTapKeyListener getInstance(boolean autotext,
|
Capitalize cap) {
|
int off = cap.ordinal() * 2 + (autotext ? 1 : 0);
|
|
if (sInstance[off] == null) {
|
sInstance[off] = new MultiTapKeyListener(cap, autotext);
|
}
|
|
return sInstance[off];
|
}
|
|
public int getInputType() {
|
return makeTextContentType(mCapitalize, mAutoText);
|
}
|
|
public boolean onKeyDown(View view, Editable content,
|
int keyCode, KeyEvent event) {
|
int selStart, selEnd;
|
int pref = 0;
|
|
if (view != null) {
|
pref = TextKeyListener.getInstance().getPrefs(view.getContext());
|
}
|
|
{
|
int a = Selection.getSelectionStart(content);
|
int b = Selection.getSelectionEnd(content);
|
|
selStart = Math.min(a, b);
|
selEnd = Math.max(a, b);
|
}
|
|
int activeStart = content.getSpanStart(TextKeyListener.ACTIVE);
|
int activeEnd = content.getSpanEnd(TextKeyListener.ACTIVE);
|
|
// now for the multitap cases...
|
|
// Try to increment the character we were working on before
|
// if we have one and it's still the same key.
|
|
int rec = (content.getSpanFlags(TextKeyListener.ACTIVE)
|
& Spannable.SPAN_USER) >>> Spannable.SPAN_USER_SHIFT;
|
|
if (activeStart == selStart && activeEnd == selEnd &&
|
selEnd - selStart == 1 &&
|
rec >= 0 && rec < sRecs.size()) {
|
if (keyCode == KeyEvent.KEYCODE_STAR) {
|
char current = content.charAt(selStart);
|
|
if (Character.isLowerCase(current)) {
|
content.replace(selStart, selEnd,
|
String.valueOf(current).toUpperCase());
|
removeTimeouts(content);
|
new Timeout(content); // for its side effects
|
|
return true;
|
}
|
if (Character.isUpperCase(current)) {
|
content.replace(selStart, selEnd,
|
String.valueOf(current).toLowerCase());
|
removeTimeouts(content);
|
new Timeout(content); // for its side effects
|
|
return true;
|
}
|
}
|
|
if (sRecs.indexOfKey(keyCode) == rec) {
|
String val = sRecs.valueAt(rec);
|
char ch = content.charAt(selStart);
|
int ix = val.indexOf(ch);
|
|
if (ix >= 0) {
|
ix = (ix + 1) % (val.length());
|
|
content.replace(selStart, selEnd, val, ix, ix + 1);
|
removeTimeouts(content);
|
new Timeout(content); // for its side effects
|
|
return true;
|
}
|
}
|
|
// Is this key one we know about at all? If so, acknowledge
|
// that the selection is our fault but the key has changed
|
// or the text no longer matches, so move the selection over
|
// so that it inserts instead of replaces.
|
|
rec = sRecs.indexOfKey(keyCode);
|
|
if (rec >= 0) {
|
Selection.setSelection(content, selEnd, selEnd);
|
selStart = selEnd;
|
}
|
} else {
|
rec = sRecs.indexOfKey(keyCode);
|
}
|
|
if (rec >= 0) {
|
// We have a valid key. Replace the selection or insertion point
|
// with the first character for that key, and remember what
|
// record it came from for next time.
|
|
String val = sRecs.valueAt(rec);
|
|
int off = 0;
|
if ((pref & TextKeyListener.AUTO_CAP) != 0 &&
|
TextKeyListener.shouldCap(mCapitalize, content, selStart)) {
|
for (int i = 0; i < val.length(); i++) {
|
if (Character.isUpperCase(val.charAt(i))) {
|
off = i;
|
break;
|
}
|
}
|
}
|
|
if (selStart != selEnd) {
|
Selection.setSelection(content, selEnd);
|
}
|
|
content.setSpan(OLD_SEL_START, selStart, selStart,
|
Spannable.SPAN_MARK_MARK);
|
|
content.replace(selStart, selEnd, val, off, off + 1);
|
|
int oldStart = content.getSpanStart(OLD_SEL_START);
|
selEnd = Selection.getSelectionEnd(content);
|
|
if (selEnd != oldStart) {
|
Selection.setSelection(content, oldStart, selEnd);
|
|
content.setSpan(TextKeyListener.LAST_TYPED,
|
oldStart, selEnd,
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
content.setSpan(TextKeyListener.ACTIVE,
|
oldStart, selEnd,
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
|
(rec << Spannable.SPAN_USER_SHIFT));
|
|
}
|
|
removeTimeouts(content);
|
new Timeout(content); // for its side effects
|
|
// Set up the callback so we can remove the timeout if the
|
// cursor moves.
|
|
if (content.getSpanStart(this) < 0) {
|
KeyListener[] methods = content.getSpans(0, content.length(),
|
KeyListener.class);
|
for (Object method : methods) {
|
content.removeSpan(method);
|
}
|
content.setSpan(this, 0, content.length(),
|
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
}
|
|
return true;
|
}
|
|
return super.onKeyDown(view, content, keyCode, event);
|
}
|
|
public void onSpanChanged(Spannable buf,
|
Object what, int s, int e, int start, int stop) {
|
if (what == Selection.SELECTION_END) {
|
buf.removeSpan(TextKeyListener.ACTIVE);
|
removeTimeouts(buf);
|
}
|
}
|
|
private static void removeTimeouts(Spannable buf) {
|
Timeout[] timeout = buf.getSpans(0, buf.length(), Timeout.class);
|
|
for (int i = 0; i < timeout.length; i++) {
|
Timeout t = timeout[i];
|
|
t.removeCallbacks(t);
|
t.mBuffer = null;
|
buf.removeSpan(t);
|
}
|
}
|
|
private class Timeout
|
extends Handler
|
implements Runnable
|
{
|
public Timeout(Editable buffer) {
|
mBuffer = buffer;
|
mBuffer.setSpan(Timeout.this, 0, mBuffer.length(),
|
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
|
postAtTime(this, SystemClock.uptimeMillis() + 2000);
|
}
|
|
public void run() {
|
Spannable buf = mBuffer;
|
|
if (buf != null) {
|
int st = Selection.getSelectionStart(buf);
|
int en = Selection.getSelectionEnd(buf);
|
|
int start = buf.getSpanStart(TextKeyListener.ACTIVE);
|
int end = buf.getSpanEnd(TextKeyListener.ACTIVE);
|
|
if (st == start && en == end) {
|
Selection.setSelection(buf, Selection.getSelectionEnd(buf));
|
}
|
|
buf.removeSpan(Timeout.this);
|
}
|
}
|
|
private Editable mBuffer;
|
}
|
|
public void onSpanAdded(Spannable s, Object what, int start, int end) { }
|
public void onSpanRemoved(Spannable s, Object what, int start, int end) { }
|
}
|