import android.view.DragAndDropPermissions;
import android.view.DragEvent;
import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.Menu;
private boolean mInsertionControllerEnabled;
private boolean mSelectionControllerEnabled;
+ private final boolean mHapticTextHandleEnabled;
+
// Used to highlight a word when it is corrected by the IME
private CorrectionHighlighter mCorrectionHighlighter;
// Synchronize the filter list, which places the undo input filter at the end.
mTextView.setFilters(mTextView.getFilters());
mProcessTextIntentActionsHandler = new ProcessTextIntentActionsHandler(this);
+ mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_enableHapticTextHandle);
}
ParcelableParcel saveInstanceState() {
mNumberPreviousOffsets++;
}
- private void filterOnTouchUp() {
+ private void filterOnTouchUp(boolean fromTouchScreen) {
final long now = SystemClock.uptimeMillis();
int i = 0;
int index = mPreviousOffsetIndex;
if (i > 0 && i < iMax
&& (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
- positionAtCursorOffset(mPreviousOffsets[index], false);
+ positionAtCursorOffset(mPreviousOffsets[index], false, fromTouchScreen);
}
}
public void invalidate() {
super.invalidate();
if (isShowing()) {
- positionAtCursorOffset(getCurrentCursorOffset(), true);
+ positionAtCursorOffset(getCurrentCursorOffset(), true, false);
}
};
// Make sure the offset is always considered new, even when focusing at same position
mPreviousOffset = -1;
- positionAtCursorOffset(getCurrentCursorOffset(), false);
+ positionAtCursorOffset(getCurrentCursorOffset(), false, false);
}
protected void dismiss() {
protected abstract void updateSelection(int offset);
- public abstract void updatePosition(float x, float y);
+ protected abstract void updatePosition(float x, float y, boolean fromTouchScreen);
protected boolean isAtRtlRun(@NonNull Layout layout, int offset) {
return layout.isRtlCharAt(offset);
* @param offset Cursor offset. Must be in [-1, length].
* @param forceUpdatePosition whether to force update the position. This should be true
* when If the parent has been scrolled, for example.
+ * @param fromTouchScreen {@code true} if the cursor is moved with motion events from the
+ * touch screen.
*/
- protected void positionAtCursorOffset(int offset, boolean forceUpdatePosition) {
+ protected void positionAtCursorOffset(int offset, boolean forceUpdatePosition,
+ boolean fromTouchScreen) {
// A HandleView relies on the layout, which may be nulled by external methods
Layout layout = mTextView.getLayout();
if (layout == null) {
if (offsetChanged || forceUpdatePosition) {
if (offsetChanged) {
updateSelection(offset);
+ if (fromTouchScreen && mHapticTextHandleEnabled) {
+ mTextView.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE);
+ }
addPositionToTouchUpFilter(offset);
}
final int line = layout.getLineForOffset(offset);
@Override
public void updatePosition(int parentPositionX, int parentPositionY,
boolean parentPositionChanged, boolean parentScrolled) {
- positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled);
+ positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled, false);
if (parentPositionChanged || mPositionHasChanged) {
if (mIsDragging) {
// Update touchToWindow offset in case of parent scrolling while dragging
xInWindow - mTouchToWindowOffsetX + mHotspotX + getHorizontalOffset();
final float newPosY = yInWindow - mTouchToWindowOffsetY + mTouchOffsetY;
- updatePosition(newPosX, newPosY);
+ updatePosition(newPosX, newPosY,
+ ev.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
break;
}
case MotionEvent.ACTION_UP:
- filterOnTouchUp();
+ filterOnTouchUp(ev.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
mIsDragging = false;
updateDrawable();
break;
}
@Override
- public void updatePosition(float x, float y) {
+ protected void updatePosition(float x, float y, boolean fromTouchScreen) {
Layout layout = mTextView.getLayout();
int offset;
if (layout != null) {
} else {
offset = -1;
}
- positionAtCursorOffset(offset, false);
+ positionAtCursorOffset(offset, false, fromTouchScreen);
if (mTextActionMode != null) {
invalidateActionMode();
}
}
@Override
- public void updatePosition(float x, float y) {
+ protected void updatePosition(float x, float y, boolean fromTouchScreen) {
final Layout layout = mTextView.getLayout();
if (layout == null) {
// HandleView will deal appropriately in positionAtCursorOffset when
// layout is null.
- positionAndAdjustForCrossingHandles(mTextView.getOffsetForPosition(x, y));
+ positionAndAdjustForCrossingHandles(mTextView.getOffsetForPosition(x, y),
+ fromTouchScreen);
return;
}
// to the current position.
mLanguageDirectionChanged = true;
mTouchWordDelta = 0.0f;
- positionAndAdjustForCrossingHandles(offset);
+ positionAndAdjustForCrossingHandles(offset, fromTouchScreen);
return;
} else if (mLanguageDirectionChanged && !isLvlBoundary) {
// We've just moved past the boundary so update the position. After this we can
// figure out if the user is expanding or shrinking to go by word or character.
- positionAndAdjustForCrossingHandles(offset);
+ positionAndAdjustForCrossingHandles(offset, fromTouchScreen);
mTouchWordDelta = 0.0f;
mLanguageDirectionChanged = false;
return;
final int nextOffset = (atRtl == isStartHandle())
? layout.getOffsetToRightOf(mPreviousOffset)
: layout.getOffsetToLeftOf(mPreviousOffset);
- positionAndAdjustForCrossingHandles(nextOffset);
+ positionAndAdjustForCrossingHandles(nextOffset, fromTouchScreen);
return;
}
}
if (positionCursor) {
mPreviousLineTouched = currLine;
- positionAndAdjustForCrossingHandles(offset);
+ positionAndAdjustForCrossingHandles(offset, fromTouchScreen);
}
mPrevX = x;
}
@Override
- protected void positionAtCursorOffset(int offset, boolean forceUpdatePosition) {
- super.positionAtCursorOffset(offset, forceUpdatePosition);
+ protected void positionAtCursorOffset(int offset, boolean forceUpdatePosition,
+ boolean fromTouchScreen) {
+ super.positionAtCursorOffset(offset, forceUpdatePosition, fromTouchScreen);
mInWord = (offset != -1) && !getWordIteratorWithText().isBoundary(offset);
}
return superResult;
}
- private void positionAndAdjustForCrossingHandles(int offset) {
+ private void positionAndAdjustForCrossingHandles(int offset, boolean fromTouchScreen) {
final int anotherHandleOffset =
isStartHandle() ? mTextView.getSelectionEnd() : mTextView.getSelectionStart();
if ((isStartHandle() && offset >= anotherHandleOffset)
} else {
offset = TextUtils.unpackRangeEndFromLong(range);
}
- positionAtCursorOffset(offset, false);
+ positionAtCursorOffset(offset, false, fromTouchScreen);
return;
}
}
// Handles can not cross and selection is at least one character.
offset = getNextCursorOffset(anotherHandleOffset, !isStartHandle());
}
- positionAtCursorOffset(offset, false);
+ positionAtCursorOffset(offset, false, fromTouchScreen);
}
private boolean positionNearEdgeOfScrollingView(float x, boolean atRtl) {
private void updateCharacterBasedSelection(MotionEvent event) {
final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY());
- Selection.setSelection((Spannable) mTextView.getText(), mStartOffset, offset);
+ updateSelectionInternal(mStartOffset, offset,
+ event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
}
private void updateWordBasedSelection(MotionEvent event) {
}
}
mLineSelectionIsOn = currLine;
- Selection.setSelection((Spannable) mTextView.getText(), startOffset, offset);
+ updateSelectionInternal(startOffset, offset,
+ event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
}
private void updateParagraphBasedSelection(MotionEvent event) {
final long paragraphsRange = getParagraphsRange(start, end);
final int selectionStart = TextUtils.unpackRangeStartFromLong(paragraphsRange);
final int selectionEnd = TextUtils.unpackRangeEndFromLong(paragraphsRange);
+ updateSelectionInternal(selectionStart, selectionEnd,
+ event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
+ }
+
+ private void updateSelectionInternal(int selectionStart, int selectionEnd,
+ boolean fromTouchScreen) {
+ final boolean performHapticFeedback = fromTouchScreen && mHapticTextHandleEnabled
+ && ((mTextView.getSelectionStart() != selectionStart)
+ || (mTextView.getSelectionEnd() != selectionEnd));
Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
+ if (performHapticFeedback) {
+ mTextView.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE);
+ }
}
/**