summaryrefslogtreecommitdiffstats
path: root/core/java/android/widget
diff options
context:
space:
mode:
authorJames Cook <jamescook@google.com>2015-03-18 21:38:50 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2015-03-18 21:39:03 +0000
commitf2560e62cf26cae64f5751b0479743e09cb7bd7f (patch)
tree802ec2d549ec9a73cfa57737e26c501633c7d5bb /core/java/android/widget
parent43c410eaacf7d287c6c3f5621e6e0b96501004dc (diff)
parentd2026686702f6c893e871c078dc5176347b3b27e (diff)
downloadframeworks_base-f2560e62cf26cae64f5751b0479743e09cb7bd7f.zip
frameworks_base-f2560e62cf26cae64f5751b0479743e09cb7bd7f.tar.gz
frameworks_base-f2560e62cf26cae64f5751b0479743e09cb7bd7f.tar.bz2
Merge "Improve undo support for text entered with IME"
Diffstat (limited to 'core/java/android/widget')
-rw-r--r--core/java/android/widget/Editor.java116
-rw-r--r--core/java/android/widget/TextView.java10
2 files changed, 100 insertions, 26 deletions
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2cb6409..6d7a547 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -236,12 +236,17 @@ public class Editor {
}
ParcelableParcel saveInstanceState() {
- // For now there is only undo state.
- return (ParcelableParcel) mUndoManager.saveInstanceState();
+ ParcelableParcel state = new ParcelableParcel(getClass().getClassLoader());
+ Parcel parcel = state.getParcel();
+ mUndoManager.saveInstanceState(parcel);
+ mUndoInputFilter.saveInstanceState(parcel);
+ return state;
}
void restoreInstanceState(ParcelableParcel state) {
- mUndoManager.restoreInstanceState(state);
+ Parcel parcel = state.getParcel();
+ mUndoManager.restoreInstanceState(parcel, state.getClassLoader());
+ mUndoInputFilter.restoreInstanceState(parcel);
// Re-associate this object as the owner of undo state.
mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this);
}
@@ -4590,20 +4595,30 @@ public class Editor {
// Whether the current filter pass is directly caused by an end-user text edit.
private boolean mIsUserEdit;
- // Whether this is the first pass through the filter for a given end-user text edit.
- private boolean mFirstFilterPass;
+ // Whether the text field is handling an IME composition. Must be parceled in case the user
+ // rotates the screen during composition.
+ private boolean mHasComposition;
public UndoInputFilter(Editor editor) {
mEditor = editor;
}
+ public void saveInstanceState(Parcel parcel) {
+ parcel.writeInt(mIsUserEdit ? 1 : 0);
+ parcel.writeInt(mHasComposition ? 1 : 0);
+ }
+
+ public void restoreInstanceState(Parcel parcel) {
+ mIsUserEdit = parcel.readInt() != 0;
+ mHasComposition = parcel.readInt() != 0;
+ }
+
/**
* Signals that a user-triggered edit is starting.
*/
public void beginBatchEdit() {
if (DEBUG_UNDO) Log.d(TAG, "beginBatchEdit");
mIsUserEdit = true;
- mFirstFilterPass = true;
}
public void endBatchEdit() {
@@ -4624,17 +4639,63 @@ public class Editor {
return null;
}
+ // Check for and handle IME composition edits.
+ if (handleCompositionEdit(source, start, end, dstart)) {
+ return null;
+ }
+
+ // Handle keyboard edits.
+ handleKeyboardEdit(source, start, end, dest, dstart, dend);
+ return null;
+ }
+
+ /**
+ * Returns true iff the edit was handled, either because it should be ignored or because
+ * this function created an undo operation for it.
+ */
+ private boolean handleCompositionEdit(CharSequence source, int start, int end, int dstart) {
+ // Ignore edits while the user is composing.
+ if (isComposition(source)) {
+ mHasComposition = true;
+ return true;
+ }
+ final boolean hadComposition = mHasComposition;
+ mHasComposition = false;
+
+ // Check for the transition out of the composing state.
+ if (hadComposition) {
+ // If there was no text the user canceled composition. Ignore the edit.
+ if (start == end) {
+ return true;
+ }
+
+ // Otherwise the user inserted the composition.
+ String newText = TextUtils.substring(source, start, end);
+ EditOperation edit = new EditOperation(mEditor, false, "", dstart, newText);
+ recordEdit(edit);
+ return true;
+ }
+
+ // This was neither a composition event nor a transition out of composing.
+ return false;
+ }
+
+ private void handleKeyboardEdit(CharSequence source, int start, int end,
+ Spanned dest, int dstart, int dend) {
// An application may install a TextWatcher to provide additional modifications after
// the initial input filters run (e.g. a credit card formatter that adds spaces to a
// string). This results in multiple filter() calls for what the user considers to be
// a single operation. Always undo the whole set of changes in one step.
- final boolean forceMerge = !mFirstFilterPass;
- mFirstFilterPass = false;
+ final boolean forceMerge = isInTextWatcher();
// Build a new operation with all the information from this edit.
- EditOperation edit = new EditOperation(mEditor, forceMerge,
- source, start, end, dest, dstart, dend);
+ String newText = TextUtils.substring(source, start, end);
+ String oldText = TextUtils.substring(dest, dstart, dend);
+ EditOperation edit = new EditOperation(mEditor, forceMerge, oldText, dstart, newText);
+ recordEdit(edit);
+ }
+ private void recordEdit(EditOperation edit) {
// Fetch the last edit operation and attempt to merge in the new edit.
final UndoManager um = mEditor.mUndoManager;
um.beginUpdate("Edit text");
@@ -4660,7 +4721,6 @@ public class Editor {
um.addOperation(edit, UndoManager.MERGE_MODE_NONE);
}
um.endUpdate();
- return null; // Text not changed.
}
private boolean canUndoEdit(CharSequence source, int start, int end,
@@ -4692,6 +4752,23 @@ public class Editor {
return true;
}
+
+ private boolean isComposition(CharSequence source) {
+ if (!(source instanceof Spannable)) {
+ return false;
+ }
+ // This is a composition edit if the source has a non-zero-length composing span.
+ Spannable text = (Spannable) source;
+ int composeBegin = EditableInputConnection.getComposingSpanStart(text);
+ int composeEnd = EditableInputConnection.getComposingSpanEnd(text);
+ return composeBegin < composeEnd;
+ }
+
+ private boolean isInTextWatcher() {
+ CharSequence text = mEditor.mTextView.getText();
+ return (text instanceof SpannableStringBuilder)
+ && ((SpannableStringBuilder) text).getTextWatcherDepth() > 0;
+ }
}
/**
@@ -4713,17 +4790,16 @@ public class Editor {
private int mNewCursorPos;
/**
- * Constructs an edit operation from a text input operation that replaces the range
- * (dstart, dend) of dest with (start, end) of source. See {@link InputFilter#filter}.
- * If forceMerge is true then always forcibly merge this operation with any previous one.
+ * Constructs an edit operation from a text input operation on editor that replaces the
+ * oldText starting at dstart with newText. If forceMerge is true then always forcibly
+ * merge this operation with any previous one.
*/
- public EditOperation(Editor editor, boolean forceMerge,
- CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
+ public EditOperation(Editor editor, boolean forceMerge, String oldText, int dstart,
+ String newText) {
super(editor.mUndoOwner);
mForceMerge = forceMerge;
-
- mOldText = dest.subSequence(dstart, dend).toString();
- mNewText = source.subSequence(start, end).toString();
+ mOldText = oldText;
+ mNewText = newText;
// Determine the type of the edit and store where it occurred. Avoid storing
// irrevelant data (e.g. mNewTextStart for a delete) because that makes the
@@ -4742,7 +4818,7 @@ public class Editor {
// Store cursor data.
mOldCursorPos = editor.mTextView.getSelectionStart();
- mNewCursorPos = dstart + (end - start);
+ mNewCursorPos = dstart + mNewText.length();
}
public EditOperation(Parcel src, ClassLoader loader) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 2bfee1c..718ef93 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -111,8 +111,6 @@ import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAssistStructure;
@@ -8957,9 +8955,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* A custom implementation can add new entries in the default menu in its
* {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
- * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
- * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
- * or {@link android.R.id#paste} ids as parameters.
+ * default actions can also be removed from the menu using
+ * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
+ * {@link android.R.id#cut}, {@link android.R.id#copy} or {@link android.R.id#paste} ids as
+ * parameters.
*
* Returning false from
* {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
@@ -9534,7 +9533,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// TODO: Add an option to configure this
private static final float MARQUEE_DELTA_MAX = 0.07f;
private static final int MARQUEE_DELAY = 1200;
- private static final int MARQUEE_RESTART_DELAY = 1200;
private static final int MARQUEE_DP_PER_SECOND = 30;
private static final byte MARQUEE_STOPPED = 0x0;