| /* |
| * 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) { } |
| } |
| |