am dbd25cdb: Made text selection work in ExtractEditText. DO NOT MERGE

Merge commit 'dbd25cdbc3dcad573aaeaf493bc186006bce3d8e' into gingerbread-plus-aosp

* commit 'dbd25cdbc3dcad573aaeaf493bc186006bce3d8e':
  Made text selection work in ExtractEditText. DO NOT MERGE
This commit is contained in:
Gilles Debunne
2010-09-07 18:59:10 -07:00
committed by Android Git Automerger
3 changed files with 122 additions and 105 deletions

View File

@@ -78450,7 +78450,7 @@
type="float" type="float"
transient="false" transient="false"
volatile="false" volatile="false"
value="0.0010f" value="0.001f"
static="true" static="true"
final="true" final="true"
deprecated="not deprecated" deprecated="not deprecated"
@@ -225477,7 +225477,7 @@
deprecated="not deprecated" deprecated="not deprecated"
visibility="public" visibility="public"
> >
<parameter name="t" type="T"> <parameter name="arg0" type="T">
</parameter> </parameter>
</method> </method>
</interface> </interface>

View File

@@ -18,6 +18,7 @@ package android.inputmethodservice;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.ContextMenu;
import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedText;
import android.widget.EditText; import android.widget.EditText;
@@ -28,6 +29,7 @@ import android.widget.EditText;
public class ExtractEditText extends EditText { public class ExtractEditText extends EditText {
private InputMethodService mIME; private InputMethodService mIME;
private int mSettingExtractedText; private int mSettingExtractedText;
private boolean mContextMenuShouldBeHandledBySuper = false;
public ExtractEditText(Context context) { public ExtractEditText(Context context) {
super(context, null); super(context, null);
@@ -97,12 +99,19 @@ public class ExtractEditText extends EditText {
return false; return false;
} }
@Override
protected void onCreateContextMenu(ContextMenu menu) {
super.onCreateContextMenu(menu);
mContextMenuShouldBeHandledBySuper = true;
}
@Override public boolean onTextContextMenuItem(int id) { @Override public boolean onTextContextMenuItem(int id) {
if (mIME != null) { if (mIME != null && !mContextMenuShouldBeHandledBySuper) {
if (mIME.onExtractTextContextMenuItem(id)) { if (mIME.onExtractTextContextMenuItem(id)) {
return true; return true;
} }
} }
mContextMenuShouldBeHandledBySuper = false;
return super.onTextContextMenuItem(id); return super.onTextContextMenuItem(id);
} }

View File

@@ -35,6 +35,7 @@ import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.inputmethodservice.ExtractEditText;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
@@ -3674,18 +3675,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean changed = false; boolean changed = false;
SelectionModifierCursorController selectionController = null;
if (mSelectionModifierCursorController != null) {
selectionController = (SelectionModifierCursorController)
mSelectionModifierCursorController;
}
if (mMovement != null) { if (mMovement != null) {
/* This code also provides auto-scrolling when a cursor is moved using a /* This code also provides auto-scrolling when a cursor is moved using a
* CursorController (insertion point or selection limits). * CursorController (insertion point or selection limits).
* For selection, ensure start or end is visible depending on controller's state. * For selection, ensure start or end is visible depending on controller's state.
*/ */
int curs = getSelectionEnd(); int curs = getSelectionEnd();
if (mSelectionModifierCursorController != null) { if (selectionController != null && selectionController.isSelectionStartDragged()) {
SelectionModifierCursorController selectionController = curs = getSelectionStart();
(SelectionModifierCursorController) mSelectionModifierCursorController;
if (selectionController.isSelectionStartDragged()) {
curs = getSelectionStart();
}
} }
/* /*
@@ -3705,10 +3709,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
changed = bringTextIntoView(); changed = bringTextIntoView();
} }
if (mShouldStartTextSelectionMode) { // This has to be checked here since:
// - onFocusChanged cannot start it when focus is given to a view with selected text (after
// a screen rotation) since layout is not yet initialized at that point.
// - ExtractEditText does not call onFocus when it is displayed. Fixing this issue would
// allow to test for hasSelection in onFocusChanged, which would trigger a
// startTextSelectionMode here. TODO
if (selectionController != null && hasSelection()) {
startTextSelectionMode(); startTextSelectionMode();
mShouldStartTextSelectionMode = false;
} }
mPreDrawState = PREDRAW_DONE; mPreDrawState = PREDRAW_DONE;
return !changed; return !changed;
} }
@@ -6476,19 +6486,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mShowCursor = SystemClock.uptimeMillis(); mShowCursor = SystemClock.uptimeMillis();
ensureEndedBatchEdit(); ensureEndedBatchEdit();
if (focused) { if (focused) {
int selStart = getSelectionStart(); int selStart = getSelectionStart();
int selEnd = getSelectionEnd(); int selEnd = getSelectionEnd();
if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) { if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
boolean selMoved = mSelectionMoved; // Has to be done before onTakeFocus, which can be overloaded.
if (mLastTouchOffset >= 0) {
if (mSelectionModifierCursorController != null) { Selection.setSelection((Spannable) mText, mLastTouchOffset);
final int touchOffset =
((SelectionModifierCursorController) mSelectionModifierCursorController).
getMinTouchOffset();
Selection.setSelection((Spannable) mText, touchOffset);
} }
if (mMovement != null) { if (mMovement != null) {
@@ -6499,7 +6505,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Selection.setSelection((Spannable) mText, 0, mText.length()); Selection.setSelection((Spannable) mText, 0, mText.length());
} }
if (selMoved && selStart >= 0 && selEnd >= 0) { // The DecorView does not have focus when the 'Done' ExtractEditText button is
// pressed. Since it is the ViewRoot's mView, it requests focus before
// ExtractEditText clears focus, which gives focus to the ExtractEditText.
// This special case ensure that we keep current selection in that case.
// It would be better to know why the DecorView does not have focus at that time.
if (((this instanceof ExtractEditText) || mSelectionMoved) && selStart >= 0 && selEnd >= 0) {
/* /*
* Someone intentionally set the selection, so let them * Someone intentionally set the selection, so let them
* do whatever it is that they wanted to do instead of * do whatever it is that they wanted to do instead of
@@ -6509,7 +6520,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* just setting the selection in theirs and we still * just setting the selection in theirs and we still
* need to go through that path. * need to go through that path.
*/ */
Selection.setSelection((Spannable) mText, selStart, selEnd); Selection.setSelection((Spannable) mText, selStart, selEnd);
} }
mTouchFocusSelected = true; mTouchFocusSelected = true;
@@ -6528,13 +6538,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mError != null) { if (mError != null) {
showError(); showError();
} }
// We cannot start the selection mode immediately. The layout may be null here and is
// needed by the cursor controller. Layout creation is deferred up to drawing. The
// selection action mode will be started in onPreDraw().
if (selStart != selEnd) {
mShouldStartTextSelectionMode = true;
}
} else { } else {
if (mError != null) { if (mError != null) {
hideError(); hideError();
@@ -6543,14 +6546,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
onEndBatchEdit(); onEndBatchEdit();
hideInsertionPointCursorController(); hideInsertionPointCursorController();
terminateTextSelectionMode(); if (this instanceof ExtractEditText) {
// terminateTextSelectionMode would remove selection, which we want to keep when
// ExtractEditText goes out of focus.
mIsInTextSelectionMode = false;
} else {
terminateTextSelectionMode();
}
} }
startStopMarquee(focused); startStopMarquee(focused);
if (mTransformation != null) { if (mTransformation != null) {
mTransformation.onFocusChanged(this, mText, focused, direction, mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
previouslyFocusedRect);
} }
super.onFocusChanged(focused, direction, previouslyFocusedRect); super.onFocusChanged(focused, direction, previouslyFocusedRect);
@@ -6609,60 +6617,57 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} }
} }
class CommitSelectionReceiver extends ResultReceiver { private void onTapUpEvent(int prevStart, int prevEnd) {
private final int mPrevStart, mPrevEnd; final int start = getSelectionStart();
private final int mNewStart, mNewEnd; final int end = getSelectionEnd();
public CommitSelectionReceiver(int mPrevStart, int mPrevEnd, int mNewStart, int mNewEnd) {
super(getHandler());
this.mPrevStart = mPrevStart;
this.mPrevEnd = mPrevEnd;
this.mNewStart = mNewStart;
this.mNewEnd = mNewEnd;
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
int start = mNewStart;
int end = mNewEnd;
// Move the cursor to the new position, unless this tap was actually if (start == end) {
// use to show the IMM. Leave cursor unchanged in that case. if (start >= prevStart && start < prevEnd) {
if (resultCode == InputMethodManager.RESULT_SHOWN) { // Tapping inside the selection displays the cut/copy/paste context menu.
start = mPrevStart; showContextMenu();
end = mPrevEnd; return;
} else { } else {
if ((mPrevStart != mPrevEnd) && (start == end)) { // Tapping outside stops selection mode, if any
if ((start >= mPrevStart) && (start < mPrevEnd)) { stopTextSelectionMode();
// Tapping inside the selection does nothing
Selection.setSelection((Spannable) mText, mPrevStart, mPrevEnd);
showContextMenu();
return;
} else {
// Tapping outside stops selection mode, if any
stopTextSelectionMode();
}
}
if (mInsertionPointCursorController != null) { if (mInsertionPointCursorController != null) {
mInsertionPointCursorController.show(); mInsertionPointCursorController.show();
} }
} }
}
}
final int len = mText.length(); class CommitSelectionReceiver extends ResultReceiver {
if (start > len) { private final int mPrevStart, mPrevEnd;
start = len;
public CommitSelectionReceiver(int prevStart, int prevEnd) {
super(getHandler());
mPrevStart = prevStart;
mPrevEnd = prevEnd;
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
// If this tap was actually used to show the IMM, leave cursor or selection unchanged
// by restoring its previous position.
if (resultCode == InputMethodManager.RESULT_SHOWN) {
final int len = mText.length();
int start = Math.min(len, mPrevStart);
int end = Math.min(len, mPrevEnd);
Selection.setSelection((Spannable)mText, start, end);
if (hasSelection()) {
startTextSelectionMode();
} else if (mInsertionPointCursorController != null) {
mInsertionPointCursorController.show();
}
} }
if (end > len) {
end = len;
}
Selection.setSelection((Spannable)mText, start, end);
} }
} }
@Override @Override
public boolean onTouchEvent(MotionEvent event) { public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction(); final int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) { if (action == MotionEvent.ACTION_DOWN) {
// Reset this state; it will be re-set if super.onTouchEvent // Reset this state; it will be re-set if super.onTouchEvent
// causes focus to move to the view. // causes focus to move to the view.
@@ -6683,10 +6688,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} }
if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) { if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
int oldSelStart = getSelectionStart();
int oldSelEnd = getSelectionEnd();
if (mInsertionPointCursorController != null) { if (mInsertionPointCursorController != null) {
mInsertionPointCursorController.onTouchEvent(event); mInsertionPointCursorController.onTouchEvent(event);
} }
@@ -6695,6 +6697,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} }
boolean handled = false; boolean handled = false;
// Save previous selection, in case this event is used to show the IME.
int oldSelStart = getSelectionStart();
int oldSelEnd = getSelectionEnd();
if (mMovement != null) { if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
@@ -6704,18 +6710,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) { if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
InputMethodManager imm = (InputMethodManager) InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE); getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
final int newSelStart = getSelectionStart();
final int newSelEnd = getSelectionEnd();
CommitSelectionReceiver csr = null; CommitSelectionReceiver csr = null;
if (newSelStart != oldSelStart || newSelEnd != oldSelEnd || if (getSelectionStart() != oldSelStart || getSelectionEnd() != oldSelEnd ||
didTouchFocusSelect()) { didTouchFocusSelect()) {
csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd, csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd);
newSelStart, newSelEnd);
} }
handled |= imm.showSoftInput(this, 0, csr) && (csr != null); handled |= imm.showSoftInput(this, 0, csr) && (csr != null);
// Cannot be done by CommitSelectionReceiver, which might not always be called,
// for instance when dealing with an ExtractEditText.
onTapUpEvent(oldSelStart, oldSelEnd);
} }
} }
@@ -7157,14 +7163,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} }
private String getWordForDictionary() { private String getWordForDictionary() {
if (mSelectionModifierCursorController == null) { if (mLastTouchOffset < 0) {
return null; return null;
} }
int offset = ((SelectionModifierCursorController) mSelectionModifierCursorController). long wordLimits = getWordLimitsAt(mLastTouchOffset);
getMinTouchOffset();
long wordLimits = getWordLimitsAt(offset);
if (wordLimits >= 0) { if (wordLimits >= 0) {
int start = (int) (wordLimits >>> 32); int start = (int) (wordLimits >>> 32);
int end = (int) (wordLimits & 0x00000000FFFFFFFFL); int end = (int) (wordLimits & 0x00000000FFFFFFFFL);
@@ -7172,7 +7175,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else { } else {
return null; return null;
} }
} }
@Override @Override
@@ -7444,18 +7446,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} }
private void startTextSelectionMode() { private void startTextSelectionMode() {
if (mSelectionModifierCursorController == null) { if (!mIsInTextSelectionMode) {
Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled."); if (mSelectionModifierCursorController == null) {
return; Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled.");
} return;
}
if (!requestFocus()) { if (!requestFocus()) {
return; return;
} }
selectCurrentWord(); selectCurrentWord();
mSelectionModifierCursorController.show(); mSelectionModifierCursorController.show();
mIsInTextSelectionMode = true; mIsInTextSelectionMode = true;
}
} }
/** /**
@@ -7560,8 +7564,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mHotSpotVerticalPosition = lineTop; mHotSpotVerticalPosition = lineTop;
final Rect bounds = sCursorControllerTempRect; final Rect bounds = sCursorControllerTempRect;
bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0); bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0)
bounds.top = (bottom ? lineBottom : lineTop) - drawableHeight / 2; + mScrollX;
bounds.top = (bottom ? lineBottom : lineTop) - drawableHeight / 2 + mScrollY;
mTopExtension = bottom ? 0 : drawableHeight / 2; mTopExtension = bottom ? 0 : drawableHeight / 2;
mBottomExtension = drawableHeight; mBottomExtension = drawableHeight;
@@ -7592,6 +7597,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
(int) (y - mBottomExtension), (int) (y - mBottomExtension),
(int) (x + drawableWidth / 2.0), (int) (x + drawableWidth / 2.0),
(int) (y + mTopExtension)); (int) (y + mTopExtension));
fingerRect.offset(mScrollX, mScrollY);
return Rect.intersects(mDrawable.getBounds(), fingerRect); return Rect.intersects(mDrawable.getBounds(), fingerRect);
} }
@@ -7870,7 +7876,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return; return;
} }
boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) == mLayout.getLineForOffset(selectionEnd); boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) ==
mLayout.getLineForOffset(selectionEnd);
mStartHandle.positionAtCursor(selectionStart, oneLineSelection); mStartHandle.positionAtCursor(selectionStart, oneLineSelection);
mEndHandle.positionAtCursor(selectionEnd, true); mEndHandle.positionAtCursor(selectionEnd, true);
@@ -7886,7 +7893,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int y = (int) event.getY(); final int y = (int) event.getY();
// Remember finger down position, to be able to start selection from there // Remember finger down position, to be able to start selection from there
mMinTouchOffset = mMaxTouchOffset = getOffset(x, y); mMinTouchOffset = mMaxTouchOffset = mLastTouchOffset = getOffset(x, y);
if (mIsVisible) { if (mIsVisible) {
if (mMovement instanceof ArrowKeyMovementMethod) { if (mMovement instanceof ArrowKeyMovementMethod) {
@@ -7902,7 +7909,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// In case both controllers are under finger (very small // In case both controllers are under finger (very small
// selection region), arbitrarily pick end controller. // selection region), arbitrarily pick end controller.
mStartIsDragged = !isOnEnd; mStartIsDragged = !isOnEnd;
final Handle draggedHandle = mStartIsDragged ? mStartHandle : mEndHandle; final Handle draggedHandle =
mStartIsDragged ? mStartHandle : mEndHandle;
final Rect bounds = draggedHandle.mDrawable.getBounds(); final Rect bounds = draggedHandle.mDrawable.getBounds();
mOffsetX = (bounds.left + bounds.right) / 2.0f - x; mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
mOffsetY = draggedHandle.mHotSpotVerticalPosition - y; mOffsetY = draggedHandle.mHotSpotVerticalPosition - y;
@@ -8076,8 +8084,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Cursor Controllers. Null when disabled. // Cursor Controllers. Null when disabled.
private CursorController mInsertionPointCursorController; private CursorController mInsertionPointCursorController;
private CursorController mSelectionModifierCursorController; private CursorController mSelectionModifierCursorController;
private boolean mShouldStartTextSelectionMode = false;
private boolean mIsInTextSelectionMode = false; private boolean mIsInTextSelectionMode = false;
private int mLastTouchOffset = -1;
// Created once and shared by different CursorController helper methods. // Created once and shared by different CursorController helper methods.
// Only one cursor controller is active at any time which prevent race conditions. // Only one cursor controller is active at any time which prevent race conditions.
private static Rect sCursorControllerTempRect = new Rect(); private static Rect sCursorControllerTempRect = new Rect();