M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions
@@ -0,0 +1,293 @@
/*
* 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.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
// XXX this doesn't extend MetaKeyKeyListener because the signatures
// don't match. Need to figure that out. Meanwhile the meta keys
// won't work in fields that don't take input.
public class ArrowKeyMovementMethod implements MovementMethod {
private boolean isCap(Spannable buffer) {
return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) ||
(MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
}
private boolean isAlt(Spannable buffer) {
return MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1;
}
private boolean up(TextView widget, Spannable buffer) {
boolean cap = isCap(buffer);
boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
if (alt) {
Selection.extendSelection(buffer, 0);
return true;
} else {
return Selection.extendUp(buffer, layout);
}
} else {
if (alt) {
Selection.setSelection(buffer, 0);
return true;
} else {
return Selection.moveUp(buffer, layout);
}
}
}
private boolean down(TextView widget, Spannable buffer) {
boolean cap = isCap(buffer);
boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
if (alt) {
Selection.extendSelection(buffer, buffer.length());
return true;
} else {
return Selection.extendDown(buffer, layout);
}
} else {
if (alt) {
Selection.setSelection(buffer, buffer.length());
return true;
} else {
return Selection.moveDown(buffer, layout);
}
}
}
private boolean left(TextView widget, Spannable buffer) {
boolean cap = isCap(buffer);
boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
if (alt) {
return Selection.extendToLeftEdge(buffer, layout);
} else {
return Selection.extendLeft(buffer, layout);
}
} else {
if (alt) {
return Selection.moveToLeftEdge(buffer, layout);
} else {
return Selection.moveLeft(buffer, layout);
}
}
}
private boolean right(TextView widget, Spannable buffer) {
boolean cap = isCap(buffer);
boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
if (alt) {
return Selection.extendToRightEdge(buffer, layout);
} else {
return Selection.extendRight(buffer, layout);
}
} else {
if (alt) {
return Selection.moveToRightEdge(buffer, layout);
} else {
return Selection.moveRight(buffer, layout);
}
}
}
public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
if (executeDown(widget, buffer, keyCode)) {
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
MetaKeyKeyListener.resetLockedMeta(buffer);
return true;
}
return false;
}
private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
boolean handled = false;
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
handled |= up(widget, buffer);
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
handled |= down(widget, buffer);
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
handled |= left(widget, buffer);
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
handled |= right(widget, buffer);
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) &&
(widget.showContextMenu())) {
handled = true;
}
}
if (handled) {
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
MetaKeyKeyListener.resetLockedMeta(buffer);
}
return handled;
}
public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
return false;
}
public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
int code = event.getKeyCode();
if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
int repeat = event.getRepeatCount();
boolean handled = false;
while ((--repeat) > 0) {
handled |= executeDown(view, text, code);
}
return handled;
}
return false;
}
public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
return false;
}
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int initialScrollX = -1, initialScrollY = -1;
if (event.getAction() == MotionEvent.ACTION_UP) {
initialScrollX = Touch.getInitialScrollX(widget, buffer);
initialScrollY = Touch.getInitialScrollY(widget, buffer);
}
boolean handled = Touch.onTouchEvent(widget, buffer, event);
if (widget.isFocused() && !widget.didTouchFocusSelect()) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
boolean cap = isCap(buffer);
if (cap) {
int offset = widget.getOffset((int) event.getX(), (int) event.getY());
buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
// Disallow intercepting of the touch events, so that
// users can scroll and select at the same time.
// without this, users would get booted out of select
// mode once the view detected it needed to scroll.
widget.getParent().requestDisallowInterceptTouchEvent(true);
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
boolean cap = isCap(buffer);
if (cap && handled) {
// Before selecting, make sure we've moved out of the "slop".
// handled will be true, if we're in select mode AND we're
// OUT of the slop
// Turn long press off while we're selecting. User needs to
// re-tap on the selection to enable long press
widget.cancelLongPress();
// Update selection as we're moving the selection area.
// Get the current touch position
int offset = widget.getOffset((int) event.getX(), (int) event.getY());
Selection.extendSelection(buffer, offset);
return true;
}
} else if (event.getAction() == MotionEvent.ACTION_UP) {
// If we have scrolled, then the up shouldn't move the cursor,
// but we do need to make sure the cursor is still visible at
// the current scroll offset to avoid the scroll jumping later
// to show it.
if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
(initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
widget.moveCursorToVisibleOffset();
return true;
}
int offset = widget.getOffset((int) event.getX(), (int) event.getY());
if (isCap(buffer)) {
buffer.removeSpan(LAST_TAP_DOWN);
Selection.extendSelection(buffer, offset);
} else {
Selection.setSelection(buffer, offset);
}
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
MetaKeyKeyListener.resetLockedMeta(buffer);
return true;
}
}
return handled;
}
public boolean canSelectArbitrarily() {
return true;
}
public void initialize(TextView widget, Spannable text) {
Selection.setSelection(text, 0);
}
public void onTakeFocus(TextView view, Spannable text, int dir) {
if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
if (view.getLayout() == null) {
// This shouldn't be null, but do something sensible if it is.
Selection.setSelection(text, text.length());
}
} else {
Selection.setSelection(text, text.length());
}
}
public static MovementMethod getInstance() {
if (sInstance == null) {
sInstance = new ArrowKeyMovementMethod();
}
return sInstance;
}
private static final Object LAST_TAP_DOWN = new Object();
private static ArrowKeyMovementMethod sInstance;
}
@@ -0,0 +1,160 @@
/*
* 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.view.KeyEvent;
import android.view.View;
import android.text.*;
import android.text.method.TextKeyListener.Capitalize;
import android.widget.TextView;
public abstract class BaseKeyListener
extends MetaKeyKeyListener
implements KeyListener {
/* package */ static final Object OLD_SEL_START = new NoCopySpan.Concrete();
/**
* Performs the action that happens when you press the DEL key in
* a TextView. If there is a selection, deletes the selection;
* otherwise, DEL alone deletes the character before the cursor,
* if any;
* ALT+DEL deletes everything on the line the cursor is on.
*
* @return true if anything was deleted; false otherwise.
*/
public boolean backspace(View view, Editable content, int keyCode,
KeyEvent event) {
int selStart, selEnd;
boolean result = true;
{
int a = Selection.getSelectionStart(content);
int b = Selection.getSelectionEnd(content);
selStart = Math.min(a, b);
selEnd = Math.max(a, b);
}
if (selStart != selEnd) {
content.delete(selStart, selEnd);
} else if (altBackspace(view, content, keyCode, event)) {
result = true;
} else {
int to = TextUtils.getOffsetBefore(content, selEnd);
if (to != selEnd) {
content.delete(Math.min(to, selEnd), Math.max(to, selEnd));
}
else {
result = false;
}
}
if (result)
adjustMetaAfterKeypress(content);
return result;
}
private boolean altBackspace(View view, Editable content, int keyCode,
KeyEvent event) {
if (getMetaState(content, META_ALT_ON) != 1) {
return false;
}
if (!(view instanceof TextView)) {
return false;
}
Layout layout = ((TextView) view).getLayout();
if (layout == null) {
return false;
}
int l = layout.getLineForOffset(Selection.getSelectionStart(content));
int start = layout.getLineStart(l);
int end = layout.getLineEnd(l);
if (end == start) {
return false;
}
content.delete(start, end);
return true;
}
static int makeTextContentType(Capitalize caps, boolean autoText) {
int contentType = InputType.TYPE_CLASS_TEXT;
switch (caps) {
case CHARACTERS:
contentType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
break;
case WORDS:
contentType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS;
break;
case SENTENCES:
contentType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
break;
}
if (autoText) {
contentType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
}
return contentType;
}
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DEL) {
backspace(view, content, keyCode, event);
return true;
}
return super.onKeyDown(view, content, keyCode, event);
}
/**
* Base implementation handles ACTION_MULTIPLE KEYCODE_UNKNOWN by inserting
* the event's text into the content.
*/
public boolean onKeyOther(View view, Editable content, KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_MULTIPLE
|| event.getKeyCode() != KeyEvent.KEYCODE_UNKNOWN) {
// Not something we are interested in.
return false;
}
int selStart, selEnd;
{
int a = Selection.getSelectionStart(content);
int b = Selection.getSelectionEnd(content);
selStart = Math.min(a, b);
selEnd = Math.max(a, b);
}
CharSequence text = event.getCharacters();
if (text == null) {
return false;
}
content.replace(selStart, selEnd, text);
return true;
}
}
@@ -0,0 +1,142 @@
/*
* Copyright (C) 2008 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 com.android.internal.R;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.text.*;
import android.view.LayoutInflater;
import android.view.View.OnClickListener;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
/**
* Dialog for choosing accented characters related to a base character.
*/
public class CharacterPickerDialog extends Dialog
implements OnItemClickListener, OnClickListener {
private View mView;
private Editable mText;
private String mOptions;
private boolean mInsert;
private LayoutInflater mInflater;
private Button mCancelButton;
/**
* Creates a new CharacterPickerDialog that presents the specified
* <code>options</code> for insertion or replacement (depending on
* the sense of <code>insert</code>) into <code>text</code>.
*/
public CharacterPickerDialog(Context context, View view,
Editable text, String options,
boolean insert) {
super(context, com.android.internal.R.style.Theme_Panel);
mView = view;
mText = text;
mOptions = options;
mInsert = insert;
mInflater = LayoutInflater.from(context);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WindowManager.LayoutParams params = getWindow().getAttributes();
params.token = mView.getApplicationWindowToken();
params.type = params.TYPE_APPLICATION_ATTACHED_DIALOG;
params.flags = params.flags | Window.FEATURE_NO_TITLE;
setContentView(R.layout.character_picker);
GridView grid = (GridView) findViewById(R.id.characterPicker);
grid.setAdapter(new OptionsAdapter(getContext()));
grid.setOnItemClickListener(this);
mCancelButton = (Button) findViewById(R.id.cancel);
mCancelButton.setOnClickListener(this);
}
/**
* Handles clicks on the character buttons.
*/
public void onItemClick(AdapterView parent, View view, int position, long id) {
String result = String.valueOf(mOptions.charAt(position));
replaceCharacterAndClose(result);
}
private void replaceCharacterAndClose(CharSequence replace) {
int selEnd = Selection.getSelectionEnd(mText);
if (mInsert || selEnd == 0) {
mText.insert(selEnd, replace);
} else {
mText.replace(selEnd - 1, selEnd, replace);
}
dismiss();
}
/**
* Handles clicks on the Cancel button.
*/
public void onClick(View v) {
if (v == mCancelButton) {
dismiss();
} else if (v instanceof Button) {
CharSequence result = ((Button) v).getText();
replaceCharacterAndClose(result);
}
}
private class OptionsAdapter extends BaseAdapter {
public OptionsAdapter(Context context) {
super();
}
public View getView(int position, View convertView, ViewGroup parent) {
Button b = (Button)
mInflater.inflate(R.layout.character_picker_button, null);
b.setText(String.valueOf(mOptions.charAt(position)));
b.setOnClickListener(CharacterPickerDialog.this);
return b;
}
public final int getCount() {
return mOptions.length();
}
public final Object getItem(int position) {
return String.valueOf(mOptions.charAt(position));
}
public final long getItemId(int position) {
return position;
}
}
}
@@ -0,0 +1,58 @@
/*
* 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.view.KeyEvent;
import android.text.InputType;
/**
* For entering dates in a text field.
*/
public class DateKeyListener extends NumberKeyListener
{
public int getInputType() {
return InputType.TYPE_CLASS_DATETIME
| InputType.TYPE_DATETIME_VARIATION_DATE;
}
@Override
protected char[] getAcceptedChars()
{
return CHARACTERS;
}
public static DateKeyListener getInstance() {
if (sInstance != null)
return sInstance;
sInstance = new DateKeyListener();
return sInstance;
}
/**
* The characters that are used.
*
* @see KeyEvent#getMatch
* @see #getAcceptedChars
*/
public static final char[] CHARACTERS = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'/', '-', '.'
};
private static DateKeyListener sInstance;
}
@@ -0,0 +1,58 @@
/*
* 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.text.InputType;
import android.view.KeyEvent;
/**
* For entering dates and times in the same text field.
*/
public class DateTimeKeyListener extends NumberKeyListener
{
public int getInputType() {
return InputType.TYPE_CLASS_DATETIME
| InputType.TYPE_DATETIME_VARIATION_NORMAL;
}
@Override
protected char[] getAcceptedChars()
{
return CHARACTERS;
}
public static DateTimeKeyListener getInstance() {
if (sInstance != null)
return sInstance;
sInstance = new DateTimeKeyListener();
return sInstance;
}
/**
* The characters that are used.
*
* @see KeyEvent#getMatch
* @see #getAcceptedChars
*/
public static final char[] CHARACTERS = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'm',
'p', ':', '/', '-', ' '
};
private static DateTimeKeyListener sInstance;
}
@@ -0,0 +1,113 @@
/*
* 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.view.KeyEvent;
import android.view.KeyCharacterMap.KeyData;
import android.text.InputType;
import android.text.Spannable;
/**
* For dialing-only text entry
*/
public class DialerKeyListener extends NumberKeyListener
{
@Override
protected char[] getAcceptedChars()
{
return CHARACTERS;
}
public static DialerKeyListener getInstance() {
if (sInstance != null)
return sInstance;
sInstance = new DialerKeyListener();
return sInstance;
}
public int getInputType() {
return InputType.TYPE_CLASS_PHONE;
}
/**
* Overrides the superclass's lookup method to prefer the number field
* from the KeyEvent.
*/
protected int lookup(KeyEvent event, Spannable content) {
int meta = getMetaState(content);
int number = event.getNumber();
/*
* Prefer number if no meta key is active, or if it produces something
* valid and the meta lookup does not.
*/
if ((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) {
if (number != 0) {
return number;
}
}
int match = super.lookup(event, content);
if (match != 0) {
return match;
} else {
/*
* If a meta key is active but the lookup with the meta key
* did not produce anything, try some other meta keys, because
* the user might have pressed SHIFT when they meant ALT,
* or vice versa.
*/
if (meta != 0) {
KeyData kd = new KeyData();
char[] accepted = getAcceptedChars();
if (event.getKeyData(kd)) {
for (int i = 1; i < kd.meta.length; i++) {
if (ok(accepted, kd.meta[i])) {
return kd.meta[i];
}
}
}
}
/*
* Otherwise, use the number associated with the key, since
* whatever they wanted to do with the meta key does not
* seem to be valid here.
*/
return number;
}
}
/**
* The characters that are used.
*
* @see KeyEvent#getMatch
* @see #getAcceptedChars
*/
public static final char[] CHARACTERS = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*',
'+', '-', '(', ')', ',', '/', 'N', '.', ' ', ';'
};
private static DialerKeyListener sInstance;
}
@@ -0,0 +1,218 @@
/*
* 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.text.InputType;
import android.text.Spanned;
import android.text.SpannableStringBuilder;
import android.view.KeyEvent;
/**
* For digits-only text entry
*/
public class DigitsKeyListener extends NumberKeyListener
{
private char[] mAccepted;
private boolean mSign;
private boolean mDecimal;
private static final int SIGN = 1;
private static final int DECIMAL = 2;
@Override
protected char[] getAcceptedChars() {
return mAccepted;
}
/**
* The characters that are used.
*
* @see KeyEvent#getMatch
* @see #getAcceptedChars
*/
private static final char[][] CHARACTERS = new char[][] {
new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' },
new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' },
new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.' },
};
/**
* Allocates a DigitsKeyListener that accepts the digits 0 through 9.
*/
public DigitsKeyListener() {
this(false, false);
}
/**
* Allocates a DigitsKeyListener that accepts the digits 0 through 9,
* plus the minus sign (only at the beginning) and/or decimal point
* (only one per field) if specified.
*/
public DigitsKeyListener(boolean sign, boolean decimal) {
mSign = sign;
mDecimal = decimal;
int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
mAccepted = CHARACTERS[kind];
}
/**
* Returns a DigitsKeyListener that accepts the digits 0 through 9.
*/
public static DigitsKeyListener getInstance() {
return getInstance(false, false);
}
/**
* Returns a DigitsKeyListener that accepts the digits 0 through 9,
* plus the minus sign (only at the beginning) and/or decimal point
* (only one per field) if specified.
*/
public static DigitsKeyListener getInstance(boolean sign, boolean decimal) {
int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
if (sInstance[kind] != null)
return sInstance[kind];
sInstance[kind] = new DigitsKeyListener(sign, decimal);
return sInstance[kind];
}
/**
* Returns a DigitsKeyListener that accepts only the characters
* that appear in the specified String. Note that not all characters
* may be available on every keyboard.
*/
public static DigitsKeyListener getInstance(String accepted) {
// TODO: do we need a cache of these to avoid allocating?
DigitsKeyListener dim = new DigitsKeyListener();
dim.mAccepted = new char[accepted.length()];
accepted.getChars(0, accepted.length(), dim.mAccepted, 0);
return dim;
}
public int getInputType() {
int contentType = InputType.TYPE_CLASS_NUMBER;
if (mSign) {
contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
}
if (mDecimal) {
contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
}
return contentType;
}
@Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
CharSequence out = super.filter(source, start, end, dest, dstart, dend);
if (mSign == false && mDecimal == false) {
return out;
}
if (out != null) {
source = out;
start = 0;
end = out.length();
}
int sign = -1;
int decimal = -1;
int dlen = dest.length();
/*
* Find out if the existing text has '-' or '.' characters.
*/
for (int i = 0; i < dstart; i++) {
char c = dest.charAt(i);
if (c == '-') {
sign = i;
} else if (c == '.') {
decimal = i;
}
}
for (int i = dend; i < dlen; i++) {
char c = dest.charAt(i);
if (c == '-') {
return ""; // Nothing can be inserted in front of a '-'.
} else if (c == '.') {
decimal = i;
}
}
/*
* If it does, we must strip them out from the source.
* In addition, '-' must be the very first character,
* and nothing can be inserted before an existing '-'.
* Go in reverse order so the offsets are stable.
*/
SpannableStringBuilder stripped = null;
for (int i = end - 1; i >= start; i--) {
char c = source.charAt(i);
boolean strip = false;
if (c == '-') {
if (i != start || dstart != 0) {
strip = true;
} else if (sign >= 0) {
strip = true;
} else {
sign = i;
}
} else if (c == '.') {
if (decimal >= 0) {
strip = true;
} else {
decimal = i;
}
}
if (strip) {
if (end == start + 1) {
return ""; // Only one character, and it was stripped.
}
if (stripped == null) {
stripped = new SpannableStringBuilder(source, start, end);
}
stripped.delete(i - start, i + 1 - start);
}
}
if (stripped != null) {
return stripped;
} else if (out != null) {
return out;
} else {
return null;
}
}
private static DigitsKeyListener[] sInstance = new DigitsKeyListener[4];
}
@@ -0,0 +1,59 @@
/*
* 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.graphics.Rect;
import android.text.GetChars;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.TextUtils;
import android.view.View;
/**
* This transformation method causes any carriage return characters (\r)
* to be hidden by displaying them as zero-width non-breaking space
* characters (\uFEFF).
*/
public class HideReturnsTransformationMethod
extends ReplacementTransformationMethod {
private static char[] ORIGINAL = new char[] { '\r' };
private static char[] REPLACEMENT = new char[] { '\uFEFF' };
/**
* The character to be replaced is \r.
*/
protected char[] getOriginal() {
return ORIGINAL;
}
/**
* The character that \r is replaced with is \uFEFF.
*/
protected char[] getReplacement() {
return REPLACEMENT;
}
public static HideReturnsTransformationMethod getInstance() {
if (sInstance != null)
return sInstance;
sInstance = new HideReturnsTransformationMethod();
return sInstance;
}
private static HideReturnsTransformationMethod sInstance;
}
@@ -0,0 +1,79 @@
/*
* 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.text.Editable;
import android.view.KeyEvent;
import android.view.View;
/**
* Interface for converting text key events into edit operations on an
* Editable class. Note that for must cases this interface has been
* superceded by general soft input methods as defined by
* {@link android.view.inputmethod.InputMethod}; it should only be used
* for cases where an application has its own on-screen keypad and also wants
* to process hard keyboard events to match it.
*/
public interface KeyListener {
/**
* Return the type of text that this key listener is manipulating,
* as per {@link android.text.InputType}. This is used to
* determine the mode of the soft keyboard that is shown for the editor.
*
* <p>If you return
* {@link android.text.InputType#TYPE_NULL}
* then <em>no</em> soft keyboard will provided. In other words, you
* must be providing your own key pad for on-screen input and the key
* listener will be used to handle input from a hard keyboard.
*
* <p>If you
* return any other value, a soft input method will be created when the
* user puts focus in the editor, which will provide a keypad and also
* consume hard key events. This means that the key listener will generally
* not be used, instead the soft input method will take care of managing
* key input as per the content type returned here.
*/
public int getInputType();
/**
* If the key listener wants to handle this key, return true,
* otherwise return false and the caller (i.e. the widget host)
* will handle the key.
*/
public boolean onKeyDown(View view, Editable text,
int keyCode, KeyEvent event);
/**
* If the key listener wants to handle this key release, return true,
* otherwise return false and the caller (i.e. the widget host)
* will handle the key.
*/
public boolean onKeyUp(View view, Editable text,
int keyCode, KeyEvent event);
/**
* If the key listener wants to other kinds of key events, return true,
* otherwise return false and the caller (i.e. the widget host)
* will handle the key.
*/
public boolean onKeyOther(View view, Editable text, KeyEvent event);
/**
* Remove the given shift states from the edited text.
*/
public void clearMetaKeyState(View view, Editable content, int states);
}
@@ -0,0 +1,256 @@
/*
* 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.content.Intent;
import android.net.Uri;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.text.*;
import android.text.style.*;
import android.view.View;
import android.widget.TextView;
public class
LinkMovementMethod
extends ScrollingMovementMethod
{
private static final int CLICK = 1;
private static final int UP = 2;
private static final int DOWN = 3;
@Override
public boolean onKeyDown(TextView widget, Spannable buffer,
int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
if (event.getRepeatCount() == 0) {
if (action(CLICK, widget, buffer)) {
return true;
}
}
}
return super.onKeyDown(widget, buffer, keyCode, event);
}
@Override
protected boolean up(TextView widget, Spannable buffer) {
if (action(UP, widget, buffer)) {
return true;
}
return super.up(widget, buffer);
}
@Override
protected boolean down(TextView widget, Spannable buffer) {
if (action(DOWN, widget, buffer)) {
return true;
}
return super.down(widget, buffer);
}
@Override
protected boolean left(TextView widget, Spannable buffer) {
if (action(UP, widget, buffer)) {
return true;
}
return super.left(widget, buffer);
}
@Override
protected boolean right(TextView widget, Spannable buffer) {
if (action(DOWN, widget, buffer)) {
return true;
}
return super.right(widget, buffer);
}
private boolean action(int what, TextView widget, Spannable buffer) {
boolean handled = false;
Layout layout = widget.getLayout();
int padding = widget.getTotalPaddingTop() +
widget.getTotalPaddingBottom();
int areatop = widget.getScrollY();
int areabot = areatop + widget.getHeight() - padding;
int linetop = layout.getLineForVertical(areatop);
int linebot = layout.getLineForVertical(areabot);
int first = layout.getLineStart(linetop);
int last = layout.getLineEnd(linebot);
ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class);
int a = Selection.getSelectionStart(buffer);
int b = Selection.getSelectionEnd(buffer);
int selStart = Math.min(a, b);
int selEnd = Math.max(a, b);
if (selStart < 0) {
if (buffer.getSpanStart(FROM_BELOW) >= 0) {
selStart = selEnd = buffer.length();
}
}
if (selStart > last)
selStart = selEnd = Integer.MAX_VALUE;
if (selEnd < first)
selStart = selEnd = -1;
switch (what) {
case CLICK:
if (selStart == selEnd) {
return false;
}
ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class);
if (link.length != 1)
return false;
link[0].onClick(widget);
break;
case UP:
int beststart, bestend;
beststart = -1;
bestend = -1;
for (int i = 0; i < candidates.length; i++) {
int end = buffer.getSpanEnd(candidates[i]);
if (end < selEnd || selStart == selEnd) {
if (end > bestend) {
beststart = buffer.getSpanStart(candidates[i]);
bestend = end;
}
}
}
if (beststart >= 0) {
Selection.setSelection(buffer, bestend, beststart);
return true;
}
break;
case DOWN:
beststart = Integer.MAX_VALUE;
bestend = Integer.MAX_VALUE;
for (int i = 0; i < candidates.length; i++) {
int start = buffer.getSpanStart(candidates[i]);
if (start > selStart || selStart == selEnd) {
if (start < beststart) {
beststart = start;
bestend = buffer.getSpanEnd(candidates[i]);
}
}
}
if (bestend < Integer.MAX_VALUE) {
Selection.setSelection(buffer, beststart, bestend);
return true;
}
break;
}
return false;
}
public boolean onKeyUp(TextView widget, Spannable buffer,
int keyCode, KeyEvent event) {
return false;
}
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
} else {
Selection.removeSelection(buffer);
}
}
return super.onTouchEvent(widget, buffer, event);
}
public void initialize(TextView widget, Spannable text) {
Selection.removeSelection(text);
text.removeSpan(FROM_BELOW);
}
public void onTakeFocus(TextView view, Spannable text, int dir) {
Selection.removeSelection(text);
if ((dir & View.FOCUS_BACKWARD) != 0) {
text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT);
} else {
text.removeSpan(FROM_BELOW);
}
}
public static MovementMethod getInstance() {
if (sInstance == null)
sInstance = new LinkMovementMethod();
return sInstance;
}
private static LinkMovementMethod sInstance;
private static Object FROM_BELOW = new NoCopySpan.Concrete();
}
@@ -0,0 +1,493 @@
/*
* 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.view.KeyEvent;
import android.view.View;
import android.text.*;
/**
* This base class encapsulates the behavior for handling the meta keys
* (shift and alt) and the pseudo-meta state of selecting text.
* Key listeners that care about meta state should
* inherit from it; you should not instantiate this class directly in a client.
*/
public abstract class MetaKeyKeyListener {
public static final int META_SHIFT_ON = KeyEvent.META_SHIFT_ON;
public static final int META_ALT_ON = KeyEvent.META_ALT_ON;
public static final int META_SYM_ON = KeyEvent.META_SYM_ON;
private static final int LOCKED_SHIFT = 8;
public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << LOCKED_SHIFT;
public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << LOCKED_SHIFT;
public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << LOCKED_SHIFT;
/**
* @hide pending API review
*/
public static final int META_SELECTING = 1 << 16;
private static final int USED_SHIFT = 24;
private static final long META_CAP_USED = ((long)KeyEvent.META_SHIFT_ON) << USED_SHIFT;
private static final long META_ALT_USED = ((long)KeyEvent.META_ALT_ON) << USED_SHIFT;
private static final long META_SYM_USED = ((long)KeyEvent.META_SYM_ON) << USED_SHIFT;
private static final int PRESSED_SHIFT = 32;
private static final long META_CAP_PRESSED = ((long)KeyEvent.META_SHIFT_ON) << PRESSED_SHIFT;
private static final long META_ALT_PRESSED = ((long)KeyEvent.META_ALT_ON) << PRESSED_SHIFT;
private static final long META_SYM_PRESSED = ((long)KeyEvent.META_SYM_ON) << PRESSED_SHIFT;
private static final int RELEASED_SHIFT = 40;
private static final long META_CAP_RELEASED = ((long)KeyEvent.META_SHIFT_ON) << RELEASED_SHIFT;
private static final long META_ALT_RELEASED = ((long)KeyEvent.META_ALT_ON) << RELEASED_SHIFT;
private static final long META_SYM_RELEASED = ((long)KeyEvent.META_SYM_ON) << RELEASED_SHIFT;
private static final long META_SHIFT_MASK = META_SHIFT_ON
| META_CAP_LOCKED | META_CAP_USED
| META_CAP_PRESSED | META_CAP_RELEASED;
private static final long META_ALT_MASK = META_ALT_ON
| META_ALT_LOCKED | META_ALT_USED
| META_ALT_PRESSED | META_ALT_RELEASED;
private static final long META_SYM_MASK = META_SYM_ON
| META_SYM_LOCKED | META_SYM_USED
| META_SYM_PRESSED | META_SYM_RELEASED;
private static final Object CAP = new NoCopySpan.Concrete();
private static final Object ALT = new NoCopySpan.Concrete();
private static final Object SYM = new NoCopySpan.Concrete();
private static final Object SELECTING = new NoCopySpan.Concrete();
/**
* Resets all meta state to inactive.
*/
public static void resetMetaState(Spannable text) {
text.removeSpan(CAP);
text.removeSpan(ALT);
text.removeSpan(SYM);
text.removeSpan(SELECTING);
}
/**
* Gets the state of the meta keys.
*
* @param text the buffer in which the meta key would have been pressed.
*
* @return an integer in which each bit set to one represents a pressed
* or locked meta key.
*/
public static final int getMetaState(CharSequence text) {
return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) |
getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) |
getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) |
getActive(text, SELECTING, META_SELECTING, META_SELECTING);
}
/**
* Gets the state of a particular meta key.
*
* @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON, or META_SELECTING
* @param text the buffer in which the meta key would have been pressed.
*
* @return 0 if inactive, 1 if active, 2 if locked.
*/
public static final int getMetaState(CharSequence text, int meta) {
switch (meta) {
case META_SHIFT_ON:
return getActive(text, CAP, 1, 2);
case META_ALT_ON:
return getActive(text, ALT, 1, 2);
case META_SYM_ON:
return getActive(text, SYM, 1, 2);
case META_SELECTING:
return getActive(text, SELECTING, 1, 2);
default:
return 0;
}
}
private static int getActive(CharSequence text, Object meta,
int on, int lock) {
if (!(text instanceof Spanned)) {
return 0;
}
Spanned sp = (Spanned) text;
int flag = sp.getSpanFlags(meta);
if (flag == LOCKED) {
return lock;
} else if (flag != 0) {
return on;
} else {
return 0;
}
}
/**
* Call this method after you handle a keypress so that the meta
* state will be reset to unshifted (if it is not still down)
* or primed to be reset to unshifted (once it is released).
*/
public static void adjustMetaAfterKeypress(Spannable content) {
adjust(content, CAP);
adjust(content, ALT);
adjust(content, SYM);
}
/**
* Returns true if this object is one that this class would use to
* keep track of any meta state in the specified text.
*/
public static boolean isMetaTracker(CharSequence text, Object what) {
return what == CAP || what == ALT || what == SYM ||
what == SELECTING;
}
/**
* Returns true if this object is one that this class would use to
* keep track of the selecting meta state in the specified text.
*/
public static boolean isSelectingMetaTracker(CharSequence text, Object what) {
return what == SELECTING;
}
private static void adjust(Spannable content, Object what) {
int current = content.getSpanFlags(what);
if (current == PRESSED)
content.setSpan(what, 0, 0, USED);
else if (current == RELEASED)
content.removeSpan(what);
}
/**
* Call this if you are a method that ignores the locked meta state
* (arrow keys, for example) and you handle a key.
*/
protected static void resetLockedMeta(Spannable content) {
resetLock(content, CAP);
resetLock(content, ALT);
resetLock(content, SYM);
resetLock(content, SELECTING);
}
private static void resetLock(Spannable content, Object what) {
int current = content.getSpanFlags(what);
if (current == LOCKED)
content.removeSpan(what);
}
/**
* Handles presses of the meta keys.
*/
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
press(content, CAP);
return true;
}
if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
|| keyCode == KeyEvent.KEYCODE_NUM) {
press(content, ALT);
return true;
}
if (keyCode == KeyEvent.KEYCODE_SYM) {
press(content, SYM);
return true;
}
return false; // no super to call through to
}
private void press(Editable content, Object what) {
int state = content.getSpanFlags(what);
if (state == PRESSED)
; // repeat before use
else if (state == RELEASED)
content.setSpan(what, 0, 0, LOCKED);
else if (state == USED)
; // repeat after use
else if (state == LOCKED)
content.removeSpan(what);
else
content.setSpan(what, 0, 0, PRESSED);
}
/**
* Start selecting text.
* @hide pending API review
*/
public static void startSelecting(View view, Spannable content) {
content.setSpan(SELECTING, 0, 0, PRESSED);
}
/**
* Stop selecting text. This does not actually collapse the selection;
* call {@link android.text.Selection#setSelection} too.
* @hide pending API review
*/
public static void stopSelecting(View view, Spannable content) {
content.removeSpan(SELECTING);
}
/**
* Handles release of the meta keys.
*/
public boolean onKeyUp(View view, Editable content, int keyCode,
KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
release(content, CAP);
return true;
}
if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
|| keyCode == KeyEvent.KEYCODE_NUM) {
release(content, ALT);
return true;
}
if (keyCode == KeyEvent.KEYCODE_SYM) {
release(content, SYM);
return true;
}
return false; // no super to call through to
}
private void release(Editable content, Object what) {
int current = content.getSpanFlags(what);
if (current == USED)
content.removeSpan(what);
else if (current == PRESSED)
content.setSpan(what, 0, 0, RELEASED);
}
public void clearMetaKeyState(View view, Editable content, int states) {
clearMetaKeyState(content, states);
}
public static void clearMetaKeyState(Editable content, int states) {
if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP);
if ((states&META_ALT_ON) != 0) content.removeSpan(ALT);
if ((states&META_SYM_ON) != 0) content.removeSpan(SYM);
if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING);
}
/**
* Call this if you are a method that ignores the locked meta state
* (arrow keys, for example) and you handle a key.
*/
public static long resetLockedMeta(long state) {
state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK);
state = resetLock(state, META_ALT_ON, META_ALT_MASK);
state = resetLock(state, META_SYM_ON, META_SYM_MASK);
return state;
}
private static long resetLock(long state, int what, long mask) {
if ((state&(((long)what)<<LOCKED_SHIFT)) != 0) {
state &= ~mask;
}
return state;
}
// ---------------------------------------------------------------------
// Version of API that operates on a state bit mask
// ---------------------------------------------------------------------
/**
* Gets the state of the meta keys.
*
* @param state the current meta state bits.
*
* @return an integer in which each bit set to one represents a pressed
* or locked meta key.
*/
public static final int getMetaState(long state) {
return getActive(state, META_SHIFT_ON, META_SHIFT_ON, META_CAP_LOCKED) |
getActive(state, META_ALT_ON, META_ALT_ON, META_ALT_LOCKED) |
getActive(state, META_SYM_ON, META_SYM_ON, META_SYM_LOCKED);
}
/**
* Gets the state of a particular meta key.
*
* @param state the current state bits.
* @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
*
* @return 0 if inactive, 1 if active, 2 if locked.
*/
public static final int getMetaState(long state, int meta) {
switch (meta) {
case META_SHIFT_ON:
return getActive(state, meta, 1, 2);
case META_ALT_ON:
return getActive(state, meta, 1, 2);
case META_SYM_ON:
return getActive(state, meta, 1, 2);
default:
return 0;
}
}
private static int getActive(long state, int meta, int on, int lock) {
if ((state&(meta<<LOCKED_SHIFT)) != 0) {
return lock;
} else if ((state&meta) != 0) {
return on;
} else {
return 0;
}
}
/**
* Call this method after you handle a keypress so that the meta
* state will be reset to unshifted (if it is not still down)
* or primed to be reset to unshifted (once it is released). Takes
* the current state, returns the new state.
*/
public static long adjustMetaAfterKeypress(long state) {
state = adjust(state, META_SHIFT_ON, META_SHIFT_MASK);
state = adjust(state, META_ALT_ON, META_ALT_MASK);
state = adjust(state, META_SYM_ON, META_SYM_MASK);
return state;
}
private static long adjust(long state, int what, long mask) {
if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
return (state&~mask) | what | ((long)what)<<USED_SHIFT;
else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0)
return state & ~mask;
return state;
}
/**
* Handles presses of the meta keys.
*/
public static long handleKeyDown(long state, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
return press(state, META_SHIFT_ON, META_SHIFT_MASK);
}
if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
|| keyCode == KeyEvent.KEYCODE_NUM) {
return press(state, META_ALT_ON, META_ALT_MASK);
}
if (keyCode == KeyEvent.KEYCODE_SYM) {
return press(state, META_SYM_ON, META_SYM_MASK);
}
return state;
}
private static long press(long state, int what, long mask) {
if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
; // repeat before use
else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0)
state = (state&~mask) | what | (((long)what) << LOCKED_SHIFT);
else if ((state&(((long)what)<<USED_SHIFT)) != 0)
; // repeat after use
else if ((state&(((long)what)<<LOCKED_SHIFT)) != 0)
state = state&~mask;
else
state = state | what | (((long)what)<<PRESSED_SHIFT);
return state;
}
/**
* Handles release of the meta keys.
*/
public static long handleKeyUp(long state, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
return release(state, META_SHIFT_ON, META_SHIFT_MASK);
}
if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
|| keyCode == KeyEvent.KEYCODE_NUM) {
return release(state, META_ALT_ON, META_ALT_MASK);
}
if (keyCode == KeyEvent.KEYCODE_SYM) {
return release(state, META_SYM_ON, META_SYM_MASK);
}
return state;
}
private static long release(long state, int what, long mask) {
if ((state&(((long)what)<<USED_SHIFT)) != 0)
state = state&~mask;
else if ((state&(((long)what)<<PRESSED_SHIFT)) != 0)
state = state | what | (((long)what)<<RELEASED_SHIFT);
return state;
}
public long clearMetaKeyState(long state, int which) {
if ((which&META_SHIFT_ON) != 0)
state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK);
if ((which&META_ALT_ON) != 0)
state = resetLock(state, META_ALT_ON, META_ALT_MASK);
if ((which&META_SYM_ON) != 0)
state = resetLock(state, META_SYM_ON, META_SYM_MASK);
return state;
}
/**
* The meta key has been pressed but has not yet been used.
*/
private static final int PRESSED =
Spannable.SPAN_MARK_MARK | (1 << Spannable.SPAN_USER_SHIFT);
/**
* The meta key has been pressed and released but has still
* not yet been used.
*/
private static final int RELEASED =
Spannable.SPAN_MARK_MARK | (2 << Spannable.SPAN_USER_SHIFT);
/**
* The meta key has been pressed and used but has not yet been released.
*/
private static final int USED =
Spannable.SPAN_MARK_MARK | (3 << Spannable.SPAN_USER_SHIFT);
/**
* The meta key has been pressed and released without use, and then
* pressed again; it may also have been released again.
*/
private static final int LOCKED =
Spannable.SPAN_MARK_MARK | (4 << Spannable.SPAN_USER_SHIFT);
}
@@ -0,0 +1,51 @@
/*
* 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.widget.TextView;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.text.*;
public interface MovementMethod
{
public void initialize(TextView widget, Spannable text);
public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event);
public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event);
/**
* If the key listener wants to other kinds of key events, return true,
* otherwise return false and the caller (i.e. the widget host)
* will handle the key.
*/
public boolean onKeyOther(TextView view, Spannable text, KeyEvent event);
public void onTakeFocus(TextView widget, Spannable text, int direction);
public boolean onTrackballEvent(TextView widget, Spannable text,
MotionEvent event);
public boolean onTouchEvent(TextView widget, Spannable text,
MotionEvent event);
/**
* Returns true if this movement method allows arbitrary selection
* of any text; false if it has no selection (like a movement method
* that only scrolls) or a constrained selection (for example
* limited to links. The "Select All" menu item is disabled
* if arbitrary selection is not allowed.
*/
public boolean canSelectArbitrarily();
}
@@ -0,0 +1,286 @@
/*
* 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.view.KeyEvent;
import android.view.View;
import android.os.Handler;
import android.os.SystemClock;
import android.text.*;
import android.text.method.TextKeyListener.Capitalize;
import android.util.SparseArray;
/**
* 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.
*/
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);
Timeout t = new Timeout(content);
return true;
}
if (Character.isUpperCase(current)) {
content.replace(selStart, selEnd,
String.valueOf(current).toLowerCase());
removeTimeouts(content);
Timeout t = new Timeout(content);
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);
Timeout t = new Timeout(content);
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);
Timeout t = new Timeout(content);
// 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) { }
}
@@ -0,0 +1,136 @@
/*
* 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.view.KeyEvent;
import android.view.View;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
/**
* For numeric text entry
*/
public abstract class NumberKeyListener extends BaseKeyListener
implements InputFilter
{
/**
* You can say which characters you can accept.
*/
protected abstract char[] getAcceptedChars();
protected int lookup(KeyEvent event, Spannable content) {
return event.getMatch(getAcceptedChars(), getMetaState(content));
}
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
char[] accept = getAcceptedChars();
boolean filter = false;
int i;
for (i = start; i < end; i++) {
if (!ok(accept, source.charAt(i))) {
break;
}
}
if (i == end) {
// It was all OK.
return null;
}
if (end - start == 1) {
// It was not OK, and there is only one char, so nothing remains.
return "";
}
SpannableStringBuilder filtered =
new SpannableStringBuilder(source, start, end);
i -= start;
end -= start;
int len = end - start;
// Only count down to i because the chars before that were all OK.
for (int j = end - 1; j >= i; j--) {
if (!ok(accept, source.charAt(j))) {
filtered.delete(j, j + 1);
}
}
return filtered;
}
protected static boolean ok(char[] accept, char c) {
for (int i = accept.length - 1; i >= 0; i--) {
if (accept[i] == c) {
return true;
}
}
return false;
}
@Override
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
int selStart, selEnd;
{
int a = Selection.getSelectionStart(content);
int b = Selection.getSelectionEnd(content);
selStart = Math.min(a, b);
selEnd = Math.max(a, b);
}
if (selStart < 0 || selEnd < 0) {
selStart = selEnd = 0;
Selection.setSelection(content, 0);
}
int i = event != null ? lookup(event, content) : 0;
int repeatCount = event != null ? event.getRepeatCount() : 0;
if (repeatCount == 0) {
if (i != 0) {
if (selStart != selEnd) {
Selection.setSelection(content, selEnd);
}
content.replace(selStart, selEnd, String.valueOf((char) i));
adjustMetaAfterKeypress(content);
return true;
}
} else if (i == '0' && repeatCount == 1) {
// Pretty hackish, it replaces the 0 with the +
if (selStart == selEnd && selEnd > 0 &&
content.charAt(selStart - 1) == '0') {
content.replace(selStart - 1, selEnd, String.valueOf('+'));
adjustMetaAfterKeypress(content);
return true;
}
}
adjustMetaAfterKeypress(content);
return super.onKeyDown(view, content, keyCode, event);
}
}
@@ -0,0 +1,267 @@
/*
* 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.graphics.Rect;
import android.view.View;
import android.text.Editable;
import android.text.GetChars;
import android.text.NoCopySpan;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.Selection;
import android.text.Spanned;
import android.text.Spannable;
import android.text.style.UpdateLayout;
import java.lang.ref.WeakReference;
public class PasswordTransformationMethod
implements TransformationMethod, TextWatcher
{
public CharSequence getTransformation(CharSequence source, View view) {
if (source instanceof Spannable) {
Spannable sp = (Spannable) source;
/*
* Remove any references to other views that may still be
* attached. This will happen when you flip the screen
* while a password field is showing; there will still
* be references to the old EditText in the text.
*/
ViewReference[] vr = sp.getSpans(0, sp.length(),
ViewReference.class);
for (int i = 0; i < vr.length; i++) {
sp.removeSpan(vr[i]);
}
removeVisibleSpans(sp);
sp.setSpan(new ViewReference(view), 0, 0,
Spannable.SPAN_POINT_POINT);
}
return new PasswordCharSequence(source);
}
public static PasswordTransformationMethod getInstance() {
if (sInstance != null)
return sInstance;
sInstance = new PasswordTransformationMethod();
return sInstance;
}
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
// This callback isn't used.
}
public void onTextChanged(CharSequence s, int start,
int before, int count) {
if (s instanceof Spannable) {
Spannable sp = (Spannable) s;
ViewReference[] vr = sp.getSpans(0, s.length(),
ViewReference.class);
if (vr.length == 0) {
return;
}
/*
* There should generally only be one ViewReference in the text,
* but make sure to look through all of them if necessary in case
* something strange is going on. (We might still end up with
* multiple ViewReferences if someone moves text from one password
* field to another.)
*/
View v = null;
for (int i = 0; v == null && i < vr.length; i++) {
v = vr[i].get();
}
if (v == null) {
return;
}
int pref = TextKeyListener.getInstance().getPrefs(v.getContext());
if ((pref & TextKeyListener.SHOW_PASSWORD) != 0) {
if (count > 0) {
removeVisibleSpans(sp);
if (count == 1) {
sp.setSpan(new Visible(sp, this), start, start + count,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
}
public void afterTextChanged(Editable s) {
// This callback isn't used.
}
public void onFocusChanged(View view, CharSequence sourceText,
boolean focused, int direction,
Rect previouslyFocusedRect) {
if (!focused) {
if (sourceText instanceof Spannable) {
Spannable sp = (Spannable) sourceText;
removeVisibleSpans(sp);
}
}
}
private static void removeVisibleSpans(Spannable sp) {
Visible[] old = sp.getSpans(0, sp.length(), Visible.class);
for (int i = 0; i < old.length; i++) {
sp.removeSpan(old[i]);
}
}
private static class PasswordCharSequence
implements CharSequence, GetChars
{
public PasswordCharSequence(CharSequence source) {
mSource = source;
}
public int length() {
return mSource.length();
}
public char charAt(int i) {
if (mSource instanceof Spanned) {
Spanned sp = (Spanned) mSource;
int st = sp.getSpanStart(TextKeyListener.ACTIVE);
int en = sp.getSpanEnd(TextKeyListener.ACTIVE);
if (i >= st && i < en) {
return mSource.charAt(i);
}
Visible[] visible = sp.getSpans(0, sp.length(), Visible.class);
for (int a = 0; a < visible.length; a++) {
if (sp.getSpanStart(visible[a].mTransformer) >= 0) {
st = sp.getSpanStart(visible[a]);
en = sp.getSpanEnd(visible[a]);
if (i >= st && i < en) {
return mSource.charAt(i);
}
}
}
}
return DOT;
}
public CharSequence subSequence(int start, int end) {
char[] buf = new char[end - start];
getChars(start, end, buf, 0);
return new String(buf);
}
public String toString() {
return subSequence(0, length()).toString();
}
public void getChars(int start, int end, char[] dest, int off) {
TextUtils.getChars(mSource, start, end, dest, off);
int st = -1, en = -1;
int nvisible = 0;
int[] starts = null, ends = null;
if (mSource instanceof Spanned) {
Spanned sp = (Spanned) mSource;
st = sp.getSpanStart(TextKeyListener.ACTIVE);
en = sp.getSpanEnd(TextKeyListener.ACTIVE);
Visible[] visible = sp.getSpans(0, sp.length(), Visible.class);
nvisible = visible.length;
starts = new int[nvisible];
ends = new int[nvisible];
for (int i = 0; i < nvisible; i++) {
if (sp.getSpanStart(visible[i].mTransformer) >= 0) {
starts[i] = sp.getSpanStart(visible[i]);
ends[i] = sp.getSpanEnd(visible[i]);
}
}
}
for (int i = start; i < end; i++) {
if (! (i >= st && i < en)) {
boolean visible = false;
for (int a = 0; a < nvisible; a++) {
if (i >= starts[a] && i < ends[a]) {
visible = true;
break;
}
}
if (!visible) {
dest[i - start + off] = DOT;
}
}
}
}
private CharSequence mSource;
}
private static class Visible
extends Handler
implements UpdateLayout, Runnable
{
public Visible(Spannable sp, PasswordTransformationMethod ptm) {
mText = sp;
mTransformer = ptm;
postAtTime(this, SystemClock.uptimeMillis() + 1500);
}
public void run() {
mText.removeSpan(this);
}
private Spannable mText;
private PasswordTransformationMethod mTransformer;
}
/**
* Used to stash a reference back to the View in the Editable so we
* can use it to check the settings.
*/
private static class ViewReference extends WeakReference<View>
implements NoCopySpan {
public ViewReference(View v) {
super(v);
}
}
private static PasswordTransformationMethod sInstance;
private static char DOT = '\u2022';
}
@@ -0,0 +1,497 @@
/*
* 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.text.*;
import android.text.method.TextKeyListener.Capitalize;
import android.util.SparseArray;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
/**
* This is the standard key listener for alphabetic input on qwerty
* keyboards. You should generally not need to instantiate this yourself;
* TextKeyListener will do it for you.
*/
public class QwertyKeyListener extends BaseKeyListener {
private static QwertyKeyListener[] sInstance =
new QwertyKeyListener[Capitalize.values().length * 2];
public QwertyKeyListener(Capitalize cap, boolean autotext) {
mAutoCap = cap;
mAutoText = autotext;
}
/**
* Returns a new or existing instance with the specified capitalization
* and correction properties.
*/
public static QwertyKeyListener getInstance(boolean autotext,
Capitalize cap) {
int off = cap.ordinal() * 2 + (autotext ? 1 : 0);
if (sInstance[off] == null) {
sInstance[off] = new QwertyKeyListener(cap, autotext);
}
return sInstance[off];
}
public int getInputType() {
return makeTextContentType(mAutoCap, 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);
if (selStart < 0 || selEnd < 0) {
selStart = selEnd = 0;
Selection.setSelection(content, 0, 0);
}
}
int activeStart = content.getSpanStart(TextKeyListener.ACTIVE);
int activeEnd = content.getSpanEnd(TextKeyListener.ACTIVE);
// QWERTY keyboard normal case
int i = event.getUnicodeChar(getMetaState(content));
int count = event.getRepeatCount();
if (count > 0 && selStart == selEnd && selStart > 0) {
char c = content.charAt(selStart - 1);
if (c == i || c == Character.toUpperCase(i) && view != null) {
if (showCharacterPicker(view, content, c, false, count)) {
resetMetaState(content);
return true;
}
}
}
if (i == KeyCharacterMap.PICKER_DIALOG_INPUT) {
if (view != null) {
showCharacterPicker(view, content,
KeyCharacterMap.PICKER_DIALOG_INPUT, true, 1);
}
resetMetaState(content);
return true;
}
if (i == KeyCharacterMap.HEX_INPUT) {
int start;
if (selStart == selEnd) {
start = selEnd;
while (start > 0 && selEnd - start < 4 &&
Character.digit(content.charAt(start - 1), 16) >= 0) {
start--;
}
} else {
start = selStart;
}
int ch = -1;
try {
String hex = TextUtils.substring(content, start, selEnd);
ch = Integer.parseInt(hex, 16);
} catch (NumberFormatException nfe) { }
if (ch >= 0) {
selStart = start;
Selection.setSelection(content, selStart, selEnd);
i = ch;
} else {
i = 0;
}
}
if (i != 0) {
boolean dead = false;
if ((i & KeyCharacterMap.COMBINING_ACCENT) != 0) {
dead = true;
i = i & KeyCharacterMap.COMBINING_ACCENT_MASK;
}
if (activeStart == selStart && activeEnd == selEnd) {
boolean replace = false;
if (selEnd - selStart - 1 == 0) {
char accent = content.charAt(selStart);
int composed = event.getDeadChar(accent, i);
if (composed != 0) {
i = composed;
replace = true;
}
}
if (!replace) {
Selection.setSelection(content, selEnd);
content.removeSpan(TextKeyListener.ACTIVE);
selStart = selEnd;
}
}
if ((pref & TextKeyListener.AUTO_CAP) != 0 &&
Character.isLowerCase(i) &&
TextKeyListener.shouldCap(mAutoCap, content, selStart)) {
int where = content.getSpanEnd(TextKeyListener.CAPPED);
int flags = content.getSpanFlags(TextKeyListener.CAPPED);
if (where == selStart && (((flags >> 16) & 0xFFFF) == i)) {
content.removeSpan(TextKeyListener.CAPPED);
} else {
flags = i << 16;
i = Character.toUpperCase(i);
if (selStart == 0)
content.setSpan(TextKeyListener.CAPPED, 0, 0,
Spannable.SPAN_MARK_MARK | flags);
else
content.setSpan(TextKeyListener.CAPPED,
selStart - 1, selStart,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
flags);
}
}
if (selStart != selEnd) {
Selection.setSelection(content, selEnd);
}
content.setSpan(OLD_SEL_START, selStart, selStart,
Spannable.SPAN_MARK_MARK);
content.replace(selStart, selEnd, String.valueOf((char) i));
int oldStart = content.getSpanStart(OLD_SEL_START);
selEnd = Selection.getSelectionEnd(content);
if (oldStart < selEnd) {
content.setSpan(TextKeyListener.LAST_TYPED,
oldStart, selEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
if (dead) {
Selection.setSelection(content, oldStart, selEnd);
content.setSpan(TextKeyListener.ACTIVE, oldStart, selEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
adjustMetaAfterKeypress(content);
// potentially do autotext replacement if the character
// that was typed was an autotext terminator
if ((pref & TextKeyListener.AUTO_TEXT) != 0 && mAutoText &&
(i == ' ' || i == '\t' || i == '\n' ||
i == ',' || i == '.' || i == '!' || i == '?' ||
i == '"' || Character.getType(i) == Character.END_PUNCTUATION) &&
content.getSpanEnd(TextKeyListener.INHIBIT_REPLACEMENT)
!= oldStart) {
int x;
for (x = oldStart; x > 0; x--) {
char c = content.charAt(x - 1);
if (c != '\'' && !Character.isLetter(c)) {
break;
}
}
String rep = getReplacement(content, x, oldStart, view);
if (rep != null) {
Replaced[] repl = content.getSpans(0, content.length(),
Replaced.class);
for (int a = 0; a < repl.length; a++)
content.removeSpan(repl[a]);
char[] orig = new char[oldStart - x];
TextUtils.getChars(content, x, oldStart, orig, 0);
content.setSpan(new Replaced(orig), x, oldStart,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
content.replace(x, oldStart, rep);
}
}
// Replace two spaces by a period and a space.
if ((pref & TextKeyListener.AUTO_PERIOD) != 0 && mAutoText) {
selEnd = Selection.getSelectionEnd(content);
if (selEnd - 3 >= 0) {
if (content.charAt(selEnd - 1) == ' ' &&
content.charAt(selEnd - 2) == ' ') {
char c = content.charAt(selEnd - 3);
for (int j = selEnd - 3; j > 0; j--) {
if (c == '"' ||
Character.getType(c) == Character.END_PUNCTUATION) {
c = content.charAt(j - 1);
} else {
break;
}
}
if (Character.isLetter(c) || Character.isDigit(c)) {
content.replace(selEnd - 2, selEnd - 1, ".");
}
}
}
}
return true;
} else if (keyCode == KeyEvent.KEYCODE_DEL && selStart == selEnd) {
// special backspace case for undoing autotext
int consider = 1;
// if backspacing over the last typed character,
// it undoes the autotext prior to that character
// (unless the character typed was newline, in which
// case this behavior would be confusing)
if (content.getSpanEnd(TextKeyListener.LAST_TYPED) == selStart) {
if (content.charAt(selStart - 1) != '\n')
consider = 2;
}
Replaced[] repl = content.getSpans(selStart - consider, selStart,
Replaced.class);
if (repl.length > 0) {
int st = content.getSpanStart(repl[0]);
int en = content.getSpanEnd(repl[0]);
String old = new String(repl[0].mText);
content.removeSpan(repl[0]);
// only cancel the autocomplete if the cursor is at the end of
// the replaced span (or after it, because the user is
// backspacing over the space after the word, not the word
// itself).
if (selStart >= en) {
content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
en, en, Spannable.SPAN_POINT_POINT);
content.replace(st, en, old);
en = content.getSpanStart(TextKeyListener.INHIBIT_REPLACEMENT);
if (en - 1 >= 0) {
content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
en - 1, en,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
content.removeSpan(TextKeyListener.INHIBIT_REPLACEMENT);
}
adjustMetaAfterKeypress(content);
} else {
adjustMetaAfterKeypress(content);
return super.onKeyDown(view, content, keyCode, event);
}
return true;
}
}
return super.onKeyDown(view, content, keyCode, event);
}
private String getReplacement(CharSequence src, int start, int end,
View view) {
int len = end - start;
boolean changecase = false;
String replacement = AutoText.get(src, start, end, view);
if (replacement == null) {
String key = TextUtils.substring(src, start, end).toLowerCase();
replacement = AutoText.get(key, 0, end - start, view);
changecase = true;
if (replacement == null)
return null;
}
int caps = 0;
if (changecase) {
for (int j = start; j < end; j++) {
if (Character.isUpperCase(src.charAt(j)))
caps++;
}
}
String out;
if (caps == 0)
out = replacement;
else if (caps == 1)
out = toTitleCase(replacement);
else if (caps == len)
out = replacement.toUpperCase();
else
out = toTitleCase(replacement);
if (out.length() == len &&
TextUtils.regionMatches(src, start, out, 0, len))
return null;
return out;
}
/**
* Marks the specified region of <code>content</code> as having
* contained <code>original</code> prior to AutoText replacement.
* Call this method when you have done or are about to do an
* AutoText-style replacement on a region of text and want to let
* the same mechanism (the user pressing DEL immediately after the
* change) undo the replacement.
*
* @param content the Editable text where the replacement was made
* @param start the start of the replaced region
* @param end the end of the replaced region; the location of the cursor
* @param original the text to be restored if the user presses DEL
*/
public static void markAsReplaced(Spannable content, int start, int end,
String original) {
Replaced[] repl = content.getSpans(0, content.length(), Replaced.class);
for (int a = 0; a < repl.length; a++) {
content.removeSpan(repl[a]);
}
int len = original.length();
char[] orig = new char[len];
original.getChars(0, len, orig, 0);
content.setSpan(new Replaced(orig), start, end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
private static SparseArray<String> PICKER_SETS =
new SparseArray<String>();
static {
PICKER_SETS.put('A', "\u00C0\u00C1\u00C2\u00C4\u00C6\u00C3\u00C5\u0104\u0100");
PICKER_SETS.put('C', "\u00C7\u0106\u010C");
PICKER_SETS.put('D', "\u010E");
PICKER_SETS.put('E', "\u00C8\u00C9\u00CA\u00CB\u0118\u011A\u0112");
PICKER_SETS.put('G', "\u011E");
PICKER_SETS.put('L', "\u0141");
PICKER_SETS.put('I', "\u00CC\u00CD\u00CE\u00CF\u012A\u0130");
PICKER_SETS.put('N', "\u00D1\u0143\u0147");
PICKER_SETS.put('O', "\u00D8\u0152\u00D5\u00D2\u00D3\u00D4\u00D6\u014C");
PICKER_SETS.put('R', "\u0158");
PICKER_SETS.put('S', "\u015A\u0160\u015E");
PICKER_SETS.put('T', "\u0164");
PICKER_SETS.put('U', "\u00D9\u00DA\u00DB\u00DC\u016E\u016A");
PICKER_SETS.put('Y', "\u00DD\u0178");
PICKER_SETS.put('Z', "\u0179\u017B\u017D");
PICKER_SETS.put('a', "\u00E0\u00E1\u00E2\u00E4\u00E6\u00E3\u00E5\u0105\u0101");
PICKER_SETS.put('c', "\u00E7\u0107\u010D");
PICKER_SETS.put('d', "\u010F");
PICKER_SETS.put('e', "\u00E8\u00E9\u00EA\u00EB\u0119\u011B\u0113");
PICKER_SETS.put('g', "\u011F");
PICKER_SETS.put('i', "\u00EC\u00ED\u00EE\u00EF\u012B\u0131");
PICKER_SETS.put('l', "\u0142");
PICKER_SETS.put('n', "\u00F1\u0144\u0148");
PICKER_SETS.put('o', "\u00F8\u0153\u00F5\u00F2\u00F3\u00F4\u00F6\u014D");
PICKER_SETS.put('r', "\u0159");
PICKER_SETS.put('s', "\u00A7\u00DF\u015B\u0161\u015F");
PICKER_SETS.put('t', "\u0165");
PICKER_SETS.put('u', "\u00F9\u00FA\u00FB\u00FC\u016F\u016B");
PICKER_SETS.put('y', "\u00FD\u00FF");
PICKER_SETS.put('z', "\u017A\u017C\u017E");
PICKER_SETS.put(KeyCharacterMap.PICKER_DIALOG_INPUT,
"\u2026\u00A5\u2022\u00AE\u00A9\u00B1[]{}\\|");
PICKER_SETS.put('/', "\\");
// From packages/inputmethods/LatinIME/res/xml/kbd_symbols.xml
PICKER_SETS.put('1', "\u00b9\u00bd\u2153\u00bc\u215b");
PICKER_SETS.put('2', "\u00b2\u2154");
PICKER_SETS.put('3', "\u00b3\u00be\u215c");
PICKER_SETS.put('4', "\u2074");
PICKER_SETS.put('5', "\u215d");
PICKER_SETS.put('7', "\u215e");
PICKER_SETS.put('0', "\u207f\u2205");
PICKER_SETS.put('$', "\u00a2\u00a3\u20ac\u00a5\u20a3\u20a4\u20b1");
PICKER_SETS.put('%', "\u2030");
PICKER_SETS.put('*', "\u2020\u2021");
PICKER_SETS.put('-', "\u2013\u2014");
PICKER_SETS.put('+', "\u00b1");
PICKER_SETS.put('(', "[{<");
PICKER_SETS.put(')', "]}>");
PICKER_SETS.put('!', "\u00a1");
PICKER_SETS.put('"', "\u201c\u201d\u00ab\u00bb\u02dd");
PICKER_SETS.put('?', "\u00bf");
PICKER_SETS.put(',', "\u201a\u201e");
// From packages/inputmethods/LatinIME/res/xml/kbd_symbols_shift.xml
PICKER_SETS.put('=', "\u2260\u2248\u221e");
PICKER_SETS.put('<', "\u2264\u00ab\u2039");
PICKER_SETS.put('>', "\u2265\u00bb\u203a");
};
private boolean showCharacterPicker(View view, Editable content, char c,
boolean insert, int count) {
String set = PICKER_SETS.get(c);
if (set == null) {
return false;
}
if (count == 1) {
new CharacterPickerDialog(view.getContext(),
view, content, set, insert).show();
}
return true;
}
private static String toTitleCase(String src) {
return Character.toUpperCase(src.charAt(0)) + src.substring(1);
}
/* package */ static class Replaced implements NoCopySpan
{
public Replaced(char[] text) {
mText = text;
}
private char[] mText;
}
private Capitalize mAutoCap;
private boolean mAutoText;
}
@@ -0,0 +1,205 @@
/*
* 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.graphics.Rect;
import android.text.Editable;
import android.text.GetChars;
import android.text.Spannable;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.TextUtils;
import android.view.View;
/**
* This transformation method causes the characters in the {@link #getOriginal}
* array to be replaced by the corresponding characters in the
* {@link #getReplacement} array.
*/
public abstract class ReplacementTransformationMethod
implements TransformationMethod
{
/**
* Returns the list of characters that are to be replaced by other
* characters when displayed.
*/
protected abstract char[] getOriginal();
/**
* Returns a parallel array of replacement characters for the ones
* that are to be replaced.
*/
protected abstract char[] getReplacement();
/**
* Returns a CharSequence that will mirror the contents of the
* source CharSequence but with the characters in {@link #getOriginal}
* replaced by ones from {@link #getReplacement}.
*/
public CharSequence getTransformation(CharSequence source, View v) {
char[] original = getOriginal();
char[] replacement = getReplacement();
/*
* Short circuit for faster display if the text will never change.
*/
if (!(source instanceof Editable)) {
/*
* Check whether the text does not contain any of the
* source characters so can be used unchanged.
*/
boolean doNothing = true;
int n = original.length;
for (int i = 0; i < n; i++) {
if (TextUtils.indexOf(source, original[i]) >= 0) {
doNothing = false;
break;
}
}
if (doNothing) {
return source;
}
if (!(source instanceof Spannable)) {
/*
* The text contains some of the source characters,
* but they can be flattened out now instead of
* at display time.
*/
if (source instanceof Spanned) {
return new SpannedString(new SpannedReplacementCharSequence(
(Spanned) source,
original, replacement));
} else {
return new ReplacementCharSequence(source,
original,
replacement).toString();
}
}
}
if (source instanceof Spanned) {
return new SpannedReplacementCharSequence((Spanned) source,
original, replacement);
} else {
return new ReplacementCharSequence(source, original, replacement);
}
}
public void onFocusChanged(View view, CharSequence sourceText,
boolean focused, int direction,
Rect previouslyFocusedRect) {
// This callback isn't used.
}
private static class ReplacementCharSequence
implements CharSequence, GetChars {
private char[] mOriginal, mReplacement;
public ReplacementCharSequence(CharSequence source, char[] original,
char[] replacement) {
mSource = source;
mOriginal = original;
mReplacement = replacement;
}
public int length() {
return mSource.length();
}
public char charAt(int i) {
char c = mSource.charAt(i);
int n = mOriginal.length;
for (int j = 0; j < n; j++) {
if (c == mOriginal[j]) {
c = mReplacement[j];
}
}
return c;
}
public CharSequence subSequence(int start, int end) {
char[] c = new char[end - start];
getChars(start, end, c, 0);
return new String(c);
}
public String toString() {
char[] c = new char[length()];
getChars(0, length(), c, 0);
return new String(c);
}
public void getChars(int start, int end, char[] dest, int off) {
TextUtils.getChars(mSource, start, end, dest, off);
int offend = end - start + off;
int n = mOriginal.length;
for (int i = off; i < offend; i++) {
char c = dest[i];
for (int j = 0; j < n; j++) {
if (c == mOriginal[j]) {
dest[i] = mReplacement[j];
}
}
}
}
private CharSequence mSource;
}
private static class SpannedReplacementCharSequence
extends ReplacementCharSequence
implements Spanned
{
public SpannedReplacementCharSequence(Spanned source, char[] original,
char[] replacement) {
super(source, original, replacement);
mSpanned = source;
}
public CharSequence subSequence(int start, int end) {
return new SpannedString(this).subSequence(start, end);
}
public <T> T[] getSpans(int start, int end, Class<T> type) {
return mSpanned.getSpans(start, end, type);
}
public int getSpanStart(Object tag) {
return mSpanned.getSpanStart(tag);
}
public int getSpanEnd(Object tag) {
return mSpanned.getSpanEnd(tag);
}
public int getSpanFlags(Object tag) {
return mSpanned.getSpanFlags(tag);
}
public int nextSpanTransition(int start, int end, Class type) {
return mSpanned.nextSpanTransition(start, end, type);
}
private Spanned mSpanned;
}
}
@@ -0,0 +1,240 @@
/*
* 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.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.text.*;
import android.widget.TextView;
import android.view.View;
public class
ScrollingMovementMethod
implements MovementMethod
{
/**
* Scrolls the text to the left if possible.
*/
protected boolean left(TextView widget, Spannable buffer) {
Layout layout = widget.getLayout();
int scrolly = widget.getScrollY();
int scr = widget.getScrollX();
int em = Math.round(layout.getPaint().getFontSpacing());
int padding = widget.getTotalPaddingTop() +
widget.getTotalPaddingBottom();
int top = layout.getLineForVertical(scrolly);
int bottom = layout.getLineForVertical(scrolly + widget.getHeight() -
padding);
int left = Integer.MAX_VALUE;
for (int i = top; i <= bottom; i++) {
left = (int) Math.min(left, layout.getLineLeft(i));
}
if (scr > left) {
int s = Math.max(scr - em, left);
widget.scrollTo(s, widget.getScrollY());
return true;
}
return false;
}
/**
* Scrolls the text to the right if possible.
*/
protected boolean right(TextView widget, Spannable buffer) {
Layout layout = widget.getLayout();
int scrolly = widget.getScrollY();
int scr = widget.getScrollX();
int em = Math.round(layout.getPaint().getFontSpacing());
int padding = widget.getTotalPaddingTop() +
widget.getTotalPaddingBottom();
int top = layout.getLineForVertical(scrolly);
int bottom = layout.getLineForVertical(scrolly + widget.getHeight() -
padding);
int right = 0;
for (int i = top; i <= bottom; i++) {
right = (int) Math.max(right, layout.getLineRight(i));
}
padding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight();
if (scr < right - (widget.getWidth() - padding)) {
int s = Math.min(scr + em, right - (widget.getWidth() - padding));
widget.scrollTo(s, widget.getScrollY());
return true;
}
return false;
}
/**
* Scrolls the text up if possible.
*/
protected boolean up(TextView widget, Spannable buffer) {
Layout layout = widget.getLayout();
int areatop = widget.getScrollY();
int line = layout.getLineForVertical(areatop);
int linetop = layout.getLineTop(line);
// If the top line is partially visible, bring it all the way
// into view; otherwise, bring the previous line into view.
if (areatop == linetop)
line--;
if (line >= 0) {
Touch.scrollTo(widget, layout,
widget.getScrollX(), layout.getLineTop(line));
return true;
}
return false;
}
/**
* Scrolls the text down if possible.
*/
protected boolean down(TextView widget, Spannable buffer) {
Layout layout = widget.getLayout();
int padding = widget.getTotalPaddingTop() +
widget.getTotalPaddingBottom();
int areabot = widget.getScrollY() + widget.getHeight() - padding;
int line = layout.getLineForVertical(areabot);
if (layout.getLineTop(line+1) < areabot + 1) {
// Less than a pixel of this line is out of view,
// so we must have tried to make it entirely in view
// and now want the next line to be in view instead.
line++;
}
if (line <= layout.getLineCount() - 1) {
widget.scrollTo(widget.getScrollX(), layout.getLineTop(line+1) -
(widget.getHeight() - padding));
Touch.scrollTo(widget, layout,
widget.getScrollX(), widget.getScrollY());
return true;
}
return false;
}
public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
return executeDown(widget, buffer, keyCode);
}
private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
boolean handled = false;
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT:
handled |= left(widget, buffer);
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
handled |= right(widget, buffer);
break;
case KeyEvent.KEYCODE_DPAD_UP:
handled |= up(widget, buffer);
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
handled |= down(widget, buffer);
break;
}
return handled;
}
public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
return false;
}
public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
int code = event.getKeyCode();
if (code != KeyEvent.KEYCODE_UNKNOWN
&& event.getAction() == KeyEvent.ACTION_MULTIPLE) {
int repeat = event.getRepeatCount();
boolean first = true;
boolean handled = false;
while ((--repeat) > 0) {
if (first && executeDown(view, text, code)) {
handled = true;
MetaKeyKeyListener.adjustMetaAfterKeypress(text);
MetaKeyKeyListener.resetLockedMeta(text);
}
first = false;
}
return handled;
}
return false;
}
public boolean onTrackballEvent(TextView widget, Spannable text,
MotionEvent event) {
return false;
}
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
return Touch.onTouchEvent(widget, buffer, event);
}
public void initialize(TextView widget, Spannable text) { }
public boolean canSelectArbitrarily() {
return false;
}
public void onTakeFocus(TextView widget, Spannable text, int dir) {
Layout layout = widget.getLayout();
if (layout != null && (dir & View.FOCUS_FORWARD) != 0) {
widget.scrollTo(widget.getScrollX(),
layout.getLineTop(0));
}
if (layout != null && (dir & View.FOCUS_BACKWARD) != 0) {
int padding = widget.getTotalPaddingTop() +
widget.getTotalPaddingBottom();
int line = layout.getLineCount() - 1;
widget.scrollTo(widget.getScrollX(),
layout.getLineTop(line+1) -
(widget.getHeight() - padding));
}
}
public static MovementMethod getInstance() {
if (sInstance == null)
sInstance = new ScrollingMovementMethod();
return sInstance;
}
private static ScrollingMovementMethod sInstance;
}
@@ -0,0 +1,62 @@
/*
* 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.graphics.Rect;
import android.text.Editable;
import android.text.GetChars;
import android.text.Spannable;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.TextUtils;
import android.view.View;
/**
* This transformation method causes any newline characters (\n) to be
* displayed as spaces instead of causing line breaks, and causes
* carriage return characters (\r) to have no appearance.
*/
public class SingleLineTransformationMethod
extends ReplacementTransformationMethod {
private static char[] ORIGINAL = new char[] { '\n', '\r' };
private static char[] REPLACEMENT = new char[] { ' ', '\uFEFF' };
/**
* The characters to be replaced are \n and \r.
*/
protected char[] getOriginal() {
return ORIGINAL;
}
/**
* The character \n is replaced with is space;
* the character \r is replaced with is FEFF (zero width space).
*/
protected char[] getReplacement() {
return REPLACEMENT;
}
public static SingleLineTransformationMethod getInstance() {
if (sInstance != null)
return sInstance;
sInstance = new SingleLineTransformationMethod();
return sInstance;
}
private static SingleLineTransformationMethod sInstance;
}
@@ -0,0 +1,299 @@
/*
* 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.
*/
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 = KeyCharacterMap.load(event.getKeyboardDevice());
int kind = kmap.getKeyboardType();
if (kind == KeyCharacterMap.ALPHA) {
return QwertyKeyListener.getInstance(mAutoText, mAutoCap);
} else if (kind == KeyCharacterMap.NUMERIC) {
return MultiTapKeyListener.getInstance(mAutoText, mAutoCap);
}
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;
}
}
@@ -0,0 +1,58 @@
/*
* 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.view.KeyEvent;
import android.text.InputType;
/**
* For entering times in a text field.
*/
public class TimeKeyListener extends NumberKeyListener
{
public int getInputType() {
return InputType.TYPE_CLASS_DATETIME
| InputType.TYPE_DATETIME_VARIATION_TIME;
}
@Override
protected char[] getAcceptedChars()
{
return CHARACTERS;
}
public static TimeKeyListener getInstance() {
if (sInstance != null)
return sInstance;
sInstance = new TimeKeyListener();
return sInstance;
}
/**
* The characters that are used.
*
* @see KeyEvent#getMatch
* @see #getAcceptedChars
*/
public static final char[] CHARACTERS = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'm',
'p', ':'
};
private static TimeKeyListener sInstance;
}
@@ -0,0 +1,215 @@
/*
* Copyright (C) 2008 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.text.Layout;
import android.text.Layout.Alignment;
import android.text.NoCopySpan;
import android.text.Spannable;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.TextView;
public class Touch {
private Touch() { }
/**
* Scrolls the specified widget to the specified coordinates, except
* constrains the X scrolling position to the horizontal regions of
* the text that will be visible after scrolling to the specified
* Y position.
*/
public static void scrollTo(TextView widget, Layout layout, int x, int y) {
int padding = widget.getTotalPaddingTop() +
widget.getTotalPaddingBottom();
int top = layout.getLineForVertical(y);
int bottom = layout.getLineForVertical(y + widget.getHeight() -
padding);
int left = Integer.MAX_VALUE;
int right = 0;
Alignment a = null;
for (int i = top; i <= bottom; i++) {
left = (int) Math.min(left, layout.getLineLeft(i));
right = (int) Math.max(right, layout.getLineRight(i));
if (a == null) {
a = layout.getParagraphAlignment(i);
}
}
padding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight();
int width = widget.getWidth();
int diff = 0;
if (right - left < width - padding) {
if (a == Alignment.ALIGN_CENTER) {
diff = (width - padding - (right - left)) / 2;
} else if (a == Alignment.ALIGN_OPPOSITE) {
diff = width - padding - (right - left);
}
}
x = Math.min(x, right - (width - padding) - diff);
x = Math.max(x, left - diff);
widget.scrollTo(x, y);
}
/**
* @hide
* Returns the maximum scroll value in x.
*/
public static int getMaxScrollX(TextView widget, Layout layout, int y) {
int top = layout.getLineForVertical(y);
int bottom = layout.getLineForVertical(y + widget.getHeight()
- widget.getTotalPaddingTop() -widget.getTotalPaddingBottom());
int left = Integer.MAX_VALUE;
int right = 0;
for (int i = top; i <= bottom; i++) {
left = (int) Math.min(left, layout.getLineLeft(i));
right = (int) Math.max(right, layout.getLineRight(i));
}
return right - left - widget.getWidth() - widget.getTotalPaddingLeft()
- widget.getTotalPaddingRight();
}
/**
* Handles touch events for dragging. You may want to do other actions
* like moving the cursor on touch as well.
*/
public static boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
DragState[] ds;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
ds = buffer.getSpans(0, buffer.length(), DragState.class);
for (int i = 0; i < ds.length; i++) {
buffer.removeSpan(ds[i]);
}
buffer.setSpan(new DragState(event.getX(), event.getY(),
widget.getScrollX(), widget.getScrollY()),
0, 0, Spannable.SPAN_MARK_MARK);
return true;
case MotionEvent.ACTION_UP:
ds = buffer.getSpans(0, buffer.length(), DragState.class);
for (int i = 0; i < ds.length; i++) {
buffer.removeSpan(ds[i]);
}
if (ds.length > 0 && ds[0].mUsed) {
return true;
} else {
return false;
}
case MotionEvent.ACTION_MOVE:
ds = buffer.getSpans(0, buffer.length(), DragState.class);
if (ds.length > 0) {
if (ds[0].mFarEnough == false) {
int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
if (Math.abs(event.getX() - ds[0].mX) >= slop ||
Math.abs(event.getY() - ds[0].mY) >= slop) {
ds[0].mFarEnough = true;
}
}
if (ds[0].mFarEnough) {
ds[0].mUsed = true;
boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
KeyEvent.META_SHIFT_ON) == 1) ||
(MetaKeyKeyListener.getMetaState(buffer,
MetaKeyKeyListener.META_SELECTING) != 0);
float dx;
float dy;
if (cap) {
// if we're selecting, we want the scroll to go in
// the direction of the drag
dx = event.getX() - ds[0].mX;
dy = event.getY() - ds[0].mY;
} else {
dx = ds[0].mX - event.getX();
dy = ds[0].mY - event.getY();
}
ds[0].mX = event.getX();
ds[0].mY = event.getY();
int nx = widget.getScrollX() + (int) dx;
int ny = widget.getScrollY() + (int) dy;
int padding = widget.getTotalPaddingTop() +
widget.getTotalPaddingBottom();
Layout layout = widget.getLayout();
ny = Math.min(ny, layout.getHeight() - (widget.getHeight() -
padding));
ny = Math.max(ny, 0);
int oldX = widget.getScrollX();
int oldY = widget.getScrollY();
scrollTo(widget, layout, nx, ny);
// If we actually scrolled, then cancel the up action.
if (oldX != widget.getScrollX()
|| oldY != widget.getScrollY()) {
widget.cancelLongPress();
}
return true;
}
}
}
return false;
}
public static int getInitialScrollX(TextView widget, Spannable buffer) {
DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class);
return ds.length > 0 ? ds[0].mScrollX : -1;
}
public static int getInitialScrollY(TextView widget, Spannable buffer) {
DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class);
return ds.length > 0 ? ds[0].mScrollY : -1;
}
private static class DragState implements NoCopySpan {
public float mX;
public float mY;
public int mScrollX;
public int mScrollY;
public boolean mFarEnough;
public boolean mUsed;
public DragState(float x, float y, int scrollX, int scrollY) {
mX = x;
mY = y;
mScrollX = scrollX;
mScrollY = scrollY;
}
}
}
@@ -0,0 +1,46 @@
/*
* 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.graphics.Rect;
import android.view.View;
import android.widget.TextView;
/**
* TextView uses TransformationMethods to do things like replacing the
* characters of passwords with dots, or keeping the newline characters
* from causing line breaks in single-line text fields.
*/
public interface TransformationMethod
{
/**
* Returns a CharSequence that is a transformation of the source text --
* for example, replacing each character with a dot in a password field.
* Beware that the returned text must be exactly the same length as
* the source text, and that if the source text is Editable, the returned
* text must mirror it dynamically instead of doing a one-time copy.
*/
public CharSequence getTransformation(CharSequence source, View view);
/**
* This method is called when the TextView that uses this
* TransformationMethod gains or loses focus.
*/
public void onFocusChanged(View view, CharSequence sourceText,
boolean focused, int direction,
Rect previouslyFocusedRect);
}
@@ -0,0 +1,21 @@
<html>
<body>
<p>Provides classes that monitor or modify keypad input.</p>
<p>You can use these classes to modify the type of keypad entry
for your application, or decipher the keypresses entered for your specific
entry method. For example:</p>
<pre>
// Set the text to password display style:
EditText txtView = (EditText)findViewById(R.id.text);
txtView.setTransformationMethod(PasswordTransformationMethod.getInstance());
//Set the input style to numbers, rather than qwerty keyboard style.
txtView.setInputMethod(DigitsInputMethod.getInstance());
// Find out whether the caps lock is on.
// 0 is no, 1 is yes, 2 is caps lock on.
int active = MultiTapInputMethod.getCapsActive(txtView.getText());
</pre>
</body>
</html>