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

Commit 2fc6057b authored by Julia Tuttle's avatar Julia Tuttle Committed by Android (Google) Code Review
Browse files

Merge changes from topic "new-callstyle-action-layout" into main

* changes:
  CallStyle: Center icon with label on action buttons
  CallStyle: Evenly divide space for action buttons
  CallStyle: Add booleans to control new action layout
parents f7476a14 4c7ac16f
Loading
Loading
Loading
Loading
+32 −2
Original line number Original line Diff line number Diff line
@@ -5946,6 +5946,12 @@ public class Notification implements Parcelable
                // there is enough space to do so (and fall back to the left edge if not).
                // there is enough space to do so (and fall back to the left edge if not).
                big.setInt(R.id.actions, "setCollapsibleIndentDimen",
                big.setInt(R.id.actions, "setCollapsibleIndentDimen",
                        R.dimen.call_notification_collapsible_indent);
                        R.dimen.call_notification_collapsible_indent);
                if (CallStyle.USE_NEW_ACTION_LAYOUT) {
                    if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
                        Log.d(TAG, "setting evenly divided mode on action list");
                    }
                    big.setBoolean(R.id.actions, "setEvenlyDividedMode", true);
                }
            }
            }
            big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
            big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
            if (numActions > 0 && !p.mHideActions) {
            if (numActions > 0 && !p.mHideActions) {
@@ -6421,7 +6427,15 @@ public class Notification implements Parcelable
                    // Remove full-length color spans and ensure text contrast with the button fill.
                    // Remove full-length color spans and ensure text contrast with the button fill.
                    title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
                    title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
                }
                }
                button.setTextViewText(R.id.action0, ensureColorSpanContrast(title, p));
                final CharSequence label = ensureColorSpanContrast(title, p);
                if (p.mCallStyleActions && CallStyle.USE_NEW_ACTION_LAYOUT) {
                    if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
                        Log.d(TAG, "new action layout enabled, gluing instead of setting text");
                    }
                    button.setCharSequence(R.id.action0, "glueLabel", label);
                } else {
                    button.setTextViewText(R.id.action0, label);
                }
                int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
                int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
                        buttonFillColor, mInNightMode);
                        buttonFillColor, mInNightMode);
                if (tombstone) {
                if (tombstone) {
@@ -6438,7 +6452,14 @@ public class Notification implements Parcelable
                button.setColorStateList(R.id.action0, "setButtonBackground",
                button.setColorStateList(R.id.action0, "setButtonBackground",
                        ColorStateList.valueOf(buttonFillColor));
                        ColorStateList.valueOf(buttonFillColor));
                if (p.mCallStyleActions) {
                if (p.mCallStyleActions) {
                    if (CallStyle.USE_NEW_ACTION_LAYOUT) {
                        if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
                            Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
                        }
                        button.setIcon(R.id.action0, "glueIcon", action.getIcon());
                    } else {
                        button.setImageViewIcon(R.id.action0, action.getIcon());
                        button.setImageViewIcon(R.id.action0, action.getIcon());
                    }
                    boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
                    boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
                    button.setBoolean(R.id.action0, "setIsPriority", priority);
                    button.setBoolean(R.id.action0, "setIsPriority", priority);
                    int minWidthDimen =
                    int minWidthDimen =
@@ -9565,6 +9586,15 @@ public class Notification implements Parcelable
     * </pre>
     * </pre>
     */
     */
    public static class CallStyle extends Style {
    public static class CallStyle extends Style {
        /**
         * @hide
         */
        public static final boolean USE_NEW_ACTION_LAYOUT = false;
        /**
         * @hide
         */
        public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
        /**
        /**
         * @hide
         * @hide
+373 −7
Original line number Original line Diff line number Diff line
@@ -16,16 +16,30 @@


package com.android.internal.widget;
package com.android.internal.widget;


import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.BlendMode;
import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableWrapper;
import android.graphics.drawable.DrawableWrapper;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.RippleDrawable;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.style.ImageSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.ReplacementSpan;
import android.util.AttributeSet;
import android.util.AttributeSet;
import android.util.Log;
import android.view.RemotableViewMethod;
import android.view.RemotableViewMethod;
import android.widget.Button;
import android.widget.Button;
import android.widget.RemoteViews;
import android.widget.RemoteViews;
@@ -43,6 +57,14 @@ public class EmphasizedNotificationButton extends Button {
    private final GradientDrawable mBackground;
    private final GradientDrawable mBackground;
    private boolean mPriority;
    private boolean mPriority;


    private int mInitialDrawablePadding;
    private int mIconSize;

    private Drawable mIconToGlue;
    private CharSequence mLabelToGlue;
    private int mGluedLayoutDirection = LAYOUT_DIRECTION_UNDEFINED;
    private boolean mGluePending;

    public EmphasizedNotificationButton(Context context) {
    public EmphasizedNotificationButton(Context context) {
        this(context, null);
        this(context, null);
    }
    }
@@ -58,10 +80,25 @@ public class EmphasizedNotificationButton extends Button {
    public EmphasizedNotificationButton(Context context, AttributeSet attrs, int defStyleAttr,
    public EmphasizedNotificationButton(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        super(context, attrs, defStyleAttr, defStyleRes);

        mRipple = (RippleDrawable) getBackground();
        mRipple = (RippleDrawable) getBackground();
        mRipple.mutate();
        mRipple.mutate();
        DrawableWrapper inset = (DrawableWrapper) mRipple.getDrawable(0);
        DrawableWrapper inset = (DrawableWrapper) mRipple.getDrawable(0);
        mBackground = (GradientDrawable) inset.getDrawable();
        mBackground = (GradientDrawable) inset.getDrawable();

        mIconSize = mContext.getResources().getDimensionPixelSize(
                R.dimen.notification_actions_icon_drawable_size);

        try (TypedArray typedArray = context.obtainStyledAttributes(
                attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes)) {
            mInitialDrawablePadding = typedArray.getDimensionPixelSize(
                    android.R.styleable.TextView_drawablePadding, 0);
        }

        if (DEBUG_NEW_ACTION_LAYOUT) {
            Log.v(TAG, "iconSize = " + mIconSize + "px, "
                    + "initialDrawablePadding = " + mInitialDrawablePadding + "px");
        }
    }
    }


    @RemotableViewMethod
    @RemotableViewMethod
@@ -95,16 +132,245 @@ public class EmphasizedNotificationButton extends Button {
        return () -> setImageDrawable(drawable);
        return () -> setImageDrawable(drawable);
    }
    }


    private void setImageDrawable(Drawable drawable) {
    private void setImageDrawable(@Nullable Drawable drawable) {
        if (drawable != null) {
        if (drawable != null) {
            prepareIcon(drawable);
        }
        setCompoundDrawablesRelative(drawable, null, null, null);
    }

    /**
     * Sets an icon to be 'glued' to the label when this button is displayed, so the icon will stay
     * with the text if the button is wider than needed and the text isn't start-aligned.
     *
     * As with {@link #setImageIcon(Icon)}, the Icon will have its size constrained and will be set
     * to the same color as the text, and this must be called after {@link #setTextColor(int)} for
     * the latter to work.
     *
     * This must be called along with {@link #glueLabel(CharSequence)}, in any order, before the
     * button is displayed.
     */
    @RemotableViewMethod(asyncImpl = "glueIconAsync")
    public void glueIcon(@Nullable Icon icon) {
        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
        setIconToGlue(drawable);
    }

    /**
     * @hide
     */
    @RemotableViewMethod
    public Runnable glueIconAsync(@Nullable Icon icon) {
        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
        return () -> setIconToGlue(drawable);
    }

    private void setIconToGlue(@Nullable Drawable icon) {
        if (!USE_NEW_ACTION_LAYOUT) {
            Log.e(TAG, "glueIcon: new action layout disabled; doing nothing");
            return;
        }

        prepareIcon(icon);

        mIconToGlue = icon;
        mGluePending = true;

        glueIconAndLabelIfNeeded();
    }

    private void prepareIcon(@NonNull Drawable drawable) {
        drawable.mutate();
        drawable.mutate();
        drawable.setTintList(getTextColors());
        drawable.setTintList(getTextColors());
        drawable.setTintBlendMode(BlendMode.SRC_IN);
        drawable.setTintBlendMode(BlendMode.SRC_IN);
            int iconSize = mContext.getResources().getDimensionPixelSize(
        drawable.setBounds(0, 0, mIconSize, mIconSize);
                    R.dimen.notification_actions_icon_drawable_size);
            drawable.setBounds(0, 0, iconSize, iconSize);
    }
    }
        setCompoundDrawablesRelative(drawable, null, null, null);

    /**
     * Sets a label to be 'glued' to the icon when this button is displayed, so the icon will stay
     * with the text if the button is wider than needed and the text isn't start-aligned.
     *
     * This must be called along with {@link #glueIcon(Icon)}, in any order, before the button is
     * displayed.
     */
    @RemotableViewMethod(asyncImpl = "glueLabelAsync")
    public void glueLabel(@Nullable CharSequence label) {
        setLabelToGlue(label);
    }

    /**
     * @hide
     */
    @RemotableViewMethod
    public Runnable glueLabelAsync(@Nullable CharSequence label) {
        return () -> setLabelToGlue(label);
    }

    private void setLabelToGlue(@Nullable CharSequence label) {
        if (!USE_NEW_ACTION_LAYOUT) {
            Log.e(TAG, "glueLabel: new action layout disabled; doing nothing");
            return;
        }

        mLabelToGlue = label;
        mGluePending = true;

        glueIconAndLabelIfNeeded();
    }

    @Override
    public void onRtlPropertiesChanged(int layoutDirection) {
        super.onRtlPropertiesChanged(layoutDirection);

        if (DEBUG_NEW_ACTION_LAYOUT) {
            Log.v(TAG, "onRtlPropertiesChanged: layoutDirection = " + layoutDirection + ", "
                    + "gluedLayoutDirection = " + mGluedLayoutDirection);
        }

        if (layoutDirection != mGluedLayoutDirection) {
            if (DEBUG_NEW_ACTION_LAYOUT) {
                Log.d(TAG, "onRtlPropertiesChanged: layout direction changed; regluing");
            }
            mGluePending = true;
        }

        glueIconAndLabelIfNeeded();
    }

    private void glueIconAndLabelIfNeeded() {
        // Don't need to glue:

        if (!mGluePending) {
            if (DEBUG_NEW_ACTION_LAYOUT) {
                Log.v(TAG, "glueIconAndLabelIfNeeded: glue not pending; doing nothing");
            }
            return;
        }

        if (mIconToGlue == null && mLabelToGlue == null) {
            if (DEBUG_NEW_ACTION_LAYOUT) {
                Log.v(TAG, "glueIconAndLabelIfNeeded: no icon or label to glue; doing nothing");
            }
            mGluePending = false;
            return;
        }

        if (!USE_NEW_ACTION_LAYOUT) {
            Log.e(TAG, "glueIconAndLabelIfNeeded: new action layout disabled; doing nothing");
            return;
        }

        // Not ready to glue yet:

        if (!isLayoutDirectionResolved()) {
            if (DEBUG_NEW_ACTION_LAYOUT) {
                Log.v(TAG, "glueIconAndLabelIfNeeded: "
                        + "layout direction not resolved; doing nothing");
            }
            return;
        }

        // Ready to glue but don't have an icon *and* a label:
        //
        // (Note that this will *not* happen while the button is being initialized, since we won't
        // be ready to glue. This can only happen if the button is initialized and displayed and
        // *then* someone calls glueIcon or glueLabel.

        if (mIconToGlue == null) {
            Log.w(TAG, "glueIconAndLabelIfNeeded: label glued without icon; doing nothing");
            return;
        }

        if (mLabelToGlue == null) {
            Log.w(TAG, "glueIconAndLabelIfNeeded: icon glued without label; doing nothing");
            return;
        }

        // Can't glue:

        final int layoutDirection = getLayoutDirection();
        if (layoutDirection != LAYOUT_DIRECTION_LTR && layoutDirection != LAYOUT_DIRECTION_RTL) {
            Log.e(TAG, "glueIconAndLabelIfNeeded: "
                    + "resolved layout direction neither LTR nor RTL; "
                    + "doing nothing");
            return;
        }

        // No excuses left, let's glue it!

        glueIconAndLabel(layoutDirection);

        mGluePending = false;
        mGluedLayoutDirection = layoutDirection;
    }

    // Unicode replacement character
    private static final String IMAGE_SPAN_TEXT = "\ufffd";

    // Unicode no-break space
    private static final String SPACER_SPAN_TEXT = "\u00a0";

    private static final String LEFT_TO_RIGHT_ISOLATE = "\u2066";
    private static final String RIGHT_TO_LEFT_ISOLATE = "\u2067";
    private static final String FIRST_STRONG_ISOLATE = "\u2068";
    private static final String POP_DIRECTIONAL_ISOLATE = "\u2069";

    private void glueIconAndLabel(int layoutDirection) {
        final boolean rtlLayout = layoutDirection == LAYOUT_DIRECTION_RTL;

        if (DEBUG_NEW_ACTION_LAYOUT) {
            Log.d(TAG, "glueIconAndLabel: "
                    + "icon = " + mIconToGlue + ", "
                    + "iconSize = " + mIconSize + "px, "
                    + "initialDrawablePadding = " + mInitialDrawablePadding + "px, "
                    + "labelToGlue.length = " + mLabelToGlue.length() + ", "
                    + "rtlLayout = " + rtlLayout);
        }

        logIfTextDirectionNotFirstStrong();

        final SpannableStringBuilder builder = new SpannableStringBuilder();

        // The text direction of the label might not match the layout direction of the button, so
        // wrap the entire string in a LEFT-TO-RIGHT ISOLATE or RIGHT-TO-LEFT ISOLATE to match the
        // layout direction. This puts the icon, padding, and label in the right order.
        builder.append(rtlLayout ? RIGHT_TO_LEFT_ISOLATE : LEFT_TO_RIGHT_ISOLATE);

        appendSpan(builder, IMAGE_SPAN_TEXT, new ImageSpan(mIconToGlue, ALIGN_CENTER));
        appendSpan(builder, SPACER_SPAN_TEXT, new SpacerSpan(mInitialDrawablePadding));

        // If the text and layout directions are different, we would end up with the *label* in the
        // wrong direction, so wrap the label in a FIRST STRONG ISOLATE. This triggers the same
        // automatic text direction heuristic that Android uses by default.
        builder.append(FIRST_STRONG_ISOLATE);

        appendSpan(builder, mLabelToGlue, new CenterBesideImageSpan(mIconSize));

        builder.append(POP_DIRECTIONAL_ISOLATE);
        builder.append(POP_DIRECTIONAL_ISOLATE);

        setText(builder);
    }

    private void logIfTextDirectionNotFirstStrong() {
        if (!isTextDirectionResolved()) {
            Log.e(TAG, "glueIconAndLabel: text direction not resolved; "
                    + "letting View assume FIRST STRONG");
        }
        final int textDirection = getTextDirection();
        if (textDirection != TEXT_DIRECTION_FIRST_STRONG) {
            Log.w(TAG, "glueIconAndLabel: "
                    + "expected text direction TEXT_DIRECTION_FIRST_STRONG "
                    + "but found " + textDirection + "; "
                    + "will use a FIRST STRONG ISOLATE regardless");
        }
    }

    private void appendSpan(SpannableStringBuilder builder, CharSequence text, Object span) {
        final int spanStart = builder.length();
        builder.append(text);
        final int spanEnd = builder.length();
        builder.setSpan(span, spanStart, spanEnd, 0);
    }
    }


    /**
    /**
@@ -123,4 +389,104 @@ public class EmphasizedNotificationButton extends Button {
    public boolean isPriority() {
    public boolean isPriority() {
        return mPriority;
        return mPriority;
    }
    }

    private static class SpacerSpan extends ReplacementSpan {
        private int mWidth;

        SpacerSpan(int width) {
            mWidth = width;

            if (DEBUG_NEW_ACTION_LAYOUT) {
                Log.d(TAG, "width = " + mWidth + "px");
            }
        }


        @Override
        public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
                           @Nullable Paint.FontMetricsInt fontMetrics) {
            if (DEBUG_NEW_ACTION_LAYOUT) {
                Log.v(TAG, "getSize returning " + mWidth + "px");
            }

            return mWidth;
        }

        @Override
        public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end,
                         float x, int top, int y, int bottom, @NonNull Paint paint) {
            if (DEBUG_NEW_ACTION_LAYOUT) {
                Log.v(TAG, "drawing nothing");
            }

            // Draw nothing, it's a spacer.
        }

        private static final String TAG = "SpacerSpan";
    }

    private static class CenterBesideImageSpan extends MetricAffectingSpan {
        private int mImageHeight;

        private boolean mMeasured;
        private int mBaselineShiftOffset;

        CenterBesideImageSpan(int imageHeight) {
            mImageHeight = imageHeight;

            if (DEBUG_NEW_ACTION_LAYOUT) {
                Log.d(TAG, "imageHeight = " + mImageHeight + "px");
            }
        }

        @Override
        public void updateMeasureState(@NonNull TextPaint textPaint) {
            final int textHeight = (int) -textPaint.ascent();

            /*
             * We only need to shift the text *up* if the text is shorter than the image; ImageSpan
             * with ALIGN_CENTER will shift the *image* up if the text is taller than the image.
             */
            if (textHeight < mImageHeight) {
                mBaselineShiftOffset = -(mImageHeight - textHeight) / 2;
            } else {
                mBaselineShiftOffset = 0;
            }

            mMeasured = true;

            if (DEBUG_NEW_ACTION_LAYOUT) {
                Log.d(TAG, "updateMeasureState: "
                        + "imageHeight = " + mImageHeight + "px, "
                        + "textHeight = " + textHeight + "px, "
                        + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
            }

            textPaint.baselineShift += mBaselineShiftOffset;
        }

        @Override
        public void updateDrawState(TextPaint textPaint) {
            if (textPaint == null) {
                Log.e(TAG, "updateDrawState: textPaint is null; doing nothing");
                return;
            }

            if (!mMeasured) {
                Log.e(TAG, "updateDrawState: called without measure; doing nothing");
                return;
            }

            if (DEBUG_NEW_ACTION_LAYOUT) {
                Log.v(TAG, "updateDrawState: "
                        + "baselineShiftOffset = " + mBaselineShiftOffset + "px");
            }

            textPaint.baselineShift += mBaselineShiftOffset;
        }

        private static final String TAG = "CenterBesideImageSpan";
    }

    private static final String TAG = "EmphasizedNotificationButton";
}
}
+83 −5
Original line number Original line Diff line number Diff line
@@ -16,12 +16,16 @@


package com.android.internal.widget;
package com.android.internal.widget;


import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;

import android.annotation.DimenRes;
import android.annotation.DimenRes;
import android.app.Notification;
import android.app.Notification;
import android.content.Context;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.TypedArray;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.RemotableViewMethod;
import android.view.View;
import android.view.View;
@@ -41,13 +45,13 @@ import java.util.Comparator;
 */
 */
@RemoteViews.RemoteView
@RemoteViews.RemoteView
public class NotificationActionListLayout extends LinearLayout {
public class NotificationActionListLayout extends LinearLayout {

    private final int mGravity;
    private final int mGravity;
    private int mTotalWidth = 0;
    private int mTotalWidth = 0;
    private int mExtraStartPadding = 0;
    private int mExtraStartPadding = 0;
    private ArrayList<TextViewInfo> mMeasureOrderTextViews = new ArrayList<>();
    private ArrayList<TextViewInfo> mMeasureOrderTextViews = new ArrayList<>();
    private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
    private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
    private boolean mEmphasizedMode;
    private boolean mEmphasizedMode;
    private boolean mEvenlyDividedMode;
    private int mDefaultPaddingBottom;
    private int mDefaultPaddingBottom;
    private int mDefaultPaddingTop;
    private int mDefaultPaddingTop;
    private int mEmphasizedPaddingTop;
    private int mEmphasizedPaddingTop;
@@ -124,6 +128,42 @@ public class NotificationActionListLayout extends LinearLayout {
        }
        }
    }
    }


    private int measureAndReturnEvenlyDividedWidth(int heightMeasureSpec, int innerWidth) {
        final int numChildren = getChildCount();
        int childMarginSum = 0;
        for (int i = 0; i < numChildren; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
                childMarginSum += lp.leftMargin + lp.rightMargin;
            }
        }

        final int innerWidthMinusChildMargins = innerWidth - childMarginSum;
        final int childWidth = innerWidthMinusChildMargins / mNumNotGoneChildren;
        final int childWidthMeasureSpec =
                MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);

        if (DEBUG_NEW_ACTION_LAYOUT) {
            Log.v(TAG, "measuring evenly divided width: "
                    + "numChildren = " + numChildren + ", "
                    + "innerWidth = " + innerWidth + "px, "
                    + "childMarginSum = " + childMarginSum + "px, "
                    + "innerWidthMinusChildMargins = " + innerWidthMinusChildMargins + "px, "
                    + "childWidth = " + childWidth + "px, "
                    + "childWidthMeasureSpec = " + MeasureSpec.toString(childWidthMeasureSpec));
        }

        for (int i = 0; i < numChildren; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                child.measure(childWidthMeasureSpec, heightMeasureSpec);
            }
        }

        return innerWidth;
    }

    private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
    private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
            boolean collapsePriorityActions) {
            boolean collapsePriorityActions) {
        final int numChildren = getChildCount();
        final int numChildren = getChildCount();
@@ -208,12 +248,17 @@ public class NotificationActionListLayout extends LinearLayout {
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        countAndRebuildMeasureOrder();
        countAndRebuildMeasureOrder();
        final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
        final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
        int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
        int usedWidth;
        if (mEvenlyDividedMode) {
            usedWidth = measureAndReturnEvenlyDividedWidth(heightMeasureSpec, innerWidth);
        } else {
            usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
                    false /* collapsePriorityButtons */);
                    false /* collapsePriorityButtons */);
            if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
            if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
                usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
                usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
                        true /* collapsePriorityButtons */);
                        true /* collapsePriorityButtons */);
            }
            }
        }


        mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
        mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
        setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
        setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
@@ -351,6 +396,38 @@ public class NotificationActionListLayout extends LinearLayout {
        }
        }
    }
    }


    /**
     * Sets whether the available width should be distributed evenly among the action buttons.
     *
     * When enabled, the available width (after subtracting this layout's padding and all of the
     * buttons' margins) is divided by the number of (not-GONE) buttons, and each button is forced
     * to that exact width, even if it is less <em>or more</em> width than they need.
     *
     * When disabled, the available width is allocated as buttons need; if that exceeds the
     * available width, priority buttons are collapsed to just their icon to save space.
     *
     * @param evenlyDividedMode whether to enable evenly divided mode
     */
    @RemotableViewMethod
    public void setEvenlyDividedMode(boolean evenlyDividedMode) {
        if (evenlyDividedMode && !USE_NEW_ACTION_LAYOUT) {
            Log.e(TAG, "setEvenlyDividedMode(true) called with new action layout disabled; "
                    + "leaving evenly divided mode disabled");
            return;
        }

        if (evenlyDividedMode == mEvenlyDividedMode) {
            return;
        }

        if (DEBUG_NEW_ACTION_LAYOUT) {
            Log.v(TAG, "evenlyDividedMode changed to " + evenlyDividedMode + "; "
                    + "requesting layout");
        }
        mEvenlyDividedMode = evenlyDividedMode;
        requestLayout();
    }

    /**
    /**
     * Set whether the list is in a mode where some actions are emphasized. This will trigger an
     * Set whether the list is in a mode where some actions are emphasized. This will trigger an
     * equal measuring where all actions are full height and change a few parameters like
     * equal measuring where all actions are full height and change a few parameters like
@@ -410,4 +487,5 @@ public class NotificationActionListLayout extends LinearLayout {
        }
        }
    }
    }


    private static final String TAG = "NotificationActionListLayout";
}
}