Merge "Text selection changes - smart handles & drag accelerator"
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f8e207f..5d519ed 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -217,6 +217,12 @@
WordIterator mWordIterator;
SpellChecker mSpellChecker;
+ // This word iterator is set with text and used to determine word boundaries
+ // when a user is selecting text.
+ private WordIterator mWordIteratorWithText;
+ // Indicate that the text in the word iterator needs to be updated.
+ private boolean mUpdateWordIteratorText;
+
private Rect mTempRect;
private TextView mTextView;
@@ -689,9 +695,52 @@
return mTextView.getTransformationMethod() instanceof PasswordTransformationMethod;
}
+ private int getWordStart(int offset) {
+ // FIXME - For this and similar methods we're not doing anything to check if there's
+ // a LocaleSpan in the text, this may be something we should try handling or checking for.
+ int retOffset = getWordIteratorWithText().getBeginning(offset);
+ if (retOffset == BreakIterator.DONE) retOffset = offset;
+ return retOffset;
+ }
+
+ private int getWordEnd(int offset, boolean includePunctuation) {
+ int retOffset = getWordIteratorWithText().getEnd(offset);
+ if (retOffset == BreakIterator.DONE) {
+ retOffset = offset;
+ } else if (includePunctuation) {
+ retOffset = handlePunctuation(retOffset);
+ }
+ return retOffset;
+ }
+
+ private boolean isEndBoundary(int offset) {
+ int thisEnd = getWordEnd(offset, false);
+ return offset == thisEnd;
+ }
+
+ private boolean isStartBoundary(int offset) {
+ int thisStart = getWordStart(offset);
+ return thisStart == offset;
+ }
+
+ private int handlePunctuation(int offset) {
+ // FIXME - Check with UX how repeated ending punctuation should be handled.
+ // FIXME - Check with UX if / how we would handle non sentence ending characters.
+ // FIXME - Consider punctuation in different languages.
+ CharSequence text = mTextView.getText();
+ if (offset < text.length()) {
+ int c = Character.codePointAt(text, offset);
+ if (c == 0x002e /* period */|| c == 0x003f /* question mark */
+ || c == 0x0021 /* exclamation mark */) {
+ offset = Character.offsetByCodePoints(text, offset, 1);
+ }
+ }
+ return offset;
+ }
+
/**
- * Adjusts selection to the word under last touch offset.
- * Return true if the operation was successfully performed.
+ * Adjusts selection to the word under last touch offset. Return true if the operation was
+ * successfully performed.
*/
private boolean selectCurrentWord() {
if (!canSelectText()) {
@@ -738,6 +787,8 @@
selectionStart = ((Spanned) mTextView.getText()).getSpanStart(urlSpan);
selectionEnd = ((Spanned) mTextView.getText()).getSpanEnd(urlSpan);
} else {
+ // FIXME - We should check if there's a LocaleSpan in the text, this may be
+ // something we should try handling or checking for.
final WordIterator wordIterator = getWordIterator();
wordIterator.setCharSequence(mTextView.getText(), minOffset, maxOffset);
@@ -760,6 +811,7 @@
void onLocaleChanged() {
// Will be re-created on demand in getWordIterator with the proper new locale
mWordIterator = null;
+ mWordIteratorWithText = null;
}
/**
@@ -772,6 +824,23 @@
return mWordIterator;
}
+ private WordIterator getWordIteratorWithText() {
+ if (mWordIteratorWithText == null) {
+ mWordIteratorWithText = new WordIterator(mTextView.getTextServicesLocale());
+ mUpdateWordIteratorText = true;
+ }
+ if (mUpdateWordIteratorText) {
+ // FIXME - Shouldn't copy all of the text as only the area of the text relevant
+ // to the user's selection is needed. A possible solution would be to
+ // copy some number N of characters near the selection and then when the
+ // user approaches N then we'd do another copy of the next N characters.
+ CharSequence text = mTextView.getText();
+ mWordIteratorWithText.setCharSequence(text, 0, text.length());
+ mUpdateWordIteratorText = false;
+ }
+ return mWordIteratorWithText;
+ }
+
private long getCharRange(int offset) {
final int textLength = mTextView.getText().length();
if (offset + 1 < textLength) {
@@ -920,9 +989,8 @@
mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
stopSelectionActionMode();
} else {
- getSelectionController().hide();
- selectCurrentWord();
- getSelectionController().show();
+ stopSelectionActionMode();
+ startSelectionActionMode();
}
handled = true;
}
@@ -1058,6 +1126,9 @@
void sendOnTextChanged(int start, int after) {
updateSpellCheckSpans(start, start + after, false);
+ // Flip flag to indicate the word iterator needs to have the text reset.
+ mUpdateWordIteratorText = true;
+
// Hide the controllers as soon as text is modified (typing, procedural...)
// We do not hide the span controllers, since they can be added when a new text is
// inserted into the text view (voice IME).
@@ -1613,6 +1684,9 @@
}
}
+ if (selectionStarted) {
+ getSelectionController().enterDrag();
+ }
return selectionStarted;
}
@@ -2894,7 +2968,6 @@
}
if (menu.hasVisibleItems() || mode.getCustomView() != null) {
- getSelectionController().show();
mTextView.setHasTransientState(true);
return true;
} else {
@@ -3232,6 +3305,8 @@
private Runnable mActionPopupShower;
// Minimum touch target size for handles
private int mMinSize;
+ // Indicates the line of text that the handle is on.
+ protected int mLine = -1;
public HandleView(Drawable drawableLtr, Drawable drawableRtl) {
super(mTextView.getContext());
@@ -3407,6 +3482,7 @@
addPositionToTouchUpFilter(offset);
}
final int line = layout.getLineForOffset(offset);
+ mLine = line;
mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX -
getHorizontalOffset() + getCursorOffset());
@@ -3456,6 +3532,30 @@
}
}
+ public void showAtLocation(int offset) {
+ // TODO - investigate if there's a better way to show the handles
+ // after the drag accelerator has occured.
+ int[] tmpCords = new int[2];
+ mTextView.getLocationInWindow(tmpCords);
+
+ Layout layout = mTextView.getLayout();
+ int posX = tmpCords[0];
+ int posY = tmpCords[1];
+
+ final int line = layout.getLineForOffset(offset);
+
+ int startX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f
+ - mHotspotX - getHorizontalOffset() + getCursorOffset());
+ int startY = layout.getLineBottom(line);
+
+ // Take TextView's padding and scroll into account.
+ startX += mTextView.viewportToContentHorizontalOffset();
+ startY += mTextView.viewportToContentVerticalOffset();
+
+ mContainer.showAtLocation(mTextView, Gravity.NO_GRAVITY,
+ startX + posX, startY + posY);
+ }
+
@Override
protected void onDraw(Canvas c) {
final int drawWidth = mDrawable.getIntrinsicWidth();
@@ -3694,6 +3794,12 @@
}
private class SelectionStartHandleView extends HandleView {
+ // The previous offset this handle was at.
+ private int mPrevOffset;
+ // Indicates whether the cursor is making adjustments within a word.
+ private boolean mInWord = false;
+ // Offset to track difference between touch and word boundary.
+ protected int mTouchWordOffset;
public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
super(drawableLtr, drawableRtl);
@@ -3701,11 +3807,7 @@
@Override
protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
- if (isRtlRun) {
- return drawable.getIntrinsicWidth() / 4;
- } else {
- return (drawable.getIntrinsicWidth() * 3) / 4;
- }
+ return isRtlRun ? 0 : drawable.getIntrinsicWidth();
}
@Override
@@ -3727,21 +3829,81 @@
@Override
public void updatePosition(float x, float y) {
- int offset = mTextView.getOffsetForPosition(x, y);
+ final int trueOffset = mTextView.getOffsetForPosition(x, y);
+ final int currLine = mTextView.getLineAtCoordinate(y);
+ int offset = trueOffset;
+ boolean positionCursor = false;
- // Handles can not cross and selection is at least one character
- final int selectionEnd = mTextView.getSelectionEnd();
- if (offset >= selectionEnd) offset = Math.max(0, selectionEnd - 1);
+ int end = getWordEnd(offset, true);
+ int start = getWordStart(offset);
- positionAtCursorOffset(offset, false);
+ if (offset < mPrevOffset) {
+ // User is increasing the selection.
+ if (!mInWord || currLine < mLine) {
+ // We're not in a word, or we're on a different line so we'll expand by
+ // word. First ensure the user has at least entered the next word.
+ int offsetToWord = Math.min((end - start) / 2, 2);
+ if (offset <= end - offsetToWord || currLine < mLine) {
+ offset = start;
+ } else {
+ offset = mPrevOffset;
+ }
+ }
+ mPrevOffset = offset;
+ mTouchWordOffset = trueOffset - offset;
+ mInWord = !isStartBoundary(offset);
+ positionCursor = true;
+ } else if (offset - mTouchWordOffset > mPrevOffset) {
+ // User is shrinking the selection.
+ if (currLine > mLine) {
+ // We're on a different line, so we'll snap to word boundaries.
+ offset = end;
+ }
+ offset -= mTouchWordOffset;
+ mPrevOffset = offset;
+ mInWord = !isEndBoundary(offset);
+ positionCursor = true;
+ }
+
+ // Handles can not cross and selection is at least one character.
+ if (positionCursor) {
+ final int selectionEnd = mTextView.getSelectionEnd();
+ if (offset >= selectionEnd) {
+ // We can't cross the handles so let's just constrain the Y value.
+ int alteredOffset = mTextView.getOffsetAtCoordinate(mLine, x);
+ if (alteredOffset >= selectionEnd) {
+ // Can't pass the other drag handle.
+ offset = Math.max(0, selectionEnd - 1);
+ } else {
+ offset = alteredOffset;
+ }
+ }
+ positionAtCursorOffset(offset, false);
+ }
}
public ActionPopupWindow getActionPopupWindow() {
return mActionPopupWindow;
}
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean superResult = super.onTouchEvent(event);
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ // Reset the touch word offset when the user has lifted their finger.
+ mTouchWordOffset = 0;
+ }
+ return superResult;
+ }
}
private class SelectionEndHandleView extends HandleView {
+ // The previous offset this handle was at.
+ private int mPrevOffset;
+ // Indicates whether the cursor is making adjustments within a word.
+ private boolean mInWord = false;
+ // Offset to track difference between touch and word boundary.
+ protected int mTouchWordOffset;
public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
super(drawableLtr, drawableRtl);
@@ -3749,11 +3911,7 @@
@Override
protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
- if (isRtlRun) {
- return (drawable.getIntrinsicWidth() * 3) / 4;
- } else {
- return drawable.getIntrinsicWidth() / 4;
- }
+ return isRtlRun ? drawable.getIntrinsicWidth() : 0;
}
@Override
@@ -3775,20 +3933,72 @@
@Override
public void updatePosition(float x, float y) {
- int offset = mTextView.getOffsetForPosition(x, y);
+ final int trueOffset = mTextView.getOffsetForPosition(x, y);
+ final int currLine = mTextView.getLineAtCoordinate(y);
+ int offset = trueOffset;
+ boolean positionCursor = false;
- // Handles can not cross and selection is at least one character
- final int selectionStart = mTextView.getSelectionStart();
- if (offset <= selectionStart) {
- offset = Math.min(selectionStart + 1, mTextView.getText().length());
+ int end = getWordEnd(offset, true);
+ int start = getWordStart(offset);
+
+ if (offset > mPrevOffset) {
+ // User is increasing the selection.
+ if (!mInWord || currLine > mLine) {
+ // We're not in a word, or we're on a different line so we'll expand by
+ // word. First ensure the user has at least entered the next word.
+ int midPoint = Math.min((end - start) / 2, 2);
+ if (offset >= start + midPoint || currLine > mLine) {
+ offset = end;
+ } else {
+ offset = mPrevOffset;
+ }
+ }
+ mPrevOffset = offset;
+ mTouchWordOffset = offset - trueOffset;
+ mInWord = !isEndBoundary(offset);
+ positionCursor = true;
+ } else if (offset + mTouchWordOffset < mPrevOffset) {
+ // User is shrinking the selection.
+ if (currLine > mLine) {
+ // We're on a different line, so we'll snap to word boundaries.
+ offset = getWordStart(offset);
+ }
+ offset += mTouchWordOffset;
+ mPrevOffset = offset;
+ positionCursor = true;
+ mInWord = !isStartBoundary(offset);
}
- positionAtCursorOffset(offset, false);
+ if (positionCursor) {
+ final int selectionStart = mTextView.getSelectionStart();
+ if (offset <= selectionStart) {
+ // We can't cross the handles so let's just constrain the Y value.
+ int alteredOffset = mTextView.getOffsetAtCoordinate(mLine, x);
+ int length = mTextView.getText().length();
+ if (alteredOffset <= selectionStart) {
+ // Can't pass the other drag handle.
+ offset = Math.min(selectionStart + 1, length);
+ } else {
+ offset = Math.min(alteredOffset, length);
+ }
+ }
+ positionAtCursorOffset(offset, false);
+ }
}
public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) {
mActionPopupWindow = actionPopupWindow;
}
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean superResult = super.onTouchEvent(event);
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ // Reset the touch word offset when the user has lifted their finger.
+ mTouchWordOffset = 0;
+ }
+ return superResult;
+ }
}
/**
@@ -3871,6 +4081,11 @@
private float mDownPositionX, mDownPositionY;
private boolean mGestureStayedInTapRegion;
+ // Where the user first starts the drag motion.
+ private int mStartOffset = -1;
+ // Indicates whether the user is selecting text and using the drag accelerator.
+ private boolean mDragAcceleratorActive;
+
SelectionModifierCursorController() {
resetTouchOffsets();
}
@@ -3920,6 +4135,22 @@
if (mEndHandle != null) mEndHandle.hide();
}
+ public void enterDrag() {
+ // Just need to init the handles / hide insertion cursor.
+ show();
+ mDragAcceleratorActive = true;
+ // Start location of selection.
+ mStartOffset = mTextView.getOffsetForPosition(mLastDownPositionX,
+ mLastDownPositionY);
+ // Don't show the handles until user has lifted finger.
+ hide();
+
+ // This stops scrolling parents from intercepting the touch event, allowing
+ // the user to continue dragging across the screen to select text; TextView will
+ // scroll as necessary.
+ mTextView.getParent().requestDisallowInterceptTouchEvent(true);
+ }
+
public void onTouchEvent(MotionEvent event) {
// This is done even when the View does not have focus, so that long presses can start
// selection and tap can move cursor from this tap position.
@@ -3928,7 +4159,7 @@
final float x = event.getX();
final float y = 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 = mTextView.getOffsetForPosition(x, y);
// Double tap detection
@@ -3967,23 +4198,112 @@
break;
case MotionEvent.ACTION_MOVE:
+ final ViewConfiguration viewConfiguration = ViewConfiguration.get(
+ mTextView.getContext());
+
if (mGestureStayedInTapRegion) {
final float deltaX = event.getX() - mDownPositionX;
final float deltaY = event.getY() - mDownPositionY;
final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
- final ViewConfiguration viewConfiguration = ViewConfiguration.get(
- mTextView.getContext());
int doubleTapTouchSlop = viewConfiguration.getScaledDoubleTapTouchSlop();
if (distanceSquared > doubleTapTouchSlop * doubleTapTouchSlop) {
mGestureStayedInTapRegion = false;
}
}
+
+ if (mStartHandle != null && mStartHandle.isShowing()) {
+ // Don't do the drag if the handles are showing already.
+ break;
+ }
+
+ if (mStartOffset != -1) {
+ final int rawOffset = mTextView.getOffsetForPosition(event.getX(),
+ event.getY());
+ int offset = rawOffset;
+
+ // We don't start "dragging" until the user is past the initial word that
+ // gets selected on long press.
+ int firstWordStart = getWordStart(mStartOffset);
+ int firstWordEnd = getWordEnd(mStartOffset, false);
+ if (offset > firstWordEnd || offset < firstWordStart) {
+
+ // Basically the goal in the below code is to have the highlight be
+ // offset so that your finger isn't covering the end point.
+ int fingerOffset = viewConfiguration.getScaledTouchSlop();
+ float mx = event.getX();
+ float my = event.getY();
+ if (mx > fingerOffset) mx -= fingerOffset;
+ if (my > fingerOffset) my -= fingerOffset;
+ offset = mTextView.getOffsetForPosition(mx, my);
+
+ // Perform the check for closeness at edge of view, if we're very close
+ // don't adjust the offset to be in front of the finger - otherwise the
+ // user can't select words at the edge.
+ if (mTextView.getWidth() - fingerOffset > mx) {
+ // We're going by word, so we need to make sure that the offset
+ // that we get is within this, so we'll get the previous boundary.
+ final WordIterator wordIterator = getWordIteratorWithText();
+
+ final int precedingOffset = wordIterator.preceding(offset);
+ if (mStartOffset < offset) {
+ // Expanding with bottom handle, in this case the selection end
+ // is before the finger.
+ offset = Math.max(precedingOffset - 1, 0);
+ } else {
+ // Expand with the start handle, in this case the selection
+ // start is before the finger.
+ if (precedingOffset == WordIterator.DONE) {
+ offset = 0;
+ } else {
+ offset = wordIterator.preceding(precedingOffset);
+ }
+ }
+ }
+ if (offset == WordIterator.DONE)
+ offset = rawOffset;
+
+ // Need to adjust start offset based on direction of movement.
+ int newStart = mStartOffset < offset ? getWordStart(mStartOffset)
+ : getWordEnd(mStartOffset, true);
+ Selection.setSelection((Spannable) mTextView.getText(), newStart,
+ offset);
+ }
+ }
break;
case MotionEvent.ACTION_UP:
mPreviousTapUpTime = SystemClock.uptimeMillis();
+ if (mDragAcceleratorActive) {
+ // No longer dragging to select text, let the parent intercept events.
+ mTextView.getParent().requestDisallowInterceptTouchEvent(false);
+
+ show();
+ int startOffset = mTextView.getSelectionStart();
+ int endOffset = mTextView.getSelectionEnd();
+
+ // Since we don't let drag handles pass once they're visible, we need to
+ // make sure the start / end locations are correct because the user *can*
+ // switch directions during the initial drag.
+ if (endOffset < startOffset) {
+ int tmp = endOffset;
+ endOffset = startOffset;
+ startOffset = tmp;
+
+ // Also update the selection with the right offsets in this case.
+ Selection.setSelection((Spannable) mTextView.getText(),
+ startOffset, endOffset);
+ }
+
+ // Need to do this to display the handles.
+ mStartHandle.showAtLocation(startOffset);
+ mEndHandle.showAtLocation(endOffset);
+
+ // No longer the first dragging motion, reset.
+ mDragAcceleratorActive = false;
+ mStartOffset = -1;
+ }
break;
}
}
@@ -4010,6 +4330,8 @@
public void resetTouchOffsets() {
mMinTouchOffset = mMaxTouchOffset = -1;
+ mStartOffset = -1;
+ mDragAcceleratorActive = false;
}
/**
@@ -4019,6 +4341,13 @@
return mStartHandle != null && mStartHandle.isDragging();
}
+ /**
+ * @return true if the user is selecting text using the drag accelerator.
+ */
+ public boolean isDragAcceleratorActive() {
+ return mDragAcceleratorActive;
+ }
+
public void onTouchModeChanged(boolean isInTouchMode) {
if (!isInTouchMode) {
hide();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 632f5c7..9bef1fe 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8098,7 +8098,14 @@
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
- if (mEditor != null) mEditor.onTouchEvent(event);
+ if (mEditor != null) {
+ mEditor.onTouchEvent(event);
+
+ if (mEditor.mSelectionModifierCursorController != null &&
+ mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
+ return true;
+ }
+ }
final boolean superResult = super.onTouchEvent(event);
@@ -9104,7 +9111,7 @@
return getLayout().getLineForVertical((int) y);
}
- private int getOffsetAtCoordinate(int line, float x) {
+ int getOffsetAtCoordinate(int line, float x) {
x = convertToLocalHorizontalCoordinate(x);
return getLayout().getOffsetForHorizontal(line, x);
}
@@ -9726,4 +9733,4 @@
TextView.this.spanChange(buf, what, s, -1, e, -1);
}
}
-}
\ No newline at end of file
+}
diff --git a/core/res/res/drawable-hdpi/text_select_handle_left_mtrl_alpha.png b/core/res/res/drawable-hdpi/text_select_handle_left_mtrl_alpha.png
index 9cdc25b..1550b44 100644
--- a/core/res/res/drawable-hdpi/text_select_handle_left_mtrl_alpha.png
+++ b/core/res/res/drawable-hdpi/text_select_handle_left_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_right_mtrl_alpha.png b/core/res/res/drawable-hdpi/text_select_handle_right_mtrl_alpha.png
index 276d480..b309dfd 100644
--- a/core/res/res/drawable-hdpi/text_select_handle_right_mtrl_alpha.png
+++ b/core/res/res/drawable-hdpi/text_select_handle_right_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_select_handle_left_mtrl_alpha.png b/core/res/res/drawable-mdpi/text_select_handle_left_mtrl_alpha.png
index 95c0168..b36a413 100644
--- a/core/res/res/drawable-mdpi/text_select_handle_left_mtrl_alpha.png
+++ b/core/res/res/drawable-mdpi/text_select_handle_left_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_select_handle_right_mtrl_alpha.png b/core/res/res/drawable-mdpi/text_select_handle_right_mtrl_alpha.png
index 569332a..afd0bd2 100644
--- a/core/res/res/drawable-mdpi/text_select_handle_right_mtrl_alpha.png
+++ b/core/res/res/drawable-mdpi/text_select_handle_right_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/text_select_handle_left_mtrl_alpha.png b/core/res/res/drawable-xhdpi/text_select_handle_left_mtrl_alpha.png
index a01ac10..58f8c43 100644
--- a/core/res/res/drawable-xhdpi/text_select_handle_left_mtrl_alpha.png
+++ b/core/res/res/drawable-xhdpi/text_select_handle_left_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/text_select_handle_right_mtrl_alpha.png b/core/res/res/drawable-xhdpi/text_select_handle_right_mtrl_alpha.png
index d3602d9..42a893d 100644
--- a/core/res/res/drawable-xhdpi/text_select_handle_right_mtrl_alpha.png
+++ b/core/res/res/drawable-xhdpi/text_select_handle_right_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/text_select_handle_left_mtrl_alpha.png b/core/res/res/drawable-xxhdpi/text_select_handle_left_mtrl_alpha.png
index 75085ce..d0f274a 100644
--- a/core/res/res/drawable-xxhdpi/text_select_handle_left_mtrl_alpha.png
+++ b/core/res/res/drawable-xxhdpi/text_select_handle_left_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/text_select_handle_right_mtrl_alpha.png b/core/res/res/drawable-xxhdpi/text_select_handle_right_mtrl_alpha.png
index e2eb5be..f1f637a 100644
--- a/core/res/res/drawable-xxhdpi/text_select_handle_right_mtrl_alpha.png
+++ b/core/res/res/drawable-xxhdpi/text_select_handle_right_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/text_select_handle_left_mtrl_alpha.png b/core/res/res/drawable-xxxhdpi/text_select_handle_left_mtrl_alpha.png
new file mode 100644
index 0000000..643168f
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/text_select_handle_left_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/text_select_handle_right_mtrl_alpha.png b/core/res/res/drawable-xxxhdpi/text_select_handle_right_mtrl_alpha.png
new file mode 100644
index 0000000..e8f3aad
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/text_select_handle_right_mtrl_alpha.png
Binary files differ