diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-10-10 15:20:13 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-10-10 15:20:13 -0400 |
commit | 93b7ee4fce01df52a6607f0b1965cbafdfeaf1a6 (patch) | |
tree | 49f76f879a89c256a4f65b674086be50760bdffb /android/widget | |
parent | bc81c7ada5aab3806dd0b17498f5c9672c9b33c4 (diff) | |
download | android-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.java | 19 | ||||
-rw-r--r-- | android/widget/EditTextCursorMovementPerfTest.java | 19 | ||||
-rw-r--r-- | android/widget/Editor.java | 153 | ||||
-rw-r--r-- | android/widget/PopupWindow.java | 11 | ||||
-rw-r--r-- | android/widget/RemoteViews.java | 551 | ||||
-rw-r--r-- | android/widget/SelectionActionModeHelper.java | 164 | ||||
-rw-r--r-- | android/widget/SmartSelectSprite.java | 192 | ||||
-rw-r--r-- | android/widget/Switch.java | 5 | ||||
-rw-r--r-- | android/widget/TabWidget.java | 32 | ||||
-rw-r--r-- | android/widget/TextView.java | 14 | ||||
-rw-r--r-- | android/widget/TextViewSetTextLocalePerfTest.java | 20 |
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) |