diff options
| author | 2020-12-21 13:33:02 -0500 | |
|---|---|---|
| committer | 2021-01-07 14:30:57 -0500 | |
| commit | a2114a56c862e13ae3a85f9cc8e8666be3deb69b (patch) | |
| tree | 79ab9544be25aed79814386e6bc18f446a620d0a | |
| parent | 60decee64a031fc51d48e1a003cb74b3136c6894 (diff) | |
Fill out RemoteViews methods for editing LayoutParams.
* All these methods are still hidden
* Methods take either a dimen or a value with a TypedValue COMPLEX_UNIT (as used for TYPE_DIMENSION)
* Rather than having 8 margin methods, I chose to have only 2, and create an @IntDef MarginType
* Created a set of methods in TypedValue for creating typed value data equivalent to that of dimension resources.
Bug: 175345582
Test: atest TypedValueTest
Change-Id: I3a8a5a1511158a6dbf55d4f9931ba100a369cb35
| -rw-r--r-- | core/java/android/app/Notification.java | 26 | ||||
| -rw-r--r-- | core/java/android/util/TypedValue.java | 144 | ||||
| -rw-r--r-- | core/java/android/widget/RemoteViews.java | 260 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/util/TypedValueTest.kt | 155 |
4 files changed, 519 insertions, 66 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8242e4db16c3..61fdc86d12fb 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -83,6 +83,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; +import android.util.TypedValue; import android.util.proto.ProtoOutputStream; import android.view.ContextThemeWrapper; import android.view.Gravity; @@ -4911,7 +4912,8 @@ public class Notification implements Parcelable setTextViewColorPrimary(contentView, R.id.title, p); contentView.setViewLayoutWidth(R.id.title, showProgress ? ViewGroup.LayoutParams.WRAP_CONTENT - : ViewGroup.LayoutParams.MATCH_PARENT); + : ViewGroup.LayoutParams.MATCH_PARENT, + TypedValue.COMPLEX_UNIT_PX); } if (p.text != null && p.text.length() != 0) { int textId = showProgress ? com.android.internal.R.id.text_line_1 @@ -5356,8 +5358,9 @@ public class Notification implements Parcelable final boolean snoozeEnabled = mContext.getContentResolver() != null && (Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1); - big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, - snoozeEnabled ? 0 : R.dimen.notification_content_margin); + int bottomMarginDimen = snoozeEnabled ? 0 : R.dimen.notification_content_margin; + big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, + RemoteViews.MARGIN_BOTTOM, bottomMarginDimen); } private static List<Notification.Action> filterOutContextualActions( @@ -5389,7 +5392,8 @@ public class Notification implements Parcelable if (N > 0) { big.setViewVisibility(R.id.actions_container, View.VISIBLE); big.setViewVisibility(R.id.actions, View.VISIBLE); - big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0); + big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, + RemoteViews.MARGIN_BOTTOM, 0); if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; for (int i=0; i<N; i++) { Action action = nonContextualActions.get(i); @@ -7788,8 +7792,8 @@ public class Notification implements Parcelable // also update the end margin if there is an image // NOTE: This template doesn't support moving this icon to the left, so we don't // need to fully apply the MarginSet - contentView.setViewLayoutMarginEnd(R.id.notification_messaging, - bindResult.mHeadingExtraMarginSet.getValue()); + contentView.setViewLayoutMargin(R.id.notification_messaging, RemoteViews.MARGIN_END, + bindResult.mHeadingExtraMarginSet.getValue(), TypedValue.COMPLEX_UNIT_PX); } contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", mBuilder.isColorized(p) @@ -8613,7 +8617,8 @@ public class Notification implements Parcelable if (mBuilder.mN.hasLargeIcon()) { endMargin = R.dimen.notification_media_image_margin_end; } - view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); + view.setViewLayoutMarginDimen(R.id.notification_main_column, + RemoteViews.MARGIN_END, endMargin); return view; } @@ -8650,8 +8655,8 @@ public class Notification implements Parcelable private void handleImage(RemoteViews contentView) { if (mBuilder.mN.hasLargeIcon()) { - contentView.setViewLayoutMarginEndDimen(R.id.line1, 0); - contentView.setViewLayoutMarginEndDimen(R.id.text, 0); + contentView.setViewLayoutMarginDimen(R.id.line1, RemoteViews.MARGIN_END, 0); + contentView.setViewLayoutMarginDimen(R.id.text, RemoteViews.MARGIN_END, 0); } } @@ -11080,7 +11085,8 @@ public class Notification implements Parcelable if (viewId == R.id.notification_header) { views.setInt(R.id.notification_header, "setTopLineExtraMarginEnd", marginEnd); } else { - views.setViewLayoutMarginEnd(viewId, marginEnd); + views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, + marginEnd, TypedValue.COMPLEX_UNIT_PX); } if (mRightIconVisible) { views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible, diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java index 7f1ee302903b..19de396c4a4a 100644 --- a/core/java/android/util/TypedValue.java +++ b/core/java/android/util/TypedValue.java @@ -17,8 +17,14 @@ package android.util; import android.annotation.AnyRes; +import android.annotation.FloatRange; +import android.annotation.IntDef; +import android.annotation.IntRange; import android.content.pm.ActivityInfo.Config; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Container for a dynamically typed data value. Primarily used with * {@link android.content.res.Resources} for holding resource values. @@ -95,6 +101,18 @@ public class TypedValue { * defined below. */ public static final int COMPLEX_UNIT_MASK = 0xf; + /** @hide **/ + @IntDef(prefix = "COMPLEX_UNIT_", value = { + COMPLEX_UNIT_PX, + COMPLEX_UNIT_DIP, + COMPLEX_UNIT_SP, + COMPLEX_UNIT_PT, + COMPLEX_UNIT_IN, + COMPLEX_UNIT_MM, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ComplexDimensionUnit {} + /** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */ public static final int COMPLEX_UNIT_PX = 0; /** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent @@ -381,7 +399,7 @@ public class TypedValue { * @return The complex floating point value multiplied by the appropriate * metrics depending on its unit. */ - public static float applyDimension(int unit, float value, + public static float applyDimension(@ComplexDimensionUnit int unit, float value, DisplayMetrics metrics) { switch (unit) { @@ -417,6 +435,130 @@ public class TypedValue { } /** + * Construct a complex data integer. This validates the radix and the magnitude of the + * mantissa, and sets the {@link TypedValue#COMPLEX_MANTISSA_MASK} and + * {@link TypedValue#COMPLEX_RADIX_MASK} components as provided. The units are not set. + ** + * @param mantissa an integer representing the mantissa. + * @param radix a radix option, e.g. {@link TypedValue#COMPLEX_RADIX_23p0}. + * @return A complex data integer representing the value. + * @hide + */ + private static int createComplex(@IntRange(from = -0x800000, to = 0x7FFFFF) int mantissa, + int radix) { + if (mantissa < -0x800000 || mantissa >= 0x800000) { + throw new IllegalArgumentException("Magnitude of mantissa is too large: " + mantissa); + } + if (radix < TypedValue.COMPLEX_RADIX_23p0 || radix > TypedValue.COMPLEX_RADIX_0p23) { + throw new IllegalArgumentException("Invalid radix: " + radix); + } + return ((mantissa & TypedValue.COMPLEX_MANTISSA_MASK) << TypedValue.COMPLEX_MANTISSA_SHIFT) + | (radix << TypedValue.COMPLEX_RADIX_SHIFT); + } + + /** + * Convert a base value to a complex data integer. This sets the {@link + * TypedValue#COMPLEX_MANTISSA_MASK} and {@link TypedValue#COMPLEX_RADIX_MASK} fields of the + * data to create a floating point representation of the given value. The units are not set. + * + * <p>This is the inverse of {@link TypedValue#complexToFloat(int)}. + * + * @param value An integer value. + * @return A complex data integer representing the value. + * @hide + */ + public static int intToComplex(int value) { + if (value < -0x800000 || value >= 0x800000) { + throw new IllegalArgumentException("Magnitude of the value is too large: " + value); + } + return createComplex(value, TypedValue.COMPLEX_RADIX_23p0); + } + + /** + * Convert a base value to a complex data integer. This sets the {@link + * TypedValue#COMPLEX_MANTISSA_MASK} and {@link TypedValue#COMPLEX_RADIX_MASK} fields of the + * data to create a floating point representation of the given value. The units are not set. + * + * <p>This is the inverse of {@link TypedValue#complexToFloat(int)}. + * + * @param value A floating point value. + * @return A complex data integer representing the value. + * @hide + */ + public static int floatToComplex(@FloatRange(from = -0x800000, to = 0x7FFFFF) float value) { + // validate that the magnitude fits in this representation + if (value < (float) -0x800000 - .5f || value >= (float) 0x800000 - .5f) { + throw new IllegalArgumentException("Magnitude of the value is too large: " + value); + } + try { + // If there's no fraction, use integer representation, as that's clearer + if (value == (float) (int) value) { + return createComplex((int) value, TypedValue.COMPLEX_RADIX_23p0); + } + float absValue = Math.abs(value); + // If the magnitude is 0, we don't need any magnitude digits + if (absValue < 1f) { + return createComplex(Math.round(value * (1 << 23)), TypedValue.COMPLEX_RADIX_0p23); + } + // If the magnitude is less than 2^8, use 8 magnitude digits + if (absValue < (float) (1 << 8)) { + return createComplex(Math.round(value * (1 << 15)), TypedValue.COMPLEX_RADIX_8p15); + } + // If the magnitude is less than 2^16, use 16 magnitude digits + if (absValue < (float) (1 << 16)) { + return createComplex(Math.round(value * (1 << 7)), TypedValue.COMPLEX_RADIX_16p7); + } + // The magnitude requires all 23 digits + return createComplex(Math.round(value), TypedValue.COMPLEX_RADIX_23p0); + } catch (IllegalArgumentException ex) { + // Wrap exception so as to include the value argument in the message. + throw new IllegalArgumentException("Unable to convert value to complex: " + value, ex); + } + } + + /** + * <p>Creates a complex data integer that stores a dimension value and units. + * + * <p>The resulting value can be passed to e.g. + * {@link TypedValue#complexToDimensionPixelOffset(int, DisplayMetrics)} to calculate the pixel + * value for the dimension. + * + * @param value the value of the dimension + * @param units the units of the dimension, e.g. {@link TypedValue#COMPLEX_UNIT_DIP} + * @return A complex data integer representing the value and units of the dimension. + * @hide + */ + public static int createComplexDimension( + @IntRange(from = -0x800000, to = 0x7FFFFF) int value, + @ComplexDimensionUnit int units) { + if (units < TypedValue.COMPLEX_UNIT_PX || units > TypedValue.COMPLEX_UNIT_MM) { + throw new IllegalArgumentException("Must be a valid COMPLEX_UNIT_*: " + units); + } + return intToComplex(value) | units; + } + + /** + * <p>Creates a complex data integer that stores a dimension value and units. + * + * <p>The resulting value can be passed to e.g. + * {@link TypedValue#complexToDimensionPixelOffset(int, DisplayMetrics)} to calculate the pixel + * value for the dimension. + * + * @param value the value of the dimension + * @param units the units of the dimension, e.g. {@link TypedValue#COMPLEX_UNIT_DIP} + * @return A complex data integer representing the value and units of the dimension. + * @hide + */ + public static int createComplexDimension( + @FloatRange(from = -0x800000, to = 0x7FFFFF) float value, + @ComplexDimensionUnit int units) { + if (units < TypedValue.COMPLEX_UNIT_PX || units > TypedValue.COMPLEX_UNIT_MM) { + throw new IllegalArgumentException("Must be a valid COMPLEX_UNIT_*: " + units); + } + return floatToComplex(value) | units; + } + + /** * Converts a complex data value holding a fraction to its final floating * point value. The given <var>data</var> must be structured as a * {@link #TYPE_FRACTION}. diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 4ba1ca8989b7..4a84851eb075 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -18,6 +18,7 @@ package android.widget; import android.annotation.ColorInt; import android.annotation.DimenRes; +import android.annotation.IdRes; import android.annotation.IntDef; import android.annotation.LayoutRes; import android.annotation.NonNull; @@ -63,12 +64,15 @@ import android.util.ArrayMap; import android.util.IntArray; import android.util.Log; import android.util.Pair; +import android.util.TypedValue; +import android.util.TypedValue.ComplexDimensionUnit; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.LayoutInflater.Filter; import android.view.RemotableViewMethod; import android.view.View; import android.view.ViewGroup; +import android.view.ViewGroup.MarginLayoutParams; import android.view.ViewStub; import android.widget.AdapterView.OnItemClickListener; @@ -173,6 +177,48 @@ public class RemoteViews implements Parcelable, Filter { private static final int SET_INT_TAG_TAG = 22; /** @hide **/ + @IntDef(prefix = "MARGIN_", value = { + MARGIN_LEFT, + MARGIN_TOP, + MARGIN_RIGHT, + MARGIN_BOTTOM, + MARGIN_START, + MARGIN_END + }) + @Retention(RetentionPolicy.SOURCE) + public @interface MarginType {} + /** + * The value will apply to the marginLeft. + * @hide + */ + public static final int MARGIN_LEFT = 0; + /** + * The value will apply to the marginTop. + * @hide + */ + public static final int MARGIN_TOP = 1; + /** + * The value will apply to the marginRight. + * @hide + */ + public static final int MARGIN_RIGHT = 2; + /** + * The value will apply to the marginBottom. + * @hide + */ + public static final int MARGIN_BOTTOM = 3; + /** + * The value will apply to the marginStart. + * @hide + */ + public static final int MARGIN_START = 4; + /** + * The value will apply to the marginEnd. + * @hide + */ + public static final int MARGIN_END = 5; + + /** @hide **/ @IntDef(flag = true, value = { FLAG_REAPPLY_DISALLOWED, FLAG_WIDGET_IS_COLLECTION_CHILD, @@ -1730,8 +1776,16 @@ public class RemoteViews implements Parcelable, Filter { final ViewGroup targetVg = (ViewGroup) target.mRoot; - // Clear all children when nested views omitted - target.mChildren = null; + if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { + // Clear all children when there's no excepted view + target.mChildren = null; + } else { + // Remove just the children which don't match the excepted view + target.mChildren.removeIf(childTree -> childTree.mRoot.getId() != mViewIdToKeep); + if (target.mChildren.isEmpty()) { + target.mChildren = null; + } + } return new RuntimeAction() { @Override public void apply(View root, ViewGroup rootParent, OnClickHandler handler) @@ -1922,13 +1976,13 @@ public class RemoteViews implements Parcelable, Filter { * Helper action to set text size on a TextView in any supported units. */ private class TextViewSizeAction extends Action { - public TextViewSizeAction(int viewId, int units, float size) { + TextViewSizeAction(@IdRes int viewId, @ComplexDimensionUnit int units, float size) { this.viewId = viewId; this.units = units; this.size = size; } - public TextViewSizeAction(Parcel parcel) { + TextViewSizeAction(Parcel parcel) { viewId = parcel.readInt(); units = parcel.readInt(); size = parcel.readFloat(); @@ -2004,36 +2058,56 @@ public class RemoteViews implements Parcelable, Filter { */ private static class LayoutParamAction extends Action { - /** Set marginEnd */ - public static final int LAYOUT_MARGIN_END_DIMEN = 1; - /** Set width */ - public static final int LAYOUT_WIDTH = 2; - public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3; - public static final int LAYOUT_MARGIN_END = 4; + static final int LAYOUT_MARGIN_LEFT = MARGIN_LEFT; + static final int LAYOUT_MARGIN_TOP = MARGIN_TOP; + static final int LAYOUT_MARGIN_RIGHT = MARGIN_RIGHT; + static final int LAYOUT_MARGIN_BOTTOM = MARGIN_BOTTOM; + static final int LAYOUT_MARGIN_START = MARGIN_START; + static final int LAYOUT_MARGIN_END = MARGIN_END; + static final int LAYOUT_WIDTH = 8; + static final int LAYOUT_HEIGHT = 9; final int mProperty; + final boolean mIsDimen; final int mValue; /** * @param viewId ID of the view alter * @param property which layout parameter to alter * @param value new value of the layout parameter + * @param units the units of the given value + */ + LayoutParamAction(@IdRes int viewId, int property, float value, + @ComplexDimensionUnit int units) { + this.viewId = viewId; + this.mProperty = property; + this.mIsDimen = false; + this.mValue = TypedValue.createComplexDimension(value, units); + } + + /** + * @param viewId ID of the view alter + * @param property which layout parameter to alter + * @param dimen new dimension with the value of the layout parameter */ - public LayoutParamAction(int viewId, int property, int value) { + LayoutParamAction(@IdRes int viewId, int property, @DimenRes int dimen) { this.viewId = viewId; this.mProperty = property; - this.mValue = value; + this.mIsDimen = true; + this.mValue = dimen; } public LayoutParamAction(Parcel parcel) { viewId = parcel.readInt(); mProperty = parcel.readInt(); + mIsDimen = parcel.readBoolean(); mValue = parcel.readInt(); } public void writeToParcel(Parcel dest, int flags) { dest.writeInt(viewId); dest.writeInt(mProperty); + dest.writeBoolean(mIsDimen); dest.writeInt(mValue); } @@ -2047,26 +2121,49 @@ public class RemoteViews implements Parcelable, Filter { if (layoutParams == null) { return; } - int value = mValue; switch (mProperty) { - case LAYOUT_MARGIN_END_DIMEN: - value = resolveDimenPixelOffset(target, mValue); - // fall-through - case LAYOUT_MARGIN_END: - if (layoutParams instanceof ViewGroup.MarginLayoutParams) { - ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(value); + case LAYOUT_MARGIN_LEFT: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).leftMargin = getPixelOffset(target); target.setLayoutParams(layoutParams); } break; - case LAYOUT_MARGIN_BOTTOM_DIMEN: - if (layoutParams instanceof ViewGroup.MarginLayoutParams) { - int resolved = resolveDimenPixelOffset(target, mValue); - ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved; + case LAYOUT_MARGIN_TOP: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).topMargin = getPixelOffset(target); + target.setLayoutParams(layoutParams); + } + break; + case LAYOUT_MARGIN_RIGHT: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).rightMargin = getPixelOffset(target); + target.setLayoutParams(layoutParams); + } + break; + case LAYOUT_MARGIN_BOTTOM: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).bottomMargin = getPixelOffset(target); + target.setLayoutParams(layoutParams); + } + break; + case LAYOUT_MARGIN_START: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).setMarginStart(getPixelOffset(target)); + target.setLayoutParams(layoutParams); + } + break; + case LAYOUT_MARGIN_END: + if (layoutParams instanceof MarginLayoutParams) { + ((MarginLayoutParams) layoutParams).setMarginEnd(getPixelOffset(target)); target.setLayoutParams(layoutParams); } break; case LAYOUT_WIDTH: - layoutParams.width = mValue; + layoutParams.width = getPixelSize(target); + target.setLayoutParams(layoutParams); + break; + case LAYOUT_HEIGHT: + layoutParams.height = getPixelSize(target); target.setLayoutParams(layoutParams); break; default: @@ -2074,11 +2171,26 @@ public class RemoteViews implements Parcelable, Filter { } } - private static int resolveDimenPixelOffset(View target, int value) { - if (value == 0) { - return 0; + private int getPixelOffset(View target) { + if (mIsDimen) { + if (mValue == 0) { + return 0; + } + return target.getResources().getDimensionPixelOffset(mValue); + } + return TypedValue.complexToDimensionPixelOffset(mValue, + target.getResources().getDisplayMetrics()); + } + + private int getPixelSize(View target) { + if (mIsDimen) { + if (mValue == 0) { + return 0; + } + return target.getResources().getDimensionPixelSize(mValue); } - return target.getContext().getResources().getDimensionPixelOffset(value); + return TypedValue.complexToDimensionPixelSize(mValue, + target.getResources().getDisplayMetrics()); } @Override @@ -2512,6 +2624,7 @@ public class RemoteViews implements Parcelable, Filter { * @param nestedView {@link RemoteViews} that describes the child. */ public void addView(int viewId, RemoteViews nestedView) { + // Clear all children when nested views omitted addAction(nestedView == null ? new ViewGroupActionRemove(viewId) : new ViewGroupActionAdd(viewId, nestedView)); @@ -3044,57 +3157,94 @@ public class RemoteViews implements Parcelable, Filter { } /** - * @hide - * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}. + * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}. * Only works if the {@link View#getLayoutParams()} supports margins. - * Hidden for now since we don't want to support this for all different layout margins yet. * * @param viewId The id of the view to change - * @param endMarginDimen a dimen resource to read the margin from or 0 to clear the margin. + * @param type The margin being set e.g. {@link #MARGIN_END} + * @param dimen a dimension resource to apply to the margin, or 0 to clear the margin. + * @hide */ - public void setViewLayoutMarginEndDimen(int viewId, @DimenRes int endMarginDimen) { - addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END_DIMEN, - endMarginDimen)); + public void setViewLayoutMarginDimen(@IdRes int viewId, @MarginType int type, + @DimenRes int dimen) { + addAction(new LayoutParamAction(viewId, type, dimen)); } /** - * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}. + * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}. * Only works if the {@link View#getLayoutParams()} supports margins. - * Hidden for now since we don't want to support this for all different layout margins yet. + * + * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0. + * Setting margins in pixels will behave poorly when the RemoteViews object is used on a + * display with a different density. * * @param viewId The id of the view to change - * @param endMargin a value in pixels for the end margin. + * @param type The margin being set e.g. {@link #MARGIN_END} + * @param value a value for the margin the given units. + * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} * @hide */ - public void setViewLayoutMarginEnd(int viewId, @DimenRes int endMargin) { - addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END, - endMargin)); + public void setViewLayoutMargin(@IdRes int viewId, @MarginType int type, float value, + @ComplexDimensionUnit int units) { + addAction(new LayoutParamAction(viewId, type, value, units)); } /** - * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}. + * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} except that you may + * provide the value in any dimension units. * - * @param bottomMarginDimen a dimen resource to read the margin from or 0 to clear the margin. + * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0, + * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}. + * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a + * display with a different density. + * + * @param width Width of the view in the given units + * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} * @hide */ - public void setViewLayoutMarginBottomDimen(int viewId, @DimenRes int bottomMarginDimen) { - addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM_DIMEN, - bottomMarginDimen)); + public void setViewLayoutWidth(@IdRes int viewId, float width, + @ComplexDimensionUnit int units) { + addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, width, units)); } /** - * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}. + * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} with + * the result of {@link Resources#getDimensionPixelSize(int)}. * - * @param layoutWidth one of 0, MATCH_PARENT or WRAP_CONTENT. Other sizes are not allowed - * because they behave poorly when the density changes. + * @param widthDimen the dimension resource for the view's width * @hide */ - public void setViewLayoutWidth(int viewId, int layoutWidth) { - if (layoutWidth != 0 && layoutWidth != ViewGroup.LayoutParams.MATCH_PARENT - && layoutWidth != ViewGroup.LayoutParams.WRAP_CONTENT) { - throw new IllegalArgumentException("Only supports 0, WRAP_CONTENT and MATCH_PARENT"); - } - mActions.add(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, layoutWidth)); + public void setViewLayoutWidthDimen(@IdRes int viewId, @DimenRes int widthDimen) { + addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, widthDimen)); + } + + /** + * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} except that you may + * provide the value in any dimension units. + * + * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0, + * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}. + * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a + * display with a different density. + * + * @param height height of the view in the given units + * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} + * @hide + */ + public void setViewLayoutHeight(@IdRes int viewId, float height, + @ComplexDimensionUnit int units) { + addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, height, units)); + } + + /** + * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} with + * the result of {@link Resources#getDimensionPixelSize(int)}. + * + * @param heightDimen a dimen resource to read the height from. + * @hide + */ + public void setViewLayoutHeightDimen(@IdRes int viewId, @DimenRes int heightDimen) { + addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, heightDimen)); } /** diff --git a/core/tests/coretests/src/android/util/TypedValueTest.kt b/core/tests/coretests/src/android/util/TypedValueTest.kt new file mode 100644 index 000000000000..7a05d970de33 --- /dev/null +++ b/core/tests/coretests/src/android/util/TypedValueTest.kt @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util + +import androidx.test.filters.LargeTest +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import kotlin.math.abs +import kotlin.math.min +import kotlin.math.roundToInt + +@RunWith(AndroidJUnit4::class) +class TypedValueTest { + @LargeTest + @Test + fun testFloatToComplex() { + fun assertRoundTripEquals(value: Float, expectedRadix: Int? = null) { + val complex = TypedValue.floatToComplex(value) + // Ensure values are accurate within .5% of the original value and within .5 + val delta = min(abs(value) / 512f, .5f) + assertEquals(value, TypedValue.complexToFloat(complex), delta) + // If expectedRadix is provided, validate it + if (expectedRadix != null) { + val actualRadix = ((complex shr TypedValue.COMPLEX_RADIX_SHIFT) + and TypedValue.COMPLEX_RADIX_MASK) + assertEquals("Incorrect radix for $value:", expectedRadix, actualRadix) + } + } + + assertRoundTripEquals(0f, TypedValue.COMPLEX_RADIX_23p0) + + assertRoundTripEquals(0.5f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(0.05f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(0.005f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(0.0005f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(0.00005f, TypedValue.COMPLEX_RADIX_0p23) + + assertRoundTripEquals(1.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(10.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(100.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(255.5f, TypedValue.COMPLEX_RADIX_8p15) // 2^8 - .5 + + assertRoundTripEquals(256.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^8 + .5 + assertRoundTripEquals(1000.5f, TypedValue.COMPLEX_RADIX_16p7) + assertRoundTripEquals(10000.5f, TypedValue.COMPLEX_RADIX_16p7) + assertRoundTripEquals(65535.5f, TypedValue.COMPLEX_RADIX_16p7) // 2^16 - .5 + + assertRoundTripEquals(65536.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^16 + .5 + assertRoundTripEquals(100000.5f, TypedValue.COMPLEX_RADIX_23p0) + assertRoundTripEquals(1000000.5f, TypedValue.COMPLEX_RADIX_23p0) + assertRoundTripEquals(8388607.2f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.8 + + assertRoundTripEquals(-0.5f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(-0.05f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(-0.005f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(-0.0005f, TypedValue.COMPLEX_RADIX_0p23) + assertRoundTripEquals(-0.00005f, TypedValue.COMPLEX_RADIX_0p23) + + assertRoundTripEquals(-1.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(-10.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(-100.5f, TypedValue.COMPLEX_RADIX_8p15) + assertRoundTripEquals(-255.5f, TypedValue.COMPLEX_RADIX_8p15) // -2^8 + .5 + + // NOTE: -256.5f fits in COMPLEX_RADIX_8p15 but is stored with COMPLEX_RADIX_16p7 for + // simplicity of the algorithm. However, it's better not to enforce that with a test. + assertRoundTripEquals(-257.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^8 - 1.5 + assertRoundTripEquals(-1000.5f, TypedValue.COMPLEX_RADIX_16p7) + assertRoundTripEquals(-10000.5f, TypedValue.COMPLEX_RADIX_16p7) + assertRoundTripEquals(-65535.5f, TypedValue.COMPLEX_RADIX_16p7) // -2^16 + .5 + + // NOTE: -65536.5f fits in COMPLEX_RADIX_16p7 but is stored with COMPLEX_RADIX_23p0 for + // simplicity of the algorithm. However, it's better not to enforce that with a test. + assertRoundTripEquals(-65537.5f, TypedValue.COMPLEX_RADIX_23p0) // -2^16 - 1.5 + assertRoundTripEquals(-100000.5f, TypedValue.COMPLEX_RADIX_23p0) + assertRoundTripEquals(-1000000.5f, TypedValue.COMPLEX_RADIX_23p0) + assertRoundTripEquals(-8388607.5f, TypedValue.COMPLEX_RADIX_23p0) // 2^23 -.5 + + // Test for every integer value in the range... + for (i: Int in -(1 shl 23) until (1 shl 23)) { + // ... that true integers are stored as the precise integer + assertRoundTripEquals(i.toFloat(), TypedValue.COMPLEX_RADIX_23p0) + // ... that values round up when just below an integer + assertRoundTripEquals(i - .1f) + // ... that values round down when just above an integer + assertRoundTripEquals(i + .1f) + } + } + + @SmallTest + @Test(expected = IllegalArgumentException::class) + fun testFloatToComplex_failsIfValueTooLarge() { + TypedValue.floatToComplex(8388607.5f) // 2^23 - .5 + } + + @SmallTest + @Test(expected = IllegalArgumentException::class) + fun testFloatToComplex_failsIfValueTooSmall() { + TypedValue.floatToComplex(8388608.5f) // -2^23 - .5 + } + + @LargeTest + @Test + fun testIntToComplex() { + // Validates every single valid value + for (value: Int in -(1 shl 23) until (1 shl 23)) { + assertEquals(value.toFloat(), TypedValue.complexToFloat(TypedValue.intToComplex(value))) + } + } + + @SmallTest + @Test(expected = IllegalArgumentException::class) + fun testIntToComplex_failsIfValueTooLarge() { + TypedValue.intToComplex(0x800000) + } + + @SmallTest + @Test(expected = IllegalArgumentException::class) + fun testIntToComplex_failsIfValueTooSmall() { + TypedValue.intToComplex(-0x800001) + } + + @SmallTest + @Test + fun testCreateComplexDimension_appliesUnits() { + val metrics: DisplayMetrics = mock(DisplayMetrics::class.java) + metrics.density = 3.25f + + val height = 52 * metrics.density + val widthFloat = height * 16 / 9 + val widthDimen = TypedValue.createComplexDimension( + widthFloat / metrics.density, + TypedValue.COMPLEX_UNIT_DIP + ) + val widthPx = TypedValue.complexToDimensionPixelSize(widthDimen, metrics) + assertEquals(widthFloat.roundToInt(), widthPx) + } +}
\ No newline at end of file |