summaryrefslogtreecommitdiffstats
path: root/core/java/android/text/method
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/text/method')
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java266
-rw-r--r--core/java/android/text/method/BaseKeyListener.java112
-rw-r--r--core/java/android/text/method/CharacterPickerDialog.java134
-rw-r--r--core/java/android/text/method/DateKeyListener.java52
-rw-r--r--core/java/android/text/method/DateTimeKeyListener.java52
-rw-r--r--core/java/android/text/method/DialerKeyListener.java109
-rw-r--r--core/java/android/text/method/DigitsKeyListener.java206
-rw-r--r--core/java/android/text/method/HideReturnsTransformationMethod.java59
-rw-r--r--core/java/android/text/method/KeyListener.java42
-rw-r--r--core/java/android/text/method/LinkMovementMethod.java256
-rw-r--r--core/java/android/text/method/MetaKeyKeyListener.java250
-rw-r--r--core/java/android/text/method/MovementMethod.java43
-rw-r--r--core/java/android/text/method/MultiTapKeyListener.java285
-rw-r--r--core/java/android/text/method/NumberKeyListener.java132
-rw-r--r--core/java/android/text/method/PasswordTransformationMethod.java260
-rw-r--r--core/java/android/text/method/QwertyKeyListener.java444
-rw-r--r--core/java/android/text/method/ReplacementTransformationMethod.java205
-rw-r--r--core/java/android/text/method/ScrollingMovementMethod.java234
-rw-r--r--core/java/android/text/method/SingleLineTransformationMethod.java60
-rw-r--r--core/java/android/text/method/TextKeyListener.java339
-rw-r--r--core/java/android/text/method/TimeKeyListener.java52
-rw-r--r--core/java/android/text/method/Touch.java138
-rw-r--r--core/java/android/text/method/TransformationMethod.java46
-rw-r--r--core/java/android/text/method/package.html21
24 files changed, 3797 insertions, 0 deletions
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
new file mode 100644
index 0000000..ac2e499
--- /dev/null
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -0,0 +1,266 @@
+/*
+ * 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.*;
+import android.widget.TextView;
+import android.view.View;
+import android.view.MotionEvent;
+
+// 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 up(TextView widget, Spannable buffer) {
+ boolean cap = MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1;
+ boolean alt = MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_ALT_ON) == 1;
+ 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 = MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1;
+ boolean alt = MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_ALT_ON) == 1;
+ 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 = MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1;
+ boolean alt = MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_ALT_ON) == 1;
+ 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 = MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1;
+ boolean alt = MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_ALT_ON) == 1;
+ 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) {
+ 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;
+ }
+
+ 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 onTrackballEvent(TextView widget, Spannable buffer,
+ MotionEvent event) {
+ boolean handled = false;
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ for (; y < 0; y++) {
+ handled |= up(widget, buffer);
+ }
+ for (; y > 0; y--) {
+ handled |= down(widget, buffer);
+ }
+
+ for (; x < 0; x++) {
+ handled |= left(widget, buffer);
+ }
+ for (; x > 0; x--) {
+ handled |= right(widget, buffer);
+ }
+
+ if (handled) {
+ MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
+ MetaKeyKeyListener.resetLockedMeta(buffer);
+ }
+
+ return handled;
+ }
+
+ public boolean onTouchEvent(TextView widget, Spannable buffer,
+ MotionEvent event) {
+ boolean handled = Touch.onTouchEvent(widget, buffer, event);
+
+ if (widget.isFocused()) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ 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);
+
+ boolean cap = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0;
+
+ if (cap) {
+ Selection.extendSelection(buffer, off);
+ } else {
+ Selection.setSelection(buffer, off);
+ }
+
+ 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) {
+ Layout layout = view.getLayout();
+
+ if (layout == null) {
+ /*
+ * This shouldn't be null, but do something sensible if it is.
+ */
+ Selection.setSelection(text, text.length());
+ } else {
+ /*
+ * Put the cursor at the end of the first line, which is
+ * either the last offset if there is only one line, or the
+ * offset before the first character of the second line
+ * if there is more than one line.
+ */
+ if (layout.getLineCount() == 1) {
+ Selection.setSelection(text, text.length());
+ } else {
+ Selection.setSelection(text, layout.getLineStart(1) - 1);
+ }
+ }
+ } else {
+ Selection.setSelection(text, text.length());
+ }
+ }
+
+ public static MovementMethod getInstance() {
+ if (sInstance == null)
+ sInstance = new ArrowKeyMovementMethod();
+
+ return sInstance;
+ }
+
+ private static ArrowKeyMovementMethod sInstance;
+}
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
new file mode 100644
index 0000000..3e92b7b
--- /dev/null
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -0,0 +1,112 @@
+/*
+ * 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.Message;
+import android.util.Log;
+import android.text.*;
+import android.widget.TextView;
+
+public abstract class BaseKeyListener
+extends MetaKeyKeyListener
+implements KeyListener {
+ /* package */ static final Object OLD_SEL_START = new Object();
+
+ /**
+ * 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;
+ }
+
+ 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);
+ }
+}
+
diff --git a/core/java/android/text/method/CharacterPickerDialog.java b/core/java/android/text/method/CharacterPickerDialog.java
new file mode 100644
index 0000000..d787132
--- /dev/null
+++ b/core/java/android/text/method/CharacterPickerDialog.java
@@ -0,0 +1,134 @@
+/*
+ * 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.LayoutParams;
+import android.view.ViewGroup;
+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;
+import android.widget.TextView;
+
+/**
+ * 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;
+
+ /**
+ * 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);
+
+ 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_PANEL;
+
+ setTitle(R.string.select_character);
+ setContentView(R.layout.character_picker);
+
+ GridView grid = (GridView) findViewById(R.id.characterPicker);
+ grid.setAdapter(new OptionsAdapter(getContext()));
+ grid.setOnItemClickListener(this);
+
+ findViewById(R.id.cancel).setOnClickListener(this);
+ }
+
+ /**
+ * Handles clicks on the character buttons.
+ */
+ public void onItemClick(AdapterView parent, View view, int position, long id) {
+ int selEnd = Selection.getSelectionEnd(mText);
+ String result = String.valueOf(mOptions.charAt(position));
+
+ if (mInsert || selEnd == 0) {
+ mText.insert(selEnd, result);
+ } else {
+ mText.replace(selEnd - 1, selEnd, result);
+ }
+
+ dismiss();
+ }
+
+ /**
+ * Handles clicks on the Cancel button.
+ */
+ public void onClick(View v) {
+ dismiss();
+ }
+
+ private class OptionsAdapter extends BaseAdapter {
+ private Context mContext;
+
+ public OptionsAdapter(Context context) {
+ super();
+ mContext = context;
+ }
+
+ 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)));
+ 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;
+ }
+ }
+}
diff --git a/core/java/android/text/method/DateKeyListener.java b/core/java/android/text/method/DateKeyListener.java
new file mode 100644
index 0000000..0ca0332
--- /dev/null
+++ b/core/java/android/text/method/DateKeyListener.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * For entering dates in a text field.
+ */
+public class DateKeyListener extends NumberKeyListener
+{
+ @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;
+}
diff --git a/core/java/android/text/method/DateTimeKeyListener.java b/core/java/android/text/method/DateTimeKeyListener.java
new file mode 100644
index 0000000..304d326
--- /dev/null
+++ b/core/java/android/text/method/DateTimeKeyListener.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * For entering dates and times in the same text field.
+ */
+public class DateTimeKeyListener extends NumberKeyListener
+{
+ @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;
+}
diff --git a/core/java/android/text/method/DialerKeyListener.java b/core/java/android/text/method/DialerKeyListener.java
new file mode 100644
index 0000000..e805ad7
--- /dev/null
+++ b/core/java/android/text/method/DialerKeyListener.java
@@ -0,0 +1,109 @@
+/*
+ * 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.util.SparseIntArray;
+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;
+ }
+
+ /**
+ * 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;
+}
diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java
new file mode 100644
index 0000000..99a3f1a
--- /dev/null
+++ b/core/java/android/text/method/DigitsKeyListener.java
@@ -0,0 +1,206 @@
+/*
+ * 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.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;
+ }
+
+ @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];
+}
diff --git a/core/java/android/text/method/HideReturnsTransformationMethod.java b/core/java/android/text/method/HideReturnsTransformationMethod.java
new file mode 100644
index 0000000..ce18692
--- /dev/null
+++ b/core/java/android/text/method/HideReturnsTransformationMethod.java
@@ -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;
+}
diff --git a/core/java/android/text/method/KeyListener.java b/core/java/android/text/method/KeyListener.java
new file mode 100644
index 0000000..05ab72d
--- /dev/null
+++ b/core/java/android/text/method/KeyListener.java
@@ -0,0 +1,42 @@
+/*
+ * 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.Message;
+import android.text.*;
+import android.widget.TextView;
+
+public interface KeyListener
+{
+ /**
+ * 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);
+}
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
new file mode 100644
index 0000000..92ac531
--- /dev/null
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -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 Object();
+}
diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java
new file mode 100644
index 0000000..2d75b87
--- /dev/null
+++ b/core/java/android/text/method/MetaKeyKeyListener.java
@@ -0,0 +1,250 @@
+/*
+ * 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
+ * (caps, fn, sym). Key listener 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;
+
+ public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << 8;
+ public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << 8;
+ public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << 8;
+
+ private static final Object CAP = new Object();
+ private static final Object ALT = new Object();
+ private static final Object SYM = new Object();
+
+ /**
+ * Resets all meta state to inactive.
+ */
+ public static void resetMetaState(Spannable text) {
+ text.removeSpan(CAP);
+ text.removeSpan(ALT);
+ text.removeSpan(SYM);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Gets the state of a particular meta key.
+ *
+ * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
+ * @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);
+
+ 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 meta state in the specified text.
+ */
+ public static boolean isMetaTracker(CharSequence text, Object what) {
+ return what == CAP || what == ALT || what == SYM;
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+}
+
diff --git a/core/java/android/text/method/MovementMethod.java b/core/java/android/text/method/MovementMethod.java
new file mode 100644
index 0000000..9e37e59
--- /dev/null
+++ b/core/java/android/text/method/MovementMethod.java
@@ -0,0 +1,43 @@
+/*
+ * 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);
+ 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();
+}
diff --git a/core/java/android/text/method/MultiTapKeyListener.java b/core/java/android/text/method/MultiTapKeyListener.java
new file mode 100644
index 0000000..7137d40
--- /dev/null
+++ b/core/java/android/text/method/MultiTapKeyListener.java
@@ -0,0 +1,285 @@
+/*
+ * 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.Message;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.*;
+import android.text.method.TextKeyListener.Capitalize;
+import android.widget.TextView;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+/**
+ * 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 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) { }
+}
+
diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java
new file mode 100644
index 0000000..348b658
--- /dev/null
+++ b/core/java/android/text/method/NumberKeyListener.java
@@ -0,0 +1,132 @@
+/*
+ * 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;
+import android.util.SparseIntArray;
+
+/**
+ * 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);
+ }
+
+ 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);
+ }
+}
diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java
new file mode 100644
index 0000000..edaa836
--- /dev/null
+++ b/core/java/android/text/method/PasswordTransformationMethod.java
@@ -0,0 +1,260 @@
+/*
+ * 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.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]);
+ }
+
+ 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) {
+ Visible[] old = sp.getSpans(0, sp.length(), Visible.class);
+ for (int i = 0; i < old.length; i++) {
+ sp.removeSpan(old[i]);
+ }
+
+ 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;
+
+ 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> {
+ public ViewReference(View v) {
+ super(v);
+ }
+ }
+
+ private static PasswordTransformationMethod sInstance;
+ private static char DOT = '\u2022';
+}
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
new file mode 100644
index 0000000..ae7ba8f
--- /dev/null
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -0,0 +1,444 @@
+/*
+ * 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.Message;
+import android.os.Handler;
+import android.text.*;
+import android.text.method.TextKeyListener.Capitalize;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.TextView;
+
+import java.util.HashMap;
+
+/**
+ * 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 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 == '"' || i == ')' || i == ']') &&
+ 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);
+
+ if (Character.isLetter(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]);
+ 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);
+
+ 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('!', "\u00A1");
+ PICKER_SETS.put('<', "\u00AB");
+ PICKER_SETS.put('>', "\u00BB");
+ PICKER_SETS.put('?', "\u00BF");
+ PICKER_SETS.put('A', "\u00C0\u00C1\u00C2\u00C4\u00C6\u00C3\u00C5");
+ PICKER_SETS.put('C', "\u00C7");
+ PICKER_SETS.put('E', "\u00C8\u00C9\u00CA\u00CB");
+ PICKER_SETS.put('I', "\u00CC\u00CD\u00CE\u00CF");
+ PICKER_SETS.put('N', "\u00D1");
+ PICKER_SETS.put('O', "\u00D8\u0152\u00D5\u00D2\u00D3\u00D4\u00D6");
+ PICKER_SETS.put('U', "\u00D9\u00DA\u00DB\u00DC");
+ PICKER_SETS.put('Y', "\u00DD\u0178");
+ PICKER_SETS.put('a', "\u00E0\u00E1\u00E2\u00E4\u00E6\u00E3\u00E5");
+ PICKER_SETS.put('c', "\u00E7");
+ PICKER_SETS.put('e', "\u00E8\u00E9\u00EA\u00EB");
+ PICKER_SETS.put('i', "\u00EC\u00ED\u00EE\u00EF");
+ PICKER_SETS.put('n', "\u00F1");
+ PICKER_SETS.put('o', "\u00F8\u0153\u00F5\u00F2\u00F3\u00F4\u00F6");
+ PICKER_SETS.put('s', "\u00A7\u00DF");
+ PICKER_SETS.put('u', "\u00F9\u00FA\u00FB\u00FC");
+ PICKER_SETS.put('y', "\u00FD\u00FF");
+ PICKER_SETS.put(KeyCharacterMap.PICKER_DIALOG_INPUT,
+ "\u2026\u00A5\u2022\u00AE\u00A9\u00B1");
+ };
+
+ 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
+ {
+ public Replaced(char[] text) {
+ mText = text;
+ }
+
+ private char[] mText;
+ }
+
+ private Capitalize mAutoCap;
+ private boolean mAutoText;
+}
+
diff --git a/core/java/android/text/method/ReplacementTransformationMethod.java b/core/java/android/text/method/ReplacementTransformationMethod.java
new file mode 100644
index 0000000..d6f879a
--- /dev/null
+++ b/core/java/android/text/method/ReplacementTransformationMethod.java
@@ -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;
+ }
+}
diff --git a/core/java/android/text/method/ScrollingMovementMethod.java b/core/java/android/text/method/ScrollingMovementMethod.java
new file mode 100644
index 0000000..0438e1e
--- /dev/null
+++ b/core/java/android/text/method/ScrollingMovementMethod.java
@@ -0,0 +1,234 @@
+/*
+ * 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) {
+ 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 onTouchEvent(TextView widget, Spannable buffer,
+ MotionEvent event) {
+ return Touch.onTouchEvent(widget, buffer, event);
+ }
+
+ public boolean onTrackballEvent(TextView widget, Spannable buffer,
+ MotionEvent event) {
+ boolean handled = false;
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ for (; y < 0; y++) {
+ handled |= up(widget, buffer);
+ }
+ for (; y > 0; y--) {
+ handled |= down(widget, buffer);
+ }
+
+ for (; x < 0; x++) {
+ handled |= left(widget, buffer);
+ }
+ for (; x > 0; x--) {
+ handled |= right(widget, buffer);
+ }
+
+ return handled;
+ }
+
+ 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;
+}
diff --git a/core/java/android/text/method/SingleLineTransformationMethod.java b/core/java/android/text/method/SingleLineTransformationMethod.java
new file mode 100644
index 0000000..a4fcf15
--- /dev/null
+++ b/core/java/android/text/method/SingleLineTransformationMethod.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+public class SingleLineTransformationMethod
+extends ReplacementTransformationMethod {
+ private static char[] ORIGINAL = new char[] { '\n' };
+ private static char[] REPLACEMENT = new char[] { ' ' };
+
+ /**
+ * The character to be replaced is \n.
+ */
+ protected char[] getOriginal() {
+ return ORIGINAL;
+ }
+
+ /**
+ * The character \n is replaced with is space.
+ */
+ protected char[] getReplacement() {
+ return REPLACEMENT;
+ }
+
+ public static SingleLineTransformationMethod getInstance() {
+ if (sInstance != null)
+ return sInstance;
+
+ sInstance = new SingleLineTransformationMethod();
+ return sInstance;
+ }
+
+ private static SingleLineTransformationMethod sInstance;
+}
diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java
new file mode 100644
index 0000000..012e41d
--- /dev/null
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -0,0 +1,339 @@
+/*
+ * 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 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 Object();
+ /* package */ static final Object CAPPED = new Object();
+ /* package */ static final Object INHIBIT_REPLACEMENT = new Object();
+ /* package */ static final Object LAST_TYPED = new Object();
+
+ 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;
+ }
+
+ // Back over allowed opening punctuation.
+
+ for (i = off; i > 0; i--) {
+ c = cs.charAt(i - 1);
+
+ if (c != '"' && c != '(' && c != '[' && c != '\'') {
+ break;
+ }
+ }
+
+ // Start of paragraph, with optional whitespace.
+
+ int j = i;
+ while (j > 0 && ((c = cs.charAt(j - 1)) == ' ' || c == '\t')) {
+ j--;
+ }
+ if (j == 0 || cs.charAt(j - 1) == '\n') {
+ return true;
+ }
+
+ // Or start of word if we are that style.
+
+ if (cap == Capitalize.WORDS) {
+ return i != j;
+ }
+
+ // There must be a space if not the start of paragraph.
+
+ if (i == j) {
+ return false;
+ }
+
+ // Back over allowed closing punctuation.
+
+ for (; j > 0; j--) {
+ c = cs.charAt(j - 1);
+
+ if (c != '"' && c != ')' && c != ']' && c != '\'') {
+ break;
+ }
+ }
+
+ if (j > 0) {
+ c = cs.charAt(j - 1);
+
+ if (c == '.' || c == '?' || c == '!') {
+ // Do not capitalize if the word ends with a period but
+ // also contains a period, in which case it is an abbreviation.
+
+ if (c == '.') {
+ for (int k = j - 2; k >= 0; k--) {
+ c = cs.charAt(k);
+
+ if (c == '.') {
+ return false;
+ }
+
+ if (!Character.isLetter(c)) {
+ break;
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @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);
+ }
+
+ /**
+ * 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 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 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);
+ 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;
+ }
+}
diff --git a/core/java/android/text/method/TimeKeyListener.java b/core/java/android/text/method/TimeKeyListener.java
new file mode 100644
index 0000000..9ba1fe6
--- /dev/null
+++ b/core/java/android/text/method/TimeKeyListener.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * For entering times in a text field.
+ */
+public class TimeKeyListener extends NumberKeyListener
+{
+ @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;
+}
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
new file mode 100644
index 0000000..bd01728
--- /dev/null
+++ b/core/java/android/text/method/Touch.java
@@ -0,0 +1,138 @@
+/*
+ * 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.Spannable;
+import android.view.MotionEvent;
+import android.view.View;
+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;
+
+ for (int i = top; i <= bottom; i++) {
+ left = (int) Math.min(left, layout.getLineLeft(i));
+ right = (int) Math.max(right, layout.getLineRight(i));
+ }
+
+ padding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight();
+ x = Math.min(x, right - (widget.getWidth() - padding));
+ x = Math.max(x, left);
+
+ widget.scrollTo(x, y);
+ }
+
+ /**
+ * 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.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ buffer.setSpan(new DragState(event.getX(), event.getY()),
+ 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.getTouchSlop();
+
+ 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;
+
+ float dx = ds[0].mX - event.getX();
+ float 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);
+
+ scrollTo(widget, layout, nx, ny);
+ widget.cancelLongPress();
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static class DragState {
+ public float mX;
+ public float mY;
+ public boolean mFarEnough;
+ public boolean mUsed;
+
+ public DragState(float x, float y) {
+ mX = x;
+ mY = y;
+ }
+ }
+}
diff --git a/core/java/android/text/method/TransformationMethod.java b/core/java/android/text/method/TransformationMethod.java
new file mode 100644
index 0000000..9f51c2a
--- /dev/null
+++ b/core/java/android/text/method/TransformationMethod.java
@@ -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);
+}
diff --git a/core/java/android/text/method/package.html b/core/java/android/text/method/package.html
new file mode 100644
index 0000000..93698b8
--- /dev/null
+++ b/core/java/android/text/method/package.html
@@ -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>