summaryrefslogtreecommitdiff
path: root/android/widget
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-10-10 15:20:13 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-10-10 15:20:13 -0400
commit93b7ee4fce01df52a6607f0b1965cbafdfeaf1a6 (patch)
tree49f76f879a89c256a4f65b674086be50760bdffb /android/widget
parentbc81c7ada5aab3806dd0b17498f5c9672c9b33c4 (diff)
downloadandroid-28-93b7ee4fce01df52a6607f0b1965cbafdfeaf1a6.tar.gz
Import Android SDK Platform P [4386628]
/google/data/ro/projects/android/fetch_artifact \ --bid 4386628 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4386628.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: I9b8400ac92116cae4f033d173f7a5682b26ccba9
Diffstat (limited to 'android/widget')
-rw-r--r--android/widget/EditTextBackspacePerfTest.java19
-rw-r--r--android/widget/EditTextCursorMovementPerfTest.java19
-rw-r--r--android/widget/Editor.java153
-rw-r--r--android/widget/PopupWindow.java11
-rw-r--r--android/widget/RemoteViews.java551
-rw-r--r--android/widget/SelectionActionModeHelper.java164
-rw-r--r--android/widget/SmartSelectSprite.java192
-rw-r--r--android/widget/Switch.java5
-rw-r--r--android/widget/TabWidget.java32
-rw-r--r--android/widget/TextView.java14
-rw-r--r--android/widget/TextViewSetTextLocalePerfTest.java20
11 files changed, 627 insertions, 553 deletions
diff --git a/android/widget/EditTextBackspacePerfTest.java b/android/widget/EditTextBackspacePerfTest.java
index 40b56f4a..d219d3a2 100644
--- a/android/widget/EditTextBackspacePerfTest.java
+++ b/android/widget/EditTextBackspacePerfTest.java
@@ -16,31 +16,26 @@
package android.widget;
-import android.app.Activity;
-import android.os.Bundle;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
@LargeTest
@RunWith(Parameterized.class)
diff --git a/android/widget/EditTextCursorMovementPerfTest.java b/android/widget/EditTextCursorMovementPerfTest.java
index b100acba..b6cf7d3f 100644
--- a/android/widget/EditTextCursorMovementPerfTest.java
+++ b/android/widget/EditTextCursorMovementPerfTest.java
@@ -16,31 +16,26 @@
package android.widget;
-import android.app.Activity;
-import android.os.Bundle;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
@LargeTest
@RunWith(Parameterized.class)
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index 0f617242..afd11881 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -119,6 +119,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.EditableInputConnection;
+import com.android.internal.widget.Magnifier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -138,6 +139,9 @@ import java.util.List;
public class Editor {
private static final String TAG = "Editor";
private static final boolean DEBUG_UNDO = false;
+ // Specifies whether to use or not the magnifier when pressing the insertion or selection
+ // handles.
+ private static final boolean FLAG_USE_MAGNIFIER = true;
static final int BLINK = 500;
private static final int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
@@ -161,6 +165,17 @@ public class Editor {
private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
+ private static final float MAGNIFIER_ZOOM = 1.5f;
+ @IntDef({MagnifierHandleTrigger.SELECTION_START,
+ MagnifierHandleTrigger.SELECTION_END,
+ MagnifierHandleTrigger.INSERTION})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface MagnifierHandleTrigger {
+ int INSERTION = 0;
+ int SELECTION_START = 1;
+ int SELECTION_END = 2;
+ }
+
// Each Editor manages its own undo stack.
private final UndoManager mUndoManager = new UndoManager();
private UndoOwner mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this);
@@ -179,6 +194,8 @@ public class Editor {
private final boolean mHapticTextHandleEnabled;
+ private final Magnifier mMagnifier;
+
// Used to highlight a word when it is corrected by the IME
private CorrectionHighlighter mCorrectionHighlighter;
@@ -250,7 +267,7 @@ public class Editor {
SuggestionRangeSpan mSuggestionRangeSpan;
private Runnable mShowSuggestionRunnable;
- Drawable mCursorDrawable = null;
+ Drawable mDrawableForCursor = null;
private Drawable mSelectHandleLeft;
private Drawable mSelectHandleRight;
@@ -325,6 +342,8 @@ public class Editor {
mProcessTextIntentActionsHandler = new ProcessTextIntentActionsHandler(this);
mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableHapticTextHandle);
+
+ mMagnifier = FLAG_USE_MAGNIFIER ? new Magnifier(mTextView) : null;
}
ParcelableParcel saveInstanceState() {
@@ -1678,7 +1697,7 @@ public class Editor {
mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
}
- if (highlight != null && selectionStart == selectionEnd && mCursorDrawable != null) {
+ if (highlight != null && selectionStart == selectionEnd && mDrawableForCursor != null) {
drawCursor(canvas, cursorOffsetVertical);
// Rely on the drawable entirely, do not draw the cursor line.
// Has to be done after the IMM related code above which relies on the highlight.
@@ -1873,8 +1892,8 @@ public class Editor {
private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
final boolean translate = cursorOffsetVertical != 0;
if (translate) canvas.translate(0, cursorOffsetVertical);
- if (mCursorDrawable != null) {
- mCursorDrawable.draw(canvas);
+ if (mDrawableForCursor != null) {
+ mDrawableForCursor.draw(canvas);
}
if (translate) canvas.translate(0, -cursorOffsetVertical);
}
@@ -1933,7 +1952,7 @@ public class Editor {
void updateCursorPosition() {
if (mTextView.mCursorDrawableRes == 0) {
- mCursorDrawable = null;
+ mDrawableForCursor = null;
return;
}
@@ -2314,17 +2333,17 @@ public class Editor {
@VisibleForTesting
@Nullable
public Drawable getCursorDrawable() {
- return mCursorDrawable;
+ return mDrawableForCursor;
}
private void updateCursorPosition(int top, int bottom, float horizontal) {
- if (mCursorDrawable == null) {
- mCursorDrawable = mTextView.getContext().getDrawable(
+ if (mDrawableForCursor == null) {
+ mDrawableForCursor = mTextView.getContext().getDrawable(
mTextView.mCursorDrawableRes);
}
- final int left = clampHorizontalPosition(mCursorDrawable, horizontal);
- final int width = mCursorDrawable.getIntrinsicWidth();
- mCursorDrawable.setBounds(left, top - mTempRect.top, left + width,
+ final int left = clampHorizontalPosition(mDrawableForCursor, horizontal);
+ final int width = mDrawableForCursor.getIntrinsicWidth();
+ mDrawableForCursor.setBounds(left, top - mTempRect.top, left + width,
bottom + mTempRect.bottom);
}
@@ -4353,6 +4372,9 @@ public class Editor {
protected abstract void updatePosition(float x, float y, boolean fromTouchScreen);
+ @MagnifierHandleTrigger
+ protected abstract int getMagnifierHandleTrigger();
+
protected boolean isAtRtlRun(@NonNull Layout layout, int offset) {
return layout.isRtlCharAt(offset);
}
@@ -4490,6 +4512,53 @@ public class Editor {
return 0;
}
+ protected final void showMagnifier() {
+ if (mMagnifier == null) {
+ return;
+ }
+
+ final int trigger = getMagnifierHandleTrigger();
+ final int offset;
+ switch (trigger) {
+ case MagnifierHandleTrigger.INSERTION: // Fall through.
+ case MagnifierHandleTrigger.SELECTION_START:
+ offset = mTextView.getSelectionStart();
+ break;
+ case MagnifierHandleTrigger.SELECTION_END:
+ offset = mTextView.getSelectionEnd();
+ break;
+ default:
+ offset = -1;
+ break;
+ }
+
+ if (offset == -1) {
+ dismissMagnifier();
+ }
+
+ final Layout layout = mTextView.getLayout();
+ final int lineNumber = layout.getLineForOffset(offset);
+ // Horizontally snap to character offset.
+ final float xPosInView = getHorizontal(mTextView.getLayout(), offset);
+ // Vertically snap to middle of current line.
+ final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber)
+ + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f;
+ final int[] coordinatesOnScreen = new int[2];
+ mTextView.getLocationOnScreen(coordinatesOnScreen);
+ final float centerXOnScreen = xPosInView + mTextView.getTotalPaddingLeft()
+ - mTextView.getScrollX() + coordinatesOnScreen[0];
+ final float centerYOnScreen = yPosInView + mTextView.getTotalPaddingTop()
+ - mTextView.getScrollY() + coordinatesOnScreen[1];
+
+ mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM);
+ }
+
+ protected final void dismissMagnifier() {
+ if (mMagnifier != null) {
+ mMagnifier.dismiss();
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent ev) {
updateFloatingToolbarVisibility(ev);
@@ -4542,10 +4611,7 @@ public class Editor {
case MotionEvent.ACTION_UP:
filterOnTouchUp(ev.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
- mIsDragging = false;
- updateDrawable();
- break;
-
+ // Fall through.
case MotionEvent.ACTION_CANCEL:
mIsDragging = false;
updateDrawable();
@@ -4646,9 +4712,9 @@ public class Editor {
@Override
protected int getCursorOffset() {
int offset = super.getCursorOffset();
- if (mCursorDrawable != null) {
- mCursorDrawable.getPadding(mTempRect);
- offset += (mCursorDrawable.getIntrinsicWidth()
+ if (mDrawableForCursor != null) {
+ mDrawableForCursor.getPadding(mTempRect);
+ offset += (mDrawableForCursor.getIntrinsicWidth()
- mTempRect.left - mTempRect.right) / 2;
}
return offset;
@@ -4656,9 +4722,9 @@ public class Editor {
@Override
int getCursorHorizontalPosition(Layout layout, int offset) {
- if (mCursorDrawable != null) {
+ if (mDrawableForCursor != null) {
final float horizontal = getHorizontal(layout, offset);
- return clampHorizontalPosition(mCursorDrawable, horizontal) + mTempRect.left;
+ return clampHorizontalPosition(mDrawableForCursor, horizontal) + mTempRect.left;
}
return super.getCursorHorizontalPosition(layout, offset);
}
@@ -4671,6 +4737,11 @@ public class Editor {
case MotionEvent.ACTION_DOWN:
mDownPositionX = ev.getRawX();
mDownPositionY = ev.getRawY();
+ showMagnifier();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ showMagnifier();
break;
case MotionEvent.ACTION_UP:
@@ -4696,11 +4767,10 @@ public class Editor {
mTextActionMode.invalidateContentRect();
}
}
- hideAfterDelay();
- break;
-
+ // Fall through.
case MotionEvent.ACTION_CANCEL:
hideAfterDelay();
+ dismissMagnifier();
break;
default:
@@ -4751,6 +4821,12 @@ public class Editor {
super.onDetached();
removeHiderCallback();
}
+
+ @Override
+ @MagnifierHandleTrigger
+ protected int getMagnifierHandleTrigger() {
+ return MagnifierHandleTrigger.INSERTION;
+ }
}
@Retention(RetentionPolicy.SOURCE)
@@ -5009,12 +5085,26 @@ public class Editor {
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean superResult = super.onTouchEvent(event);
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- // Reset the touch word offset and x value when the user
- // re-engages the handle.
- mTouchWordDelta = 0.0f;
- mPrevX = UNSET_X_VALUE;
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ // Reset the touch word offset and x value when the user
+ // re-engages the handle.
+ mTouchWordDelta = 0.0f;
+ mPrevX = UNSET_X_VALUE;
+ showMagnifier();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ showMagnifier();
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ dismissMagnifier();
+ break;
}
+
return superResult;
}
@@ -5110,6 +5200,13 @@ public class Editor {
return isRtlChar == isRtlParagraph ? primaryOffset : secondaryOffset;
}
}
+
+ @MagnifierHandleTrigger
+ protected int getMagnifierHandleTrigger() {
+ return isStartHandle()
+ ? MagnifierHandleTrigger.SELECTION_START
+ : MagnifierHandleTrigger.SELECTION_END;
+ }
}
private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) {
diff --git a/android/widget/PopupWindow.java b/android/widget/PopupWindow.java
index bf25915d..23ebadb3 100644
--- a/android/widget/PopupWindow.java
+++ b/android/widget/PopupWindow.java
@@ -2296,8 +2296,8 @@ public class PopupWindow {
}
/** @hide */
- protected final void detachFromAnchor() {
- final View anchor = mAnchor != null ? mAnchor.get() : null;
+ protected void detachFromAnchor() {
+ final View anchor = getAnchor();
if (anchor != null) {
final ViewTreeObserver vto = anchor.getViewTreeObserver();
vto.removeOnScrollChangedListener(mOnScrollChangedListener);
@@ -2316,7 +2316,7 @@ public class PopupWindow {
}
/** @hide */
- protected final void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+ protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
detachFromAnchor();
final ViewTreeObserver vto = anchor.getViewTreeObserver();
@@ -2339,6 +2339,11 @@ public class PopupWindow {
mAnchoredGravity = gravity;
}
+ /** @hide */
+ protected @Nullable View getAnchor() {
+ return mAnchor != null ? mAnchor.get() : null;
+ }
+
private void alignToAnchor() {
final View anchor = mAnchor != null ? mAnchor.get() : null;
if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) {
diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java
index bc85fadb..1b26f8e2 100644
--- a/android/widget/RemoteViews.java
+++ b/android/widget/RemoteViews.java
@@ -16,9 +16,11 @@
package android.widget;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
import android.annotation.ColorInt;
import android.annotation.DimenRes;
-import android.app.ActivityManager.StackId;
+import android.annotation.NonNull;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.Application;
@@ -107,9 +109,9 @@ public class RemoteViews implements Parcelable, Filter {
// The unique identifiers for each custom {@link Action}.
private static final int SET_ON_CLICK_PENDING_INTENT_TAG = 1;
private static final int REFLECTION_ACTION_TAG = 2;
- private static final int SET_DRAWABLE_PARAMETERS_TAG = 3;
+ private static final int SET_DRAWABLE_TINT_TAG = 3;
private static final int VIEW_GROUP_ACTION_ADD_TAG = 4;
- private static final int SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG = 5;
+ private static final int VIEW_CONTENT_NAVIGATION_TAG = 5;
private static final int SET_EMPTY_VIEW_ACTION_TAG = 6;
private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7;
private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8;
@@ -120,7 +122,6 @@ public class RemoteViews implements Parcelable, Filter {
private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13;
private static final int VIEW_PADDING_ACTION_TAG = 14;
private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15;
- private static final int TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG = 17;
private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18;
private static final int LAYOUT_PARAM_ACTION_TAG = 19;
private static final int OVERRIDE_TEXT_COLORS_TAG = 20;
@@ -324,11 +325,11 @@ public class RemoteViews implements Parcelable, Filter {
public boolean onClickHandler(View view, PendingIntent pendingIntent,
Intent fillInIntent) {
- return onClickHandler(view, pendingIntent, fillInIntent, StackId.INVALID_STACK_ID);
+ return onClickHandler(view, pendingIntent, fillInIntent, WINDOWING_MODE_UNDEFINED);
}
public boolean onClickHandler(View view, PendingIntent pendingIntent,
- Intent fillInIntent, int launchStackId) {
+ Intent fillInIntent, int windowingMode) {
try {
// TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
Context context = view.getContext();
@@ -339,8 +340,8 @@ public class RemoteViews implements Parcelable, Filter {
opts = ActivityOptions.makeBasic();
}
- if (launchStackId != StackId.INVALID_STACK_ID) {
- opts.setLaunchStackId(launchStackId);
+ if (windowingMode != WINDOWING_MODE_UNDEFINED) {
+ opts.setLaunchWindowingMode(windowingMode);
}
context.startIntentSender(
pendingIntent.getIntentSender(), fillInIntent,
@@ -388,10 +389,10 @@ public class RemoteViews implements Parcelable, Filter {
return MERGE_REPLACE;
}
- public abstract String getActionName();
+ public abstract int getActionTag();
public String getUniqueKey() {
- return (getActionName() + viewId);
+ return (getActionTag() + "_" + viewId);
}
/**
@@ -423,8 +424,8 @@ public class RemoteViews implements Parcelable, Filter {
*/
private static abstract class RuntimeAction extends Action {
@Override
- public final String getActionName() {
- return "RuntimeAction";
+ public final int getActionTag() {
+ return 0;
}
@Override
@@ -513,7 +514,6 @@ public class RemoteViews implements Parcelable, Filter {
}
private class SetEmptyView extends Action {
- int viewId;
int emptyViewId;
SetEmptyView(int viewId, int emptyViewId) {
@@ -527,7 +527,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(SET_EMPTY_VIEW_ACTION_TAG);
out.writeInt(this.viewId);
out.writeInt(this.emptyViewId);
}
@@ -545,8 +544,9 @@ public class RemoteViews implements Parcelable, Filter {
adapterView.setEmptyView(emptyView);
}
- public String getActionName() {
- return "SetEmptyView";
+ @Override
+ public int getActionTag() {
+ return SET_EMPTY_VIEW_ACTION_TAG;
}
}
@@ -558,13 +558,12 @@ public class RemoteViews implements Parcelable, Filter {
public SetOnClickFillInIntent(Parcel parcel) {
viewId = parcel.readInt();
- fillInIntent = Intent.CREATOR.createFromParcel(parcel);
+ fillInIntent = parcel.readTypedObject(Intent.CREATOR);
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_ON_CLICK_FILL_IN_INTENT_TAG);
dest.writeInt(viewId);
- fillInIntent.writeToParcel(dest, 0 /* no flags */);
+ dest.writeTypedObject(fillInIntent, 0 /* no flags */);
}
@Override
@@ -623,8 +622,9 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- public String getActionName() {
- return "SetOnClickFillInIntent";
+ @Override
+ public int getActionTag() {
+ return SET_ON_CLICK_FILL_IN_INTENT_TAG;
}
Intent fillInIntent;
@@ -642,9 +642,8 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_PENDING_INTENT_TEMPLATE_TAG);
dest.writeInt(viewId);
- pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */);
+ PendingIntent.writePendingIntentOrNullToParcel(pendingIntentTemplate, dest);
}
@Override
@@ -698,8 +697,9 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- public String getActionName() {
- return "SetPendingIntentTemplate";
+ @Override
+ public int getActionTag() {
+ return SET_PENDING_INTENT_TEMPLATE_TAG;
}
PendingIntent pendingIntentTemplate;
@@ -715,30 +715,13 @@ public class RemoteViews implements Parcelable, Filter {
public SetRemoteViewsAdapterList(Parcel parcel) {
viewId = parcel.readInt();
viewTypeCount = parcel.readInt();
- int count = parcel.readInt();
- list = new ArrayList<RemoteViews>();
-
- for (int i = 0; i < count; i++) {
- RemoteViews rv = RemoteViews.CREATOR.createFromParcel(parcel);
- list.add(rv);
- }
+ list = parcel.createTypedArrayList(RemoteViews.CREATOR);
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_REMOTE_VIEW_ADAPTER_LIST_TAG);
dest.writeInt(viewId);
dest.writeInt(viewTypeCount);
-
- if (list == null || list.size() == 0) {
- dest.writeInt(0);
- } else {
- int count = list.size();
- dest.writeInt(count);
- for (int i = 0; i < count; i++) {
- RemoteViews rv = list.get(i);
- rv.writeToParcel(dest, flags);
- }
- }
+ dest.writeTypedList(list, flags);
}
@Override
@@ -778,8 +761,9 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- public String getActionName() {
- return "SetRemoteViewsAdapterList";
+ @Override
+ public int getActionTag() {
+ return SET_REMOTE_VIEW_ADAPTER_LIST_TAG;
}
int viewTypeCount;
@@ -794,13 +778,12 @@ public class RemoteViews implements Parcelable, Filter {
public SetRemoteViewsAdapterIntent(Parcel parcel) {
viewId = parcel.readInt();
- intent = Intent.CREATOR.createFromParcel(parcel);
+ intent = parcel.readTypedObject(Intent.CREATOR);
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_REMOTE_VIEW_ADAPTER_INTENT_TAG);
dest.writeInt(viewId);
- intent.writeToParcel(dest, flags);
+ dest.writeTypedObject(intent, flags);
}
@Override
@@ -844,8 +827,9 @@ public class RemoteViews implements Parcelable, Filter {
return copy;
}
- public String getActionName() {
- return "SetRemoteViewsAdapterIntent";
+ @Override
+ public int getActionTag() {
+ return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
}
Intent intent;
@@ -865,22 +849,12 @@ public class RemoteViews implements Parcelable, Filter {
public SetOnClickPendingIntent(Parcel parcel) {
viewId = parcel.readInt();
-
- // We check a flag to determine if the parcel contains a PendingIntent.
- if (parcel.readInt() != 0) {
- pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
- }
+ pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_ON_CLICK_PENDING_INTENT_TAG);
dest.writeInt(viewId);
-
- // We use a flag to indicate whether the parcel contains a valid object.
- dest.writeInt(pendingIntent != null ? 1 : 0);
- if (pendingIntent != null) {
- pendingIntent.writeToParcel(dest, 0 /* no flags */);
- }
+ PendingIntent.writePendingIntentOrNullToParcel(pendingIntent, dest);
}
@Override
@@ -921,8 +895,9 @@ public class RemoteViews implements Parcelable, Filter {
target.setOnClickListener(listener);
}
- public String getActionName() {
- return "SetOnClickPendingIntent";
+ @Override
+ public int getActionTag() {
+ return SET_ON_CLICK_PENDING_INTENT_TAG;
}
PendingIntent pendingIntent;
@@ -1011,55 +986,37 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
- * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+ * Equivalent to calling
* {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
- * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
+ * on the {@link Drawable} of a given view.
* <p>
- * These operations will be performed on the {@link Drawable} returned by the
+ * The operation will be performed on the {@link Drawable} returned by the
* target {@link View#getBackground()} by default. If targetBackground is false,
* we assume the target is an {@link ImageView} and try applying the operations
* to {@link ImageView#getDrawable()}.
* <p>
- * You can omit specific calls by marking their values with null or -1.
*/
- private class SetDrawableParameters extends Action {
- public SetDrawableParameters(int id, boolean targetBackground, int alpha,
- int colorFilter, PorterDuff.Mode mode, int level) {
+ private class SetDrawableTint extends Action {
+ SetDrawableTint(int id, boolean targetBackground,
+ int colorFilter, @NonNull PorterDuff.Mode mode) {
this.viewId = id;
this.targetBackground = targetBackground;
- this.alpha = alpha;
this.colorFilter = colorFilter;
this.filterMode = mode;
- this.level = level;
}
- public SetDrawableParameters(Parcel parcel) {
+ SetDrawableTint(Parcel parcel) {
viewId = parcel.readInt();
targetBackground = parcel.readInt() != 0;
- alpha = parcel.readInt();
colorFilter = parcel.readInt();
- boolean hasMode = parcel.readInt() != 0;
- if (hasMode) {
- filterMode = PorterDuff.Mode.valueOf(parcel.readString());
- } else {
- filterMode = null;
- }
- level = parcel.readInt();
+ filterMode = PorterDuff.intToMode(parcel.readInt());
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_DRAWABLE_PARAMETERS_TAG);
dest.writeInt(viewId);
dest.writeInt(targetBackground ? 1 : 0);
- dest.writeInt(alpha);
dest.writeInt(colorFilter);
- if (filterMode != null) {
- dest.writeInt(1);
- dest.writeString(filterMode.toString());
- } else {
- dest.writeInt(0);
- }
- dest.writeInt(level);
+ dest.writeInt(PorterDuff.modeToInt(filterMode));
}
@Override
@@ -1077,47 +1034,36 @@ public class RemoteViews implements Parcelable, Filter {
}
if (targetDrawable != null) {
- // Perform modifications only if values are set correctly
- if (alpha != -1) {
- targetDrawable.mutate().setAlpha(alpha);
- }
- if (filterMode != null) {
- targetDrawable.mutate().setColorFilter(colorFilter, filterMode);
- }
- if (level != -1) {
- targetDrawable.mutate().setLevel(level);
- }
+ targetDrawable.mutate().setColorFilter(colorFilter, filterMode);
}
}
- public String getActionName() {
- return "SetDrawableParameters";
+ @Override
+ public int getActionTag() {
+ return SET_DRAWABLE_TINT_TAG;
}
boolean targetBackground;
- int alpha;
int colorFilter;
PorterDuff.Mode filterMode;
- int level;
}
- private final class ReflectionActionWithoutParams extends Action {
- final String methodName;
+ private final class ViewContentNavigation extends Action {
+ final boolean mNext;
- ReflectionActionWithoutParams(int viewId, String methodName) {
+ ViewContentNavigation(int viewId, boolean next) {
this.viewId = viewId;
- this.methodName = methodName;
+ this.mNext = next;
}
- ReflectionActionWithoutParams(Parcel in) {
+ ViewContentNavigation(Parcel in) {
this.viewId = in.readInt();
- this.methodName = in.readString();
+ this.mNext = in.readBoolean();
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG);
out.writeInt(this.viewId);
- out.writeString(this.methodName);
+ out.writeBoolean(this.mNext);
}
@Override
@@ -1126,23 +1072,20 @@ public class RemoteViews implements Parcelable, Filter {
if (view == null) return;
try {
- getMethod(view, this.methodName, null, false /* async */).invoke(view);
+ getMethod(view,
+ mNext ? "showNext" : "showPrevious", null, false /* async */).invoke(view);
} catch (Throwable ex) {
throw new ActionException(ex);
}
}
public int mergeBehavior() {
- // we don't need to build up showNext or showPrevious calls
- if (methodName.equals("showNext") || methodName.equals("showPrevious")) {
- return MERGE_IGNORE;
- } else {
- return MERGE_REPLACE;
- }
+ return MERGE_IGNORE;
}
- public String getActionName() {
- return "ReflectionActionWithoutParams";
+ @Override
+ public int getActionTag() {
+ return VIEW_CONTENT_NAVIGATION_TAG;
}
}
@@ -1156,12 +1099,7 @@ public class RemoteViews implements Parcelable, Filter {
}
public BitmapCache(Parcel source) {
- int count = source.readInt();
- mBitmaps = new ArrayList<>(count);
- for (int i = 0; i < count; i++) {
- Bitmap b = Bitmap.CREATOR.createFromParcel(source);
- mBitmaps.add(b);
- }
+ mBitmaps = source.createTypedArrayList(Bitmap.CREATOR);
}
public int getBitmapId(Bitmap b) {
@@ -1187,11 +1125,7 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeBitmapsToParcel(Parcel dest, int flags) {
- int count = mBitmaps.size();
- dest.writeInt(count);
- for (int i = 0; i < count; i++) {
- mBitmaps.get(i).writeToParcel(dest, flags);
- }
+ dest.writeTypedList(mBitmaps, flags);
}
public int getBitmapMemory() {
@@ -1227,7 +1161,6 @@ public class RemoteViews implements Parcelable, Filter {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(BITMAP_REFLECTION_ACTION_TAG);
dest.writeInt(viewId);
dest.writeString(methodName);
dest.writeInt(bitmapId);
@@ -1246,8 +1179,9 @@ public class RemoteViews implements Parcelable, Filter {
bitmapId = bitmapCache.getBitmapId(bitmap);
}
- public String getActionName() {
- return "BitmapReflectionAction";
+ @Override
+ public int getActionTag() {
+ return BITMAP_REFLECTION_ACTION_TAG;
}
}
@@ -1299,7 +1233,7 @@ public class RemoteViews implements Parcelable, Filter {
// written to the parcel.
switch (this.type) {
case BOOLEAN:
- this.value = in.readInt() != 0;
+ this.value = in.readBoolean();
break;
case BYTE:
this.value = in.readByte();
@@ -1329,39 +1263,28 @@ public class RemoteViews implements Parcelable, Filter {
this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
break;
case URI:
- if (in.readInt() != 0) {
- this.value = Uri.CREATOR.createFromParcel(in);
- }
+ this.value = in.readTypedObject(Uri.CREATOR);
break;
case BITMAP:
- if (in.readInt() != 0) {
- this.value = Bitmap.CREATOR.createFromParcel(in);
- }
+ this.value = in.readTypedObject(Bitmap.CREATOR);
break;
case BUNDLE:
this.value = in.readBundle();
break;
case INTENT:
- if (in.readInt() != 0) {
- this.value = Intent.CREATOR.createFromParcel(in);
- }
+ this.value = in.readTypedObject(Intent.CREATOR);
break;
case COLOR_STATE_LIST:
- if (in.readInt() != 0) {
- this.value = ColorStateList.CREATOR.createFromParcel(in);
- }
+ this.value = in.readTypedObject(ColorStateList.CREATOR);
break;
case ICON:
- if (in.readInt() != 0) {
- this.value = Icon.CREATOR.createFromParcel(in);
- }
+ this.value = in.readTypedObject(Icon.CREATOR);
default:
break;
}
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(REFLECTION_ACTION_TAG);
out.writeInt(this.viewId);
out.writeString(this.methodName);
out.writeInt(this.type);
@@ -1375,7 +1298,7 @@ public class RemoteViews implements Parcelable, Filter {
// we have written a valid value to the parcel.
switch (this.type) {
case BOOLEAN:
- out.writeInt((Boolean) this.value ? 1 : 0);
+ out.writeBoolean((Boolean) this.value);
break;
case BYTE:
out.writeByte((Byte) this.value);
@@ -1412,10 +1335,7 @@ public class RemoteViews implements Parcelable, Filter {
case INTENT:
case COLOR_STATE_LIST:
case ICON:
- out.writeInt(this.value != null ? 1 : 0);
- if (this.value != null) {
- ((Parcelable) this.value).writeToParcel(out, flags);
- }
+ out.writeTypedObject((Parcelable) this.value, flags);
break;
default:
break;
@@ -1521,10 +1441,16 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- public String getActionName() {
+ @Override
+ public int getActionTag() {
+ return REFLECTION_ACTION_TAG;
+ }
+
+ @Override
+ public String getUniqueKey() {
// Each type of reflection action corresponds to a setter, so each should be seen as
// unique from the standpoint of merging.
- return "ReflectionAction" + this.methodName + this.type;
+ return super.getUniqueKey() + this.methodName + this.type;
}
@Override
@@ -1586,7 +1512,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(VIEW_GROUP_ACTION_ADD_TAG);
dest.writeInt(viewId);
dest.writeInt(mIndex);
mNestedViews.writeToParcel(dest, flags);
@@ -1661,10 +1586,9 @@ public class RemoteViews implements Parcelable, Filter {
return mNestedViews.prefersAsyncApply();
}
-
@Override
- public String getActionName() {
- return "ViewGroupActionAdd";
+ public int getActionTag() {
+ return VIEW_GROUP_ACTION_ADD_TAG;
}
}
@@ -1696,7 +1620,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(VIEW_GROUP_ACTION_REMOVE_TAG);
dest.writeInt(viewId);
dest.writeInt(mViewIdToKeep);
}
@@ -1762,8 +1685,8 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public String getActionName() {
- return "ViewGroupActionRemove";
+ public int getActionTag() {
+ return VIEW_GROUP_ACTION_REMOVE_TAG;
}
@Override
@@ -1803,18 +1726,10 @@ public class RemoteViews implements Parcelable, Filter {
isRelative = (parcel.readInt() != 0);
useIcons = (parcel.readInt() != 0);
if (useIcons) {
- if (parcel.readInt() != 0) {
- i1 = Icon.CREATOR.createFromParcel(parcel);
- }
- if (parcel.readInt() != 0) {
- i2 = Icon.CREATOR.createFromParcel(parcel);
- }
- if (parcel.readInt() != 0) {
- i3 = Icon.CREATOR.createFromParcel(parcel);
- }
- if (parcel.readInt() != 0) {
- i4 = Icon.CREATOR.createFromParcel(parcel);
- }
+ i1 = parcel.readTypedObject(Icon.CREATOR);
+ i2 = parcel.readTypedObject(Icon.CREATOR);
+ i3 = parcel.readTypedObject(Icon.CREATOR);
+ i4 = parcel.readTypedObject(Icon.CREATOR);
} else {
d1 = parcel.readInt();
d2 = parcel.readInt();
@@ -1824,35 +1739,14 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TEXT_VIEW_DRAWABLE_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(isRelative ? 1 : 0);
dest.writeInt(useIcons ? 1 : 0);
if (useIcons) {
- if (i1 != null) {
- dest.writeInt(1);
- i1.writeToParcel(dest, 0);
- } else {
- dest.writeInt(0);
- }
- if (i2 != null) {
- dest.writeInt(1);
- i2.writeToParcel(dest, 0);
- } else {
- dest.writeInt(0);
- }
- if (i3 != null) {
- dest.writeInt(1);
- i3.writeToParcel(dest, 0);
- } else {
- dest.writeInt(0);
- }
- if (i4 != null) {
- dest.writeInt(1);
- i4.writeToParcel(dest, 0);
- } else {
- dest.writeInt(0);
- }
+ dest.writeTypedObject(i1, 0);
+ dest.writeTypedObject(i2, 0);
+ dest.writeTypedObject(i3, 0);
+ dest.writeTypedObject(i4, 0);
} else {
dest.writeInt(d1);
dest.writeInt(d2);
@@ -1923,8 +1817,9 @@ public class RemoteViews implements Parcelable, Filter {
return useIcons;
}
- public String getActionName() {
- return "TextViewDrawableAction";
+ @Override
+ public int getActionTag() {
+ return TEXT_VIEW_DRAWABLE_ACTION_TAG;
}
boolean isRelative = false;
@@ -1953,7 +1848,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TEXT_VIEW_SIZE_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(units);
dest.writeFloat(size);
@@ -1966,8 +1860,9 @@ public class RemoteViews implements Parcelable, Filter {
target.setTextSize(units, size);
}
- public String getActionName() {
- return "TextViewSizeAction";
+ @Override
+ public int getActionTag() {
+ return TEXT_VIEW_SIZE_ACTION_TAG;
}
int units;
@@ -1995,7 +1890,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(VIEW_PADDING_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(left);
dest.writeInt(top);
@@ -2010,8 +1904,9 @@ public class RemoteViews implements Parcelable, Filter {
target.setPadding(left, top, right, bottom);
}
- public String getActionName() {
- return "ViewPaddingAction";
+ @Override
+ public int getActionTag() {
+ return VIEW_PADDING_ACTION_TAG;
}
int left, top, right, bottom;
@@ -2028,6 +1923,9 @@ public class RemoteViews implements Parcelable, Filter {
public static final int LAYOUT_WIDTH = 2;
public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3;
+ final int mProperty;
+ final int mValue;
+
/**
* @param viewId ID of the view alter
* @param property which layout parameter to alter
@@ -2035,21 +1933,20 @@ public class RemoteViews implements Parcelable, Filter {
*/
public LayoutParamAction(int viewId, int property, int value) {
this.viewId = viewId;
- this.property = property;
- this.value = value;
+ this.mProperty = property;
+ this.mValue = value;
}
public LayoutParamAction(Parcel parcel) {
viewId = parcel.readInt();
- property = parcel.readInt();
- value = parcel.readInt();
+ mProperty = parcel.readInt();
+ mValue = parcel.readInt();
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(LAYOUT_PARAM_ACTION_TAG);
dest.writeInt(viewId);
- dest.writeInt(property);
- dest.writeInt(value);
+ dest.writeInt(mProperty);
+ dest.writeInt(mValue);
}
@Override
@@ -2062,27 +1959,27 @@ public class RemoteViews implements Parcelable, Filter {
if (layoutParams == null) {
return;
}
- switch (property) {
+ switch (mProperty) {
case LAYOUT_MARGIN_END_DIMEN:
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
- int resolved = resolveDimenPixelOffset(target, value);
+ int resolved = resolveDimenPixelOffset(target, mValue);
((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(resolved);
target.setLayoutParams(layoutParams);
}
break;
case LAYOUT_MARGIN_BOTTOM_DIMEN:
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
- int resolved = resolveDimenPixelOffset(target, value);
+ int resolved = resolveDimenPixelOffset(target, mValue);
((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved;
target.setLayoutParams(layoutParams);
}
break;
case LAYOUT_WIDTH:
- layoutParams.width = value;
+ layoutParams.width = mValue;
target.setLayoutParams(layoutParams);
break;
default:
- throw new IllegalArgumentException("Unknown property " + property);
+ throw new IllegalArgumentException("Unknown property " + mProperty);
}
}
@@ -2093,79 +1990,15 @@ public class RemoteViews implements Parcelable, Filter {
return target.getContext().getResources().getDimensionPixelOffset(value);
}
- public String getActionName() {
- return "LayoutParamAction" + property + ".";
- }
-
- int property;
- int value;
- }
-
- /**
- * Helper action to set a color filter on a compound drawable on a TextView. Supports relative
- * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
- */
- private class TextViewDrawableColorFilterAction extends Action {
- public TextViewDrawableColorFilterAction(int viewId, boolean isRelative, int index,
- int color, PorterDuff.Mode mode) {
- this.viewId = viewId;
- this.isRelative = isRelative;
- this.index = index;
- this.color = color;
- this.mode = mode;
- }
-
- public TextViewDrawableColorFilterAction(Parcel parcel) {
- viewId = parcel.readInt();
- isRelative = (parcel.readInt() != 0);
- index = parcel.readInt();
- color = parcel.readInt();
- mode = readPorterDuffMode(parcel);
- }
-
- private PorterDuff.Mode readPorterDuffMode(Parcel parcel) {
- int mode = parcel.readInt();
- if (mode >= 0 && mode < PorterDuff.Mode.values().length) {
- return PorterDuff.Mode.values()[mode];
- } else {
- return PorterDuff.Mode.CLEAR;
- }
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG);
- dest.writeInt(viewId);
- dest.writeInt(isRelative ? 1 : 0);
- dest.writeInt(index);
- dest.writeInt(color);
- dest.writeInt(mode.ordinal());
- }
-
@Override
- public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
- final TextView target = root.findViewById(viewId);
- if (target == null) return;
- Drawable[] drawables = isRelative
- ? target.getCompoundDrawablesRelative()
- : target.getCompoundDrawables();
- if (index < 0 || index >= 4) {
- throw new IllegalStateException("index must be in range [0, 3].");
- }
- Drawable d = drawables[index];
- if (d != null) {
- d.mutate();
- d.setColorFilter(color, mode);
- }
+ public int getActionTag() {
+ return LAYOUT_PARAM_ACTION_TAG;
}
- public String getActionName() {
- return "TextViewDrawableColorFilterAction";
+ @Override
+ public String getUniqueKey() {
+ return super.getUniqueKey() + mProperty;
}
-
- final boolean isRelative;
- final int index;
- final int color;
- final PorterDuff.Mode mode;
}
/**
@@ -2184,7 +2017,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_REMOTE_INPUTS_ACTION_TAG);
dest.writeInt(viewId);
dest.writeTypedArray(remoteInputs, flags);
}
@@ -2197,8 +2029,9 @@ public class RemoteViews implements Parcelable, Filter {
target.setTagInternal(R.id.remote_input_tag, remoteInputs);
}
- public String getActionName() {
- return "SetRemoteInputsAction";
+ @Override
+ public int getActionTag() {
+ return SET_REMOTE_INPUTS_ACTION_TAG;
}
final Parcelable[] remoteInputs;
@@ -2220,7 +2053,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(OVERRIDE_TEXT_COLORS_TAG);
dest.writeInt(textColor);
}
@@ -2245,8 +2077,9 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- public String getActionName() {
- return "OverrideTextColorsAction";
+ @Override
+ public int getActionTag() {
+ return OVERRIDE_TEXT_COLORS_TAG;
}
}
@@ -2338,20 +2171,12 @@ public class RemoteViews implements Parcelable, Filter {
}
if (src.mActions != null) {
- mActions = new ArrayList<>();
-
Parcel p = Parcel.obtain();
- int count = src.mActions.size();
- for (int i = 0; i < count; i++) {
- p.setDataPosition(0);
- Action a = src.mActions.get(i);
- a.writeToParcel(
- p, a.hasSameAppInfo(mApplication) ? PARCELABLE_ELIDE_DUPLICATES : 0);
- p.setDataPosition(0);
- // Since src is already in memory, we do not care about stack overflow as it has
- // already been read once.
- mActions.add(getActionFromParcel(p, 0));
- }
+ src.writeActionsToParcel(p);
+ p.setDataPosition(0);
+ // Since src is already in memory, we do not care about stack overflow as it has
+ // already been read once.
+ readActionsFromParcel(p, 0);
p.recycle();
}
@@ -2392,13 +2217,7 @@ public class RemoteViews implements Parcelable, Filter {
mLayoutId = parcel.readInt();
mIsWidgetCollectionChild = parcel.readInt() == 1;
- int count = parcel.readInt();
- if (count > 0) {
- mActions = new ArrayList<>(count);
- for (int i = 0; i < count; i++) {
- mActions.add(getActionFromParcel(parcel, depth));
- }
- }
+ readActionsFromParcel(parcel, depth);
} else {
// MODE_HAS_LANDSCAPE_AND_PORTRAIT
mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth);
@@ -2409,21 +2228,31 @@ public class RemoteViews implements Parcelable, Filter {
mReapplyDisallowed = parcel.readInt() == 0;
}
+ private void readActionsFromParcel(Parcel parcel, int depth) {
+ int count = parcel.readInt();
+ if (count > 0) {
+ mActions = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ mActions.add(getActionFromParcel(parcel, depth));
+ }
+ }
+ }
+
private Action getActionFromParcel(Parcel parcel, int depth) {
int tag = parcel.readInt();
switch (tag) {
case SET_ON_CLICK_PENDING_INTENT_TAG:
return new SetOnClickPendingIntent(parcel);
- case SET_DRAWABLE_PARAMETERS_TAG:
- return new SetDrawableParameters(parcel);
+ case SET_DRAWABLE_TINT_TAG:
+ return new SetDrawableTint(parcel);
case REFLECTION_ACTION_TAG:
return new ReflectionAction(parcel);
case VIEW_GROUP_ACTION_ADD_TAG:
return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth);
case VIEW_GROUP_ACTION_REMOVE_TAG:
return new ViewGroupActionRemove(parcel);
- case SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG:
- return new ReflectionActionWithoutParams(parcel);
+ case VIEW_CONTENT_NAVIGATION_TAG:
+ return new ViewContentNavigation(parcel);
case SET_EMPTY_VIEW_ACTION_TAG:
return new SetEmptyView(parcel);
case SET_PENDING_INTENT_TEMPLATE_TAG:
@@ -2442,8 +2271,6 @@ public class RemoteViews implements Parcelable, Filter {
return new BitmapReflectionAction(parcel);
case SET_REMOTE_VIEW_ADAPTER_LIST_TAG:
return new SetRemoteViewsAdapterList(parcel);
- case TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG:
- return new TextViewDrawableColorFilterAction(parcel);
case SET_REMOTE_INPUTS_ACTION_TAG:
return new SetRemoteInputsAction(parcel);
case LAYOUT_PARAM_ACTION_TAG:
@@ -2600,7 +2427,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
*/
public void showNext(int viewId) {
- addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
+ addAction(new ViewContentNavigation(viewId, true /* next */));
}
/**
@@ -2609,7 +2436,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
*/
public void showPrevious(int viewId) {
- addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
+ addAction(new ViewContentNavigation(viewId, false /* next */));
}
/**
@@ -2683,28 +2510,6 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
- * Equivalent to applying a color filter on one of the drawables in
- * {@link android.widget.TextView#getCompoundDrawablesRelative()}.
- *
- * @param viewId The id of the view whose text should change.
- * @param index The index of the drawable in the array of
- * {@link android.widget.TextView#getCompoundDrawablesRelative()} to set the color
- * filter on. Must be in [0, 3].
- * @param color The color of the color filter. See
- * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
- * @param mode The mode of the color filter. See
- * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
- * @hide
- */
- public void setTextViewCompoundDrawablesRelativeColorFilter(int viewId,
- int index, int color, PorterDuff.Mode mode) {
- if (index < 0 || index >= 4) {
- throw new IllegalArgumentException("index must be in range [0, 3].");
- }
- addAction(new TextViewDrawableColorFilterAction(viewId, true, index, color, mode));
- }
-
- /**
* Equivalent to calling {@link
* TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)}
* using the drawables yielded by {@link Icon#loadDrawable(Context)}.
@@ -2901,12 +2706,10 @@ public class RemoteViews implements Parcelable, Filter {
/**
* @hide
- * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+ * Equivalent to calling
* {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
- * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
- * view.
+ * on the {@link Drawable} of a given view.
* <p>
- * You can omit specific calls by marking their values with null or -1.
*
* @param viewId The id of the view that contains the target
* {@link Drawable}
@@ -2915,20 +2718,15 @@ public class RemoteViews implements Parcelable, Filter {
* {@link android.view.View#getBackground()}. Otherwise, assume
* the target view is an {@link ImageView} and apply them to
* {@link ImageView#getDrawable()}.
- * @param alpha Specify an alpha value for the drawable, or -1 to leave
- * unchanged.
* @param colorFilter Specify a color for a
* {@link android.graphics.ColorFilter} for this drawable. This will be ignored if
* {@code mode} is {@code null}.
* @param mode Specify a PorterDuff mode for this drawable, or null to leave
* unchanged.
- * @param level Specify the level for the drawable, or -1 to leave
- * unchanged.
*/
- public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
- int colorFilter, PorterDuff.Mode mode, int level) {
- addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
- colorFilter, mode, level));
+ public void setDrawableTint(int viewId, boolean targetBackground,
+ int colorFilter, @NonNull PorterDuff.Mode mode) {
+ addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode));
}
/**
@@ -3695,18 +3493,7 @@ public class RemoteViews implements Parcelable, Filter {
}
dest.writeInt(mLayoutId);
dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
- int count;
- if (mActions != null) {
- count = mActions.size();
- } else {
- count = 0;
- }
- dest.writeInt(count);
- for (int i=0; i<count; i++) {
- Action a = mActions.get(i);
- a.writeToParcel(dest, a.hasSameAppInfo(mApplication)
- ? PARCELABLE_ELIDE_DUPLICATES : 0);
- }
+ writeActionsToParcel(dest);
} else {
dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
// We only write the bitmap cache if we are the root RemoteViews, as this cache
@@ -3721,6 +3508,22 @@ public class RemoteViews implements Parcelable, Filter {
dest.writeInt(mReapplyDisallowed ? 1 : 0);
}
+ private void writeActionsToParcel(Parcel parcel) {
+ int count;
+ if (mActions != null) {
+ count = mActions.size();
+ } else {
+ count = 0;
+ }
+ parcel.writeInt(count);
+ for (int i = 0; i < count; i++) {
+ Action a = mActions.get(i);
+ parcel.writeInt(a.getActionTag());
+ a.writeToParcel(parcel, a.hasSameAppInfo(mApplication)
+ ? PARCELABLE_ELIDE_DUPLICATES : 0);
+ }
+ }
+
private static ApplicationInfo getApplicationInfo(String packageName, int userId) {
if (packageName == null) {
return null;
diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java
index 36dc3308..3be42a5b 100644
--- a/android/widget/SelectionActionModeHelper.java
+++ b/android/widget/SelectionActionModeHelper.java
@@ -43,9 +43,11 @@ import com.android.internal.util.Preconditions;
import java.text.BreakIterator;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
@@ -58,11 +60,7 @@ import java.util.regex.Pattern;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class SelectionActionModeHelper {
- /**
- * Maximum time (in milliseconds) to wait for a result before timing out.
- */
- // TODO: Consider making this a ViewConfiguration.
- private static final int TIMEOUT_DURATION = 200;
+ private static final String LOG_TAG = "SelectActionModeHelper";
private static final boolean SMART_SELECT_ANIMATION_ENABLED = true;
@@ -83,7 +81,8 @@ public final class SelectionActionModeHelper {
mEditor = Preconditions.checkNotNull(editor);
mTextView = mEditor.getTextView();
mTextClassificationHelper = new TextClassificationHelper(
- mTextView.getTextClassifier(), mTextView.getText(),
+ mTextView.getTextClassifier(),
+ getText(mTextView),
0, 1, mTextView.getTextLocales());
mSelectionTracker = new SelectionTracker(mTextView);
@@ -97,7 +96,7 @@ public final class SelectionActionModeHelper {
public void startActionModeAsync(boolean adjustSelection) {
mSelectionTracker.onOriginalSelection(
- mTextView.getText(),
+ getText(mTextView),
mTextView.getSelectionStart(),
mTextView.getSelectionEnd(),
mTextView.isTextEditable());
@@ -108,7 +107,7 @@ public final class SelectionActionModeHelper {
resetTextClassificationHelper();
mTextClassificationAsyncTask = new TextClassificationAsyncTask(
mTextView,
- TIMEOUT_DURATION,
+ mTextClassificationHelper.getTimeoutDuration(),
adjustSelection
? mTextClassificationHelper::suggestSelection
: mTextClassificationHelper::classifyText,
@@ -127,7 +126,7 @@ public final class SelectionActionModeHelper {
resetTextClassificationHelper();
mTextClassificationAsyncTask = new TextClassificationAsyncTask(
mTextView,
- TIMEOUT_DURATION,
+ mTextClassificationHelper.getTimeoutDuration(),
mTextClassificationHelper::classifyText,
this::invalidateActionMode)
.execute();
@@ -195,7 +194,7 @@ public final class SelectionActionModeHelper {
}
private void startActionMode(@Nullable SelectionResult result) {
- final CharSequence text = mTextView.getText();
+ final CharSequence text = getText(mTextView);
if (result != null && text instanceof Spannable) {
Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
mTextClassification = result.mClassification;
@@ -229,7 +228,7 @@ public final class SelectionActionModeHelper {
return;
}
- final List<RectF> selectionRectangles =
+ final List<SmartSelectSprite.RectangleWithTextSelectionLayout> selectionRectangles =
convertSelectionToRectangles(layout, result.mStart, result.mEnd);
final PointF touchPoint = new PointF(
@@ -237,7 +236,8 @@ public final class SelectionActionModeHelper {
mEditor.getLastUpPositionY());
final PointF animationStartPoint =
- movePointInsideNearestRectangle(touchPoint, selectionRectangles);
+ movePointInsideNearestRectangle(touchPoint, selectionRectangles,
+ SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle);
mSmartSelectSprite.startAnimation(
animationStartPoint,
@@ -245,38 +245,58 @@ public final class SelectionActionModeHelper {
onAnimationEndCallback);
}
- private List<RectF> convertSelectionToRectangles(final Layout layout, final int start,
- final int end) {
- final List<RectF> result = new ArrayList<>();
- layout.getSelection(start, end, (left, top, right, bottom, textSelectionLayout) ->
- mergeRectangleIntoList(result, new RectF(left, top, right, bottom)));
+ private List<SmartSelectSprite.RectangleWithTextSelectionLayout> convertSelectionToRectangles(
+ final Layout layout, final int start, final int end) {
+ final List<SmartSelectSprite.RectangleWithTextSelectionLayout> result = new ArrayList<>();
+
+ final Layout.SelectionRectangleConsumer consumer =
+ (left, top, right, bottom, textSelectionLayout) -> mergeRectangleIntoList(
+ result,
+ new RectF(left, top, right, bottom),
+ SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle,
+ r -> new SmartSelectSprite.RectangleWithTextSelectionLayout(r,
+ textSelectionLayout)
+ );
+
+ layout.getSelection(start, end, consumer);
+
+ result.sort(Comparator.comparing(
+ SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle,
+ SmartSelectSprite.RECTANGLE_COMPARATOR));
- result.sort(SmartSelectSprite.RECTANGLE_COMPARATOR);
return result;
}
+ // TODO: Move public pure functions out of this class and make it package-private.
/**
- * Merges a {@link RectF} into an existing list of rectangles. While merging, this method
- * makes sure that:
+ * Merges a {@link RectF} into an existing list of any objects which contain a rectangle.
+ * While merging, this method makes sure that:
*
* <ol>
* <li>No rectangle is redundant (contained within a bigger rectangle)</li>
* <li>Rectangles of the same height and vertical position that intersect get merged</li>
* </ol>
*
- * @param list the list of rectangles to merge the new rectangle in
+ * @param list the list of rectangles (or other rectangle containers) to merge the new
+ * rectangle into
* @param candidate the {@link RectF} to merge into the list
+ * @param extractor a function that can extract a {@link RectF} from an element of the given
+ * list
+ * @param packer a function that can wrap the resulting {@link RectF} into an element that
+ * the list contains
* @hide
*/
@VisibleForTesting
- public static void mergeRectangleIntoList(List<RectF> list, RectF candidate) {
+ public static <T> void mergeRectangleIntoList(final List<T> list,
+ final RectF candidate, final Function<T, RectF> extractor,
+ final Function<RectF, T> packer) {
if (candidate.isEmpty()) {
return;
}
final int elementCount = list.size();
for (int index = 0; index < elementCount; ++index) {
- final RectF existingRectangle = list.get(index);
+ final RectF existingRectangle = extractor.apply(list.get(index));
if (existingRectangle.contains(candidate)) {
return;
}
@@ -299,26 +319,27 @@ public final class SelectionActionModeHelper {
}
for (int index = elementCount - 1; index >= 0; --index) {
- if (list.get(index).isEmpty()) {
+ final RectF rectangle = extractor.apply(list.get(index));
+ if (rectangle.isEmpty()) {
list.remove(index);
}
}
- list.add(candidate);
+ list.add(packer.apply(candidate));
}
/** @hide */
@VisibleForTesting
- public static PointF movePointInsideNearestRectangle(final PointF point,
- final List<RectF> rectangles) {
+ public static <T> PointF movePointInsideNearestRectangle(final PointF point,
+ final List<T> list, final Function<T, RectF> extractor) {
float bestX = -1;
float bestY = -1;
double bestDistance = Double.MAX_VALUE;
- final int elementCount = rectangles.size();
+ final int elementCount = list.size();
for (int index = 0; index < elementCount; ++index) {
- final RectF rectangle = rectangles.get(index);
+ final RectF rectangle = extractor.apply(list.get(index));
final float candidateY = rectangle.centerY();
final float candidateX;
@@ -356,7 +377,9 @@ public final class SelectionActionModeHelper {
}
private void resetTextClassificationHelper() {
- mTextClassificationHelper.reset(mTextView.getTextClassifier(), mTextView.getText(),
+ mTextClassificationHelper.reset(
+ mTextView.getTextClassifier(),
+ getText(mTextView),
mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
mTextView.getTextLocales());
}
@@ -382,6 +405,7 @@ public final class SelectionActionModeHelper {
private int mSelectionStart;
private int mSelectionEnd;
private boolean mAllowReset;
+ private final LogAbandonRunnable mDelayedLogAbandon = new LogAbandonRunnable();
SelectionTracker(TextView textView) {
mTextView = Preconditions.checkNotNull(textView);
@@ -393,6 +417,10 @@ public final class SelectionActionModeHelper {
*/
public void onOriginalSelection(
CharSequence text, int selectionStart, int selectionEnd, boolean editableText) {
+ // If we abandoned a selection and created a new one very shortly after, we may still
+ // have a pending request to log ABANDON, which we flush here.
+ mDelayedLogAbandon.flush();
+
mOriginalStart = mSelectionStart = selectionStart;
mOriginalEnd = mSelectionEnd = selectionEnd;
mAllowReset = false;
@@ -433,12 +461,7 @@ public final class SelectionActionModeHelper {
public void onSelectionDestroyed() {
mAllowReset = false;
// Wait a few ms to see if the selection was destroyed because of a text change event.
- mTextView.postDelayed(() -> {
- mLogger.logSelectionAction(
- mSelectionStart, mSelectionEnd,
- SelectionEvent.ActionType.ABANDON, null /* classification */);
- mSelectionStart = mSelectionEnd = -1;
- }, 100 /* ms */);
+ mDelayedLogAbandon.schedule(100 /* ms */);
}
/**
@@ -465,7 +488,7 @@ public final class SelectionActionModeHelper {
if (isSelectionStarted()
&& mAllowReset
&& textIndex >= mSelectionStart && textIndex <= mSelectionEnd
- && textView.getText() instanceof Spannable) {
+ && getText(textView) instanceof Spannable) {
mAllowReset = false;
boolean selected = editor.selectCurrentWord();
if (selected) {
@@ -495,6 +518,38 @@ public final class SelectionActionModeHelper {
private boolean isSelectionStarted() {
return mSelectionStart >= 0 && mSelectionEnd >= 0 && mSelectionStart != mSelectionEnd;
}
+
+ /** A helper for keeping track of pending abandon logging requests. */
+ private final class LogAbandonRunnable implements Runnable {
+ private boolean mIsPending;
+
+ /** Schedules an abandon to be logged with the given delay. Flush if necessary. */
+ void schedule(int delayMillis) {
+ if (mIsPending) {
+ Log.e(LOG_TAG, "Force flushing abandon due to new scheduling request");
+ flush();
+ }
+ mIsPending = true;
+ mTextView.postDelayed(this, delayMillis);
+ }
+
+ /** If there is a pending log request, execute it now. */
+ void flush() {
+ mTextView.removeCallbacks(this);
+ run();
+ }
+
+ @Override
+ public void run() {
+ if (mIsPending) {
+ mLogger.logSelectionAction(
+ mSelectionStart, mSelectionEnd,
+ SelectionEvent.ActionType.ABANDON, null /* classification */);
+ mSelectionStart = mSelectionEnd = -1;
+ mIsPending = false;
+ }
+ }
+ }
}
// TODO: Write tests
@@ -689,7 +744,7 @@ public final class SelectionActionModeHelper {
mSelectionResultSupplier = Preconditions.checkNotNull(selectionResultSupplier);
mSelectionResultCallback = Preconditions.checkNotNull(selectionResultCallback);
// Make a copy of the original text.
- mOriginalText = mTextView.getText().toString();
+ mOriginalText = getText(mTextView).toString();
}
@Override
@@ -705,7 +760,7 @@ public final class SelectionActionModeHelper {
@Override
@UiThread
protected void onPostExecute(SelectionResult result) {
- result = TextUtils.equals(mOriginalText, mTextView.getText()) ? result : null;
+ result = TextUtils.equals(mOriginalText, getText(mTextView)) ? result : null;
mSelectionResultCallback.accept(result);
}
@@ -752,6 +807,9 @@ public final class SelectionActionModeHelper {
private LocaleList mLastClassificationLocales;
private SelectionResult mLastClassificationResult;
+ /** Whether the TextClassifier has been initialized. */
+ private boolean mHot;
+
TextClassificationHelper(TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
reset(textClassifier, text, selectionStart, selectionEnd, locales);
@@ -771,11 +829,13 @@ public final class SelectionActionModeHelper {
@WorkerThread
public SelectionResult classifyText() {
+ mHot = true;
return performClassification(null /* selection */);
}
@WorkerThread
public SelectionResult suggestSelection() {
+ mHot = true;
trimText();
final TextSelection selection = mTextClassifier.suggestSelection(
mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
@@ -784,6 +844,22 @@ public final class SelectionActionModeHelper {
return performClassification(selection);
}
+ /**
+ * Maximum time (in milliseconds) to wait for a textclassifier result before timing out.
+ */
+ // TODO: Consider making this a ViewConfiguration.
+ public int getTimeoutDuration() {
+ if (mHot) {
+ return 200;
+ } else {
+ // Return a slightly larger number than usual when the TextClassifier is first
+ // initialized. Initialization would usually take longer than subsequent calls to
+ // the TextClassifier. The impact of this on the UI is that we do not show the
+ // selection handles or toolbar until after this timeout.
+ return 500;
+ }
+ }
+
private SelectionResult performClassification(@Nullable TextSelection selection) {
if (!Objects.equals(mText, mLastClassificationText)
|| mSelectionStart != mLastClassificationSelectionStart
@@ -854,4 +930,14 @@ public final class SelectionActionModeHelper {
return SelectionEvent.ActionType.OTHER;
}
}
+
+ private static CharSequence getText(TextView textView) {
+ // Extracts the textView's text.
+ // TODO: Investigate why/when TextView.getText() is null.
+ final CharSequence text = textView.getText();
+ if (text != null) {
+ return text;
+ }
+ return "";
+ }
}
diff --git a/android/widget/SmartSelectSprite.java b/android/widget/SmartSelectSprite.java
index 27b93bc7..a391c6ee 100644
--- a/android/widget/SmartSelectSprite.java
+++ b/android/widget/SmartSelectSprite.java
@@ -35,6 +35,7 @@ import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.Shape;
+import android.text.Layout;
import android.util.TypedValue;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -42,9 +43,9 @@ import android.view.animation.Interpolator;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.LinkedList;
import java.util.List;
/**
@@ -76,6 +77,26 @@ final class SmartSelectSprite {
private Drawable mExistingDrawable = null;
private RectangleList mExistingRectangleList = null;
+ static final class RectangleWithTextSelectionLayout {
+ private final RectF mRectangle;
+ @Layout.TextSelectionLayout
+ private final int mTextSelectionLayout;
+
+ RectangleWithTextSelectionLayout(RectF rectangle, int textSelectionLayout) {
+ mRectangle = Preconditions.checkNotNull(rectangle);
+ mTextSelectionLayout = textSelectionLayout;
+ }
+
+ public RectF getRectangle() {
+ return mRectangle;
+ }
+
+ @Layout.TextSelectionLayout
+ public int getTextSelectionLayout() {
+ return mTextSelectionLayout;
+ }
+ }
+
/**
* A rounded rectangle with a configurable corner radius and the ability to expand outside of
* its bounding rectangle and clip against it.
@@ -84,12 +105,23 @@ final class SmartSelectSprite {
private static final String PROPERTY_ROUND_RATIO = "roundRatio";
+ /**
+ * The direction in which the rectangle will perform its expansion. A rectangle can expand
+ * from its left edge, its right edge or from the center (or, more precisely, the user's
+ * touch point). For example, in left-to-right text, a selection spanning two lines with the
+ * user's action being on the first line will have the top rectangle and expansion direction
+ * of CENTER, while the bottom one will have an expansion direction of RIGHT.
+ */
@Retention(SOURCE)
@IntDef({ExpansionDirection.LEFT, ExpansionDirection.CENTER, ExpansionDirection.RIGHT})
private @interface ExpansionDirection {
- int LEFT = 0;
- int CENTER = 1;
- int RIGHT = 2;
+ int LEFT = -1;
+ int CENTER = 0;
+ int RIGHT = 1;
+ }
+
+ private static @ExpansionDirection int invert(@ExpansionDirection int expansionDirection) {
+ return expansionDirection * -1;
}
@Retention(SOURCE)
@@ -114,20 +146,33 @@ final class SmartSelectSprite {
private final RectF mClipRect = new RectF();
private final Path mClipPath = new Path();
- /** How far offset the left edge of the rectangle is from the bounding box. */
+ /** How offset the left edge of the rectangle is from the left side of the bounding box. */
private float mLeftBoundary = 0;
- /** How far offset the right edge of the rectangle is from the bounding box. */
+ /** How offset the right edge of the rectangle is from the left side of the bounding box. */
private float mRightBoundary = 0;
+ /** Whether the horizontal bounds are inverted (for RTL scenarios). */
+ private final boolean mInverted;
+
+ private final float mBoundingWidth;
+
private RoundedRectangleShape(
final RectF boundingRectangle,
final @ExpansionDirection int expansionDirection,
final @RectangleBorderType int rectangleBorderType,
+ final boolean inverted,
final float strokeWidth) {
mBoundingRectangle = new RectF(boundingRectangle);
- mExpansionDirection = expansionDirection;
+ mBoundingWidth = boundingRectangle.width();
mRectangleBorderType = rectangleBorderType;
mStrokeWidth = strokeWidth;
+ mInverted = inverted && expansionDirection != ExpansionDirection.CENTER;
+
+ if (inverted) {
+ mExpansionDirection = invert(expansionDirection);
+ } else {
+ mExpansionDirection = expansionDirection;
+ }
if (boundingRectangle.height() > boundingRectangle.width()) {
setRoundRatio(0.0f);
@@ -148,6 +193,10 @@ final class SmartSelectSprite {
*/
@Override
public void draw(Canvas canvas, Paint paint) {
+ if (mLeftBoundary == mRightBoundary) {
+ return;
+ }
+
final float cornerRadius = getCornerRadius();
final float adjustedCornerRadius = getAdjustedCornerRadius();
@@ -157,7 +206,7 @@ final class SmartSelectSprite {
if (mRectangleBorderType == RectangleBorderType.OVERSHOOT) {
mDrawRect.left -= cornerRadius / 2;
- mDrawRect.right -= cornerRadius / 2;
+ mDrawRect.right += cornerRadius / 2;
} else {
switch (mExpansionDirection) {
case ExpansionDirection.CENTER:
@@ -173,7 +222,7 @@ final class SmartSelectSprite {
canvas.save();
mClipRect.set(mBoundingRectangle);
- mClipRect.inset(-mStrokeWidth, -mStrokeWidth);
+ mClipRect.inset(-mStrokeWidth / 2, -mStrokeWidth / 2);
canvas.clipRect(mClipRect);
canvas.drawRoundRect(mDrawRect, adjustedCornerRadius, adjustedCornerRadius, paint);
canvas.restore();
@@ -190,20 +239,28 @@ final class SmartSelectSprite {
canvas.restore();
}
- public void setRoundRatio(@FloatRange(from = 0.0, to = 1.0) final float roundRatio) {
+ void setRoundRatio(@FloatRange(from = 0.0, to = 1.0) final float roundRatio) {
mRoundRatio = roundRatio;
}
- public float getRoundRatio() {
+ float getRoundRatio() {
return mRoundRatio;
}
- private void setLeftBoundary(final float leftBoundary) {
- mLeftBoundary = leftBoundary;
+ private void setStartBoundary(final float startBoundary) {
+ if (mInverted) {
+ mRightBoundary = mBoundingWidth - startBoundary;
+ } else {
+ mLeftBoundary = startBoundary;
+ }
}
- private void setRightBoundary(final float rightBoundary) {
- mRightBoundary = rightBoundary;
+ private void setEndBoundary(final float endBoundary) {
+ if (mInverted) {
+ mLeftBoundary = mBoundingWidth - endBoundary;
+ } else {
+ mRightBoundary = endBoundary;
+ }
}
private float getCornerRadius() {
@@ -247,8 +304,8 @@ final class SmartSelectSprite {
private @DisplayType int mDisplayType = DisplayType.RECTANGLES;
private RectangleList(final List<RoundedRectangleShape> rectangles) {
- mRectangles = new LinkedList<>(rectangles);
- mReversedRectangles = new LinkedList<>(rectangles);
+ mRectangles = new ArrayList<>(rectangles);
+ mReversedRectangles = new ArrayList<>(rectangles);
Collections.reverse(mReversedRectangles);
mOutlinePolygonPath = generateOutlinePolygonPath(rectangles);
}
@@ -258,11 +315,11 @@ final class SmartSelectSprite {
for (RoundedRectangleShape rectangle : mReversedRectangles) {
final float rectangleLeftBoundary = boundarySoFar - rectangle.getBoundingWidth();
if (leftBoundary < rectangleLeftBoundary) {
- rectangle.setLeftBoundary(0);
+ rectangle.setStartBoundary(0);
} else if (leftBoundary > boundarySoFar) {
- rectangle.setLeftBoundary(rectangle.getBoundingWidth());
+ rectangle.setStartBoundary(rectangle.getBoundingWidth());
} else {
- rectangle.setLeftBoundary(
+ rectangle.setStartBoundary(
rectangle.getBoundingWidth() - boundarySoFar + leftBoundary);
}
@@ -275,11 +332,11 @@ final class SmartSelectSprite {
for (RoundedRectangleShape rectangle : mRectangles) {
final float rectangleRightBoundary = rectangle.getBoundingWidth() + boundarySoFar;
if (rectangleRightBoundary < rightBoundary) {
- rectangle.setRightBoundary(rectangle.getBoundingWidth());
+ rectangle.setEndBoundary(rectangle.getBoundingWidth());
} else if (boundarySoFar > rightBoundary) {
- rectangle.setRightBoundary(0);
+ rectangle.setEndBoundary(0);
} else {
- rectangle.setRightBoundary(rightBoundary - boundarySoFar);
+ rectangle.setEndBoundary(rightBoundary - boundarySoFar);
}
boundarySoFar = rectangleRightBoundary;
@@ -331,8 +388,8 @@ final class SmartSelectSprite {
}
/**
- * @param context The {@link Context} in which the animation will run
- * @param invalidator A {@link Runnable} which will be called every time the animation updates,
+ * @param context the {@link Context} in which the animation will run
+ * @param invalidator a {@link Runnable} which will be called every time the animation updates,
* indicating that the view drawing the animation should invalidate itself
*/
SmartSelectSprite(final Context context, final Runnable invalidator) {
@@ -356,67 +413,97 @@ final class SmartSelectSprite {
* "selection" and finally join them into a single polygon. In
* order to get the correct visual behavior, these rectangles
* should be sorted according to {@link #RECTANGLE_COMPARATOR}.
- * @param onAnimationEnd The callback which will be invoked once the whole animation
- * completes.
+ * @param onAnimationEnd the callback which will be invoked once the whole animation
+ * completes
* @throws IllegalArgumentException if the given start point is not in any of the
- * destinationRectangles.
+ * destinationRectangles
* @see #cancelAnimation()
*/
+ // TODO nullability checks on parameters
public void startAnimation(
final PointF start,
- final List<RectF> destinationRectangles,
- final Runnable onAnimationEnd) throws IllegalArgumentException {
+ final List<RectangleWithTextSelectionLayout> destinationRectangles,
+ final Runnable onAnimationEnd) {
cancelAnimation();
final ValueAnimator.AnimatorUpdateListener updateListener =
valueAnimator -> mInvalidator.run();
- final List<RoundedRectangleShape> shapes = new LinkedList<>();
- final List<Animator> cornerAnimators = new LinkedList<>();
+ final int rectangleCount = destinationRectangles.size();
+
+ final List<RoundedRectangleShape> shapes = new ArrayList<>(rectangleCount);
+ final List<Animator> cornerAnimators = new ArrayList<>(rectangleCount);
- final RectF centerRectangle = destinationRectangles
- .stream()
- .filter((r) -> contains(r, start))
- .findFirst()
- .orElseThrow(() -> new IllegalArgumentException(
- "Center point is not inside any of the rectangles!"));
+ RectangleWithTextSelectionLayout centerRectangle = null;
int startingOffset = 0;
- for (RectF rectangle : destinationRectangles) {
- if (rectangle.equals(centerRectangle)) {
+ int startingRectangleIndex = 0;
+ for (int index = 0; index < rectangleCount; ++index) {
+ final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
+ destinationRectangles.get(index);
+ final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
+ if (contains(rectangle, start)) {
+ centerRectangle = rectangleWithTextSelectionLayout;
break;
}
startingOffset += rectangle.width();
+ ++startingRectangleIndex;
}
- startingOffset += start.x - centerRectangle.left;
+ if (centerRectangle == null) {
+ throw new IllegalArgumentException("Center point is not inside any of the rectangles!");
+ }
- final float centerRectangleHalfHeight = centerRectangle.height() / 2;
- final float startingOffsetLeft = startingOffset - centerRectangleHalfHeight;
- final float startingOffsetRight = startingOffset + centerRectangleHalfHeight;
+ startingOffset += start.x - centerRectangle.getRectangle().left;
final @RoundedRectangleShape.ExpansionDirection int[] expansionDirections =
generateDirections(centerRectangle, destinationRectangles);
final @RoundedRectangleShape.RectangleBorderType int[] rectangleBorderTypes =
- generateBorderTypes(destinationRectangles);
-
- int index = 0;
+ generateBorderTypes(rectangleCount);
- for (RectF rectangle : destinationRectangles) {
+ for (int index = 0; index < rectangleCount; ++index) {
+ final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
+ destinationRectangles.get(index);
+ final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
final RoundedRectangleShape shape = new RoundedRectangleShape(
rectangle,
expansionDirections[index],
rectangleBorderTypes[index],
+ rectangleWithTextSelectionLayout.getTextSelectionLayout()
+ == Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT,
mStrokeWidth);
cornerAnimators.add(createCornerAnimator(shape, updateListener));
shapes.add(shape);
- index++;
}
final RectangleList rectangleList = new RectangleList(shapes);
final ShapeDrawable shapeDrawable = new ShapeDrawable(rectangleList);
+ final float startingOffsetLeft;
+ final float startingOffsetRight;
+
+ final RoundedRectangleShape startingRectangleShape = shapes.get(startingRectangleIndex);
+ final float cornerRadius = startingRectangleShape.getCornerRadius();
+ if (startingRectangleShape.mRectangleBorderType
+ == RoundedRectangleShape.RectangleBorderType.FIT) {
+ switch (startingRectangleShape.mExpansionDirection) {
+ case RoundedRectangleShape.ExpansionDirection.LEFT:
+ startingOffsetLeft = startingOffsetRight = startingOffset - cornerRadius / 2;
+ break;
+ case RoundedRectangleShape.ExpansionDirection.RIGHT:
+ startingOffsetLeft = startingOffsetRight = startingOffset + cornerRadius / 2;
+ break;
+ case RoundedRectangleShape.ExpansionDirection.CENTER: // fall through
+ default:
+ startingOffsetLeft = startingOffset - cornerRadius / 2;
+ startingOffsetRight = startingOffset + cornerRadius / 2;
+ break;
+ }
+ } else {
+ startingOffsetLeft = startingOffsetRight = startingOffset;
+ }
+
final Paint paint = shapeDrawable.getPaint();
paint.setColor(mStrokeColor);
paint.setStyle(Paint.Style.STROKE);
@@ -511,7 +598,8 @@ final class SmartSelectSprite {
}
private static @RoundedRectangleShape.ExpansionDirection int[] generateDirections(
- final RectF centerRectangle, final List<RectF> rectangles) {
+ final RectangleWithTextSelectionLayout centerRectangle,
+ final List<RectangleWithTextSelectionLayout> rectangles) {
final @RoundedRectangleShape.ExpansionDirection int[] result = new int[rectangles.size()];
final int centerRectangleIndex = rectangles.indexOf(centerRectangle);
@@ -538,8 +626,8 @@ final class SmartSelectSprite {
}
private static @RoundedRectangleShape.RectangleBorderType int[] generateBorderTypes(
- final List<RectF> rectangles) {
- final @RoundedRectangleShape.RectangleBorderType int[] result = new int[rectangles.size()];
+ final int numberOfRectangles) {
+ final @RoundedRectangleShape.RectangleBorderType int[] result = new int[numberOfRectangles];
for (int i = 1; i < result.length - 1; ++i) {
result[i] = RoundedRectangleShape.RectangleBorderType.OVERSHOOT;
diff --git a/android/widget/Switch.java b/android/widget/Switch.java
index 2e1e9636..604575fa 100644
--- a/android/widget/Switch.java
+++ b/android/widget/Switch.java
@@ -248,10 +248,7 @@ public class Switch extends CompoundButton {
com.android.internal.R.styleable.Switch_switchPadding, 0);
mSplitTrack = a.getBoolean(com.android.internal.R.styleable.Switch_splitTrack, false);
- // TODO: replace CUR_DEVELOPMENT with P once P is added to android.os.Build.VERSION_CODES.
- // STOPSHIP if the above TODO is not done.
- mUseFallbackLineSpacing =
- context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.CUR_DEVELOPMENT;
+ mUseFallbackLineSpacing = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.P;
ColorStateList thumbTintList = a.getColorStateList(
com.android.internal.R.styleable.Switch_thumbTint);
diff --git a/android/widget/TabWidget.java b/android/widget/TabWidget.java
index 05f7c0a1..f8b6837e 100644
--- a/android/widget/TabWidget.java
+++ b/android/widget/TabWidget.java
@@ -61,7 +61,10 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
// This value will be set to 0 as soon as the first tab is added to TabHost.
private int mSelectedTab = -1;
+ @Nullable
private Drawable mLeftStrip;
+
+ @Nullable
private Drawable mRightStrip;
private boolean mDrawBottomStrips = true;
@@ -374,23 +377,36 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
final Drawable leftStrip = mLeftStrip;
final Drawable rightStrip = mRightStrip;
- leftStrip.setState(selectedChild.getDrawableState());
- rightStrip.setState(selectedChild.getDrawableState());
+ if (leftStrip != null) {
+ leftStrip.setState(selectedChild.getDrawableState());
+ }
+ if (rightStrip != null) {
+ rightStrip.setState(selectedChild.getDrawableState());
+ }
if (mStripMoved) {
final Rect bounds = mBounds;
bounds.left = selectedChild.getLeft();
bounds.right = selectedChild.getRight();
final int myHeight = getHeight();
- leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
- myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
- rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
- Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()), myHeight);
+ if (leftStrip != null) {
+ leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
+ myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
+ }
+ if (rightStrip != null) {
+ rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
+ Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()),
+ myHeight);
+ }
mStripMoved = false;
}
- leftStrip.draw(canvas);
- rightStrip.draw(canvas);
+ if (leftStrip != null) {
+ leftStrip.draw(canvas);
+ }
+ if (rightStrip != null) {
+ rightStrip.draw(canvas);
+ }
}
/**
diff --git a/android/widget/TextView.java b/android/widget/TextView.java
index efcc3a2f..24ae03c3 100644
--- a/android/widget/TextView.java
+++ b/android/widget/TextView.java
@@ -1256,9 +1256,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
- // TODO: replace CUR_DEVELOPMENT with P once P is added to android.os.Build.VERSION_CODES.
- // STOPSHIP if the above TODO is not done.
- mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.CUR_DEVELOPMENT;
+ mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
if (inputMethod != null) {
Class<?> c;
@@ -5549,7 +5547,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public final void setHint(CharSequence hint) {
setHintInternal(hint);
- if (isInputMethodTarget()) {
+ if (mEditor != null && isInputMethodTarget()) {
mEditor.reportExtractedText();
}
}
@@ -6283,7 +6281,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int horizontalPadding = getCompoundPaddingLeft();
final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
- if (mEditor.mCursorDrawable == null) {
+ if (mEditor.mDrawableForCursor == null) {
synchronized (TEMP_RECTF) {
/*
* The reason for this concern about the thickness of the
@@ -6310,7 +6308,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
(int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
}
} else {
- final Rect bounds = mEditor.mCursorDrawable.getBounds();
+ final Rect bounds = mEditor.mDrawableForCursor.getBounds();
invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
}
@@ -6362,8 +6360,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int bottom = mLayout.getLineBottom(lineEnd);
// mEditor can be null in case selection is set programmatically.
- if (invalidateCursor && mEditor != null && mEditor.mCursorDrawable != null) {
- final Rect bounds = mEditor.mCursorDrawable.getBounds();
+ if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
+ final Rect bounds = mEditor.mDrawableForCursor.getBounds();
top = Math.min(top, bounds.top);
bottom = Math.max(bottom, bounds.bottom);
}
diff --git a/android/widget/TextViewSetTextLocalePerfTest.java b/android/widget/TextViewSetTextLocalePerfTest.java
index 7fc5e4f8..e95676b2 100644
--- a/android/widget/TextViewSetTextLocalePerfTest.java
+++ b/android/widget/TextViewSetTextLocalePerfTest.java
@@ -16,27 +16,21 @@
package android.widget;
-import android.app.Activity;
-import android.os.Bundle;
-import android.perftests.utils.PerfStatusReporter;
-import android.util.Log;
-
import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
import android.support.test.rule.ActivityTestRule;
-import android.support.test.InstrumentationRegistry;
-
-import java.util.Locale;
-import java.util.Collection;
-import java.util.Arrays;
-import org.junit.Test;
import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Locale;
@LargeTest
@RunWith(Parameterized.class)