summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yohei Yukawa <yukawa@google.com> 2017-06-19 18:27:34 -0700
committer Yohei Yukawa <yukawa@google.com> 2017-06-19 18:27:34 -0700
commitc9cd9db4bb67b4e3a89e44a21c42f742940069da (patch)
tree49f7dacc88259b3dfde79e0b7d29b31704c9d33d
parentbc275085ca67998bd00428bf14aa24560dfa8659 (diff)
Use TEXT_HANDLE_MOVE in TextView behind the flag
This CL enables TextView to trigger HapticFeedbackConstants.TEXT_HANDLE_MOVE on devices that explicitly enable this feature with resource overlay. This CL should have no behavior change and no performance impact by default. Since the use case of HapticFeedbackConstants.TEXT_HANDLE_MOVE is when the user is manually moving the text insertion/selection handle on the touch screen, it is intentional that text handle move triggered by hardware keyboard, mouse, TextView APIs, IME APIs, and any other internal API calls do not trigger the haptic feedback even if the feature is enabled with resource overlay. Bug: 62454887 Test: Manually done as follows. * Not triggered on cursor move by mouse * Not triggered on cursor move by hardware keyboard * Not triggered on cursor move by IME * Triggered on cursor move by touch screen Change-Id: I5e78aafb065378ca88ba39ec507b121c8baa3631
-rw-r--r--core/java/android/widget/Editor.java76
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml1
3 files changed, 58 insertions, 24 deletions
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 45e5f8adc468..fa1bd910bdbb 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -83,6 +83,7 @@ import android.view.DisplayListCanvas;
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;
@@ -176,6 +177,8 @@ public class Editor {
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;
@@ -320,6 +323,8 @@ public class Editor {
// 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() {
@@ -4253,7 +4258,7 @@ public class Editor {
mNumberPreviousOffsets++;
}
- private void filterOnTouchUp() {
+ private void filterOnTouchUp(boolean fromTouchScreen) {
final long now = SystemClock.uptimeMillis();
int i = 0;
int index = mPreviousOffsetIndex;
@@ -4265,7 +4270,7 @@ public class Editor {
if (i > 0 && i < iMax
&& (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
- positionAtCursorOffset(mPreviousOffsets[index], false);
+ positionAtCursorOffset(mPreviousOffsets[index], false, fromTouchScreen);
}
}
@@ -4282,7 +4287,7 @@ public class Editor {
public void invalidate() {
super.invalidate();
if (isShowing()) {
- positionAtCursorOffset(getCurrentCursorOffset(), true);
+ positionAtCursorOffset(getCurrentCursorOffset(), true, false);
}
};
@@ -4301,7 +4306,7 @@ public class Editor {
// 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() {
@@ -4338,7 +4343,7 @@ public class Editor {
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);
@@ -4357,8 +4362,11 @@ public class Editor {
* @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) {
@@ -4372,6 +4380,9 @@ public class Editor {
if (offsetChanged || forceUpdatePosition) {
if (offsetChanged) {
updateSelection(offset);
+ if (fromTouchScreen && mHapticTextHandleEnabled) {
+ mTextView.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE);
+ }
addPositionToTouchUpFilter(offset);
}
final int line = layout.getLineForOffset(offset);
@@ -4404,7 +4415,7 @@ public class Editor {
@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
@@ -4516,12 +4527,13 @@ public class Editor {
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;
@@ -4702,7 +4714,7 @@ public class Editor {
}
@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) {
@@ -4715,7 +4727,7 @@ public class Editor {
} else {
offset = -1;
}
- positionAtCursorOffset(offset, false);
+ positionAtCursorOffset(offset, false, fromTouchScreen);
if (mTextActionMode != null) {
invalidateActionMode();
}
@@ -4806,12 +4818,13 @@ public class Editor {
}
@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;
}
@@ -4854,12 +4867,12 @@ public class Editor {
// 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;
@@ -4893,7 +4906,7 @@ public class Editor {
final int nextOffset = (atRtl == isStartHandle())
? layout.getOffsetToRightOf(mPreviousOffset)
: layout.getOffsetToLeftOf(mPreviousOffset);
- positionAndAdjustForCrossingHandles(nextOffset);
+ positionAndAdjustForCrossingHandles(nextOffset, fromTouchScreen);
return;
}
}
@@ -4972,14 +4985,15 @@ public class Editor {
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);
}
@@ -4995,7 +5009,7 @@ public class Editor {
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)
@@ -5020,14 +5034,14 @@ public class Editor {
} 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) {
@@ -5470,7 +5484,8 @@ public class Editor {
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) {
@@ -5528,7 +5543,8 @@ public class Editor {
}
}
mLineSelectionIsOn = currLine;
- Selection.setSelection((Spannable) mTextView.getText(), startOffset, offset);
+ updateSelectionInternal(startOffset, offset,
+ event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
}
private void updateParagraphBasedSelection(MotionEvent event) {
@@ -5539,7 +5555,19 @@ public class Editor {
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);
+ }
}
/**
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4a000b91c4a7..96b30da7c15a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -147,6 +147,11 @@
fading edges is prohibitively expensive on most GPUs. -->
<bool name="config_ui_enableFadingMarquee">false</bool>
+ <!-- Enables or disables haptic effect when the text insertion/selection handle is moved
+ manually by the user. Off by default, since the expected haptic feedback may not be
+ available on some devices. -->
+ <bool name="config_enableHapticTextHandle">false</bool>
+
<!-- Whether dialogs should close automatically when the user touches outside
of them. This should not normally be modified. -->
<bool name="config_closeDialogWhenTouchOutside">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7cee6d698c81..d60c8d2febc0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -273,6 +273,7 @@
<java-symbol type="bool" name="config_syncstorageengine_masterSyncAutomatically" />
<java-symbol type="bool" name="config_telephony_use_own_number_for_voicemail" />
<java-symbol type="bool" name="config_ui_enableFadingMarquee" />
+ <java-symbol type="bool" name="config_enableHapticTextHandle" />
<java-symbol type="bool" name="config_use_strict_phone_number_comparation" />
<java-symbol type="bool" name="config_single_volume" />
<java-symbol type="bool" name="config_voice_capable" />