summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeff DeCew <jeffdq@google.com> 2020-12-21 13:33:02 -0500
committer Jeff DeCew <jeffdq@google.com> 2021-01-07 14:30:57 -0500
commita2114a56c862e13ae3a85f9cc8e8666be3deb69b (patch)
tree79ab9544be25aed79814386e6bc18f446a620d0a
parent60decee64a031fc51d48e1a003cb74b3136c6894 (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.java26
-rw-r--r--core/java/android/util/TypedValue.java144
-rw-r--r--core/java/android/widget/RemoteViews.java260
-rw-r--r--core/tests/coretests/src/android/util/TypedValueTest.kt155
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