Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a2114a56 authored by Jeff DeCew's avatar Jeff DeCew
Browse files

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
parent 60decee6
Loading
Loading
Loading
Loading
+16 −10
Original line number Diff line number Diff line
@@ -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,
+143 −1
Original line number Diff line number Diff line
@@ -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) {
@@ -416,6 +434,130 @@ public class TypedValue {
        return complexToDimension(data, metrics);
    }

    /**
     * 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 
+205 −55

File changed.

Preview size limit exceeded, changes collapsed.

+155 −0
Original line number Diff line number Diff line
/*
 * 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