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

Commit 21147c02 authored by Jeff DeCew's avatar Jeff DeCew
Browse files

Show LargeIcon as wide for apps targeting S.

* This does not inhibit the grouping behavior which demotes this image and shows it in a smaller square area on the left.
* This also converts the Notification class to calculate margins and sizes in DIP instead of PX, as that is more resilient.

Fixes: 175409684
Test: atest NotificationTemplateTest
Change-Id: I35d28c8df341dbbac2774026c6ca749e296c0482
parent a2114a56
Loading
Loading
Loading
Loading
+72 −36
Original line number Diff line number Diff line
@@ -30,7 +30,6 @@ import android.annotation.IdRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -218,6 +217,11 @@ public class Notification implements Parcelable
     */
    private static final int MAX_REPLY_HISTORY = 5;

    /**
     * Maximum aspect ratio of the large icon. 16:9
     */
    private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f;

    /**
     * Maximum number of (generic) action buttons in a notification (contextual action buttons are
     * handled separately).
@@ -4229,9 +4233,9 @@ public class Notification implements Parcelable
        /**
         * Add a large icon to the notification content view.
         *
         * In the platform template, this image will be shown on the left of the notification view
         * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
         * badge atop the large icon).
         * In the platform template, this image will be shown either on the right of the
         * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
         * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
         */
        @NonNull
        public Builder setLargeIcon(Bitmap b) {
@@ -4241,9 +4245,9 @@ public class Notification implements Parcelable
        /**
         * Add a large icon to the notification content view.
         *
         * In the platform template, this image will be shown on the left of the notification view
         * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small
         * badge atop the large icon).
         * In the platform template, this image will be shown either on the right of the
         * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped)
         * on the left in place of the {@link #setSmallIcon(Icon) small icon}.
         */
        @NonNull
        public Builder setLargeIcon(Icon icon) {
@@ -5111,8 +5115,7 @@ public class Notification implements Parcelable
            if (result == null) {
                result = new TemplateBindResult();
            }
            final boolean largeIconShown = bindLargeIcon(contentView, p);
            calculateLargeIconMarginEnd(largeIconShown, result);
            bindLargeIcon(contentView, p, result);
            if (p.mHeaderless) {
                // views in the headerless (collapsed) state
                result.mHeadingExtraMarginSet.applyToView(contentView,
@@ -5124,28 +5127,54 @@ public class Notification implements Parcelable
            }
        }

        private void calculateLargeIconMarginEnd(boolean largeIconShown,
        // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps,
        // a use case that is not supported by the Compat Framework library.  Workarounds to resolve
        // the change's state in NotificationManagerService were very complex. These behavior
        // changes are entirely visual, and should otherwise be undetectable by apps.
        @SuppressWarnings("AndroidFrameworkCompatChange")
        private void calculateLargeIconDimens(boolean largeIconShown,
                @NonNull TemplateBindResult result) {
            final Resources resources = mContext.getResources();
            final int contentMargin = resources.getDimensionPixelOffset(
                    R.dimen.notification_content_margin_end);
            final int expanderSize = resources.getDimensionPixelSize(
                    R.dimen.notification_header_expand_icon_size) - contentMargin;
            final int extraMarginEndIfVisible = resources.getDimensionPixelSize(
                    R.dimen.notification_right_icon_size) + contentMargin;
            result.setRightIconState(largeIconShown, extraMarginEndIfVisible, expanderSize);
            final float density = resources.getDisplayMetrics().density;
            final float contentMarginDp = resources.getDimension(
                    R.dimen.notification_content_margin_end) / density;
            final float expanderSizeDp = resources.getDimension(
                    R.dimen.notification_header_expand_icon_size) / density - contentMarginDp;
            final float viewHeightDp = resources.getDimension(
                    R.dimen.notification_right_icon_size) / density;
            float viewWidthDp = viewHeightDp;  // icons are 1:1 by default
            if (largeIconShown && (
                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S
                            || DevFlags.shouldBackportSNotifRules(mContext.getContentResolver()))) {
                Drawable drawable = mN.mLargeIcon.loadDrawable(mContext);
                if (drawable != null) {
                    int iconWidth = drawable.getIntrinsicWidth();
                    int iconHeight = drawable.getIntrinsicHeight();
                    if (iconWidth > iconHeight && iconHeight > 0) {
                        final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO;
                        viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight,
                                maxViewWidthDp);
                    }
                }
            }
            final float extraMarginEndDpIfVisible = viewWidthDp + contentMarginDp;
            result.setRightIconState(largeIconShown, viewWidthDp,
                    extraMarginEndDpIfVisible, expanderSizeDp);
        }

        /**
         * Bind the large icon.
         * @return if the largeIcon is visible
         */
        private boolean bindLargeIcon(RemoteViews contentView, StandardTemplateParams p) {
        private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p,
                @NonNull TemplateBindResult result) {
            if (mN.mLargeIcon == null && mN.largeIcon != null) {
                mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
            }
            boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon;
            calculateLargeIconDimens(showLargeIcon, result);
            if (showLargeIcon) {
                contentView.setViewLayoutWidth(R.id.right_icon,
                        result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP);
                contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
                contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
                processLargeLegacyIcon(mN.mLargeIcon, contentView, p);
@@ -5155,7 +5184,6 @@ public class Notification implements Parcelable
                // visibility) is used by NotificationGroupingUtil to set the visibility.
                contentView.setImageViewIcon(R.id.right_icon, null);
            }
            return showLargeIcon;
        }

        private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) {
@@ -7793,7 +7821,8 @@ public class Notification implements Parcelable
                // NOTE: This template doesn't support moving this icon to the left, so we don't
                // need to fully apply the MarginSet
                contentView.setViewLayoutMargin(R.id.notification_messaging, RemoteViews.MARGIN_END,
                        bindResult.mHeadingExtraMarginSet.getValue(), TypedValue.COMPLEX_UNIT_PX);
                        bindResult.mHeadingExtraMarginSet.getDpValue(),
                        TypedValue.COMPLEX_UNIT_DIP);
            }
            contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
                    mBuilder.isColorized(p)
@@ -8792,7 +8821,8 @@ public class Notification implements Parcelable
                // also update the end margin to account for the large icon or expander
                Resources resources = mBuilder.mContext.getResources();
                result.mTitleMarginSet.applyToView(remoteViews, R.id.notification_main_column,
                        resources.getDimensionPixelOffset(R.dimen.notification_content_margin_end));
                        resources.getDimension(R.dimen.notification_content_margin_end)
                                / resources.getDisplayMetrics().density);
            }
        }

@@ -11030,6 +11060,7 @@ public class Notification implements Parcelable
     */
    private static class TemplateBindResult {
        boolean mRightIconVisible;
        float mRightIconWidthDp;

        /**
         * The margin end that needs to be added to the heading so that it won't overlap
@@ -11054,11 +11085,13 @@ public class Notification implements Parcelable
         */
        public final MarginSet mTitleMarginSet = new MarginSet();

        public void setRightIconState(boolean visible, int marginEndIfVisible, int expanderSize) {
        public void setRightIconState(boolean visible, float widthDp,
                float marginEndDpIfVisible, float expanderSizeDp) {
            mRightIconVisible = visible;
            mHeadingExtraMarginSet.setValues(0, marginEndIfVisible);
            mHeadingFullMarginSet.setValues(expanderSize, marginEndIfVisible + expanderSize);
            mTitleMarginSet.setValues(0, marginEndIfVisible + expanderSize);
            mRightIconWidthDp = widthDp;
            mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible);
            mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp);
            mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp);
        }

        /**
@@ -11067,10 +11100,10 @@ public class Notification implements Parcelable
         * left_icon and adjust the margins, and to undo that change as well.
         */
        private class MarginSet {
            private int mValueIfGone;
            private int mValueIfVisible;
            private float mValueIfGone;
            private float mValueIfVisible;

            public void setValues(int valueIfGone, int valueIfVisible) {
            public void setValues(float valueIfGone, float valueIfVisible) {
                mValueIfGone = valueIfGone;
                mValueIfVisible = valueIfVisible;
            }
@@ -11080,23 +11113,26 @@ public class Notification implements Parcelable
            }

            public void applyToView(@NonNull RemoteViews views, @IdRes int viewId,
                    @Px int extraMargin) {
                final int marginEnd = getValue() + extraMargin;
                    float extraMarginDp) {
                final float marginEndDp = getDpValue() + extraMarginDp;
                if (viewId == R.id.notification_header) {
                    views.setInt(R.id.notification_header, "setTopLineExtraMarginEnd", marginEnd);
                    views.setFloat(R.id.notification_header,
                            "setTopLineExtraMarginEndDp", marginEndDp);
                } else {
                    views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END,
                                    marginEnd, TypedValue.COMPLEX_UNIT_PX);
                                    marginEndDp, TypedValue.COMPLEX_UNIT_DIP);
                }
                if (mRightIconVisible) {
                    views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible,
                            mValueIfVisible + extraMargin);
                            TypedValue.createComplexDimension(
                                    mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
                    views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone,
                            mValueIfGone + extraMargin);
                            TypedValue.createComplexDimension(
                                    mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP));
                }
            }

            public int getValue() {
            public float getDpValue() {
                return mRightIconVisible ? mValueIfVisible : mValueIfGone;
            }
        }
+13 −2
Original line number Diff line number Diff line
@@ -156,13 +156,24 @@ public class NotificationHeaderView extends FrameLayout {
     * Sets the extra margin at the end of the top line of left-aligned text + icons.
     * This value will have the margin required to accommodate the expand button added to it.
     *
     * @param extraMarginEnd extra margin
     * @param extraMarginEnd extra margin in px
     */
    @RemotableViewMethod
    public void setTopLineExtraMarginEnd(int extraMarginEnd) {
        mTopLineView.setHeaderTextMarginEnd(extraMarginEnd + mHeadingEndMargin);
    }

    /**
     * Sets the extra margin at the end of the top line of left-aligned text + icons.
     * This value will have the margin required to accommodate the expand button added to it.
     *
     * @param extraMarginEndDp extra margin in dp
     */
    @RemotableViewMethod
    public void setTopLineExtraMarginEndDp(float extraMarginEndDp) {
        setTopLineExtraMarginEnd(
                (int) (extraMarginEndDp * getResources().getDisplayMetrics().density));
    }

    /**
     * Get the current margin end value for the header text.
     * Add this to {@link #getTopLineBaseMarginEnd()} to get the total margin of the top line.
+6 −2
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import android.app.Notification;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
@@ -456,12 +458,14 @@ public class NotificationGroupingUtil {
            if (target == null) {
                return;
            }
            Integer value = (Integer) target.getTag(iconVisible
            final Integer data = (Integer) target.getTag(iconVisible
                    ? com.android.internal.R.id.tag_margin_end_when_icon_visible
                    : com.android.internal.R.id.tag_margin_end_when_icon_gone);
            if (value == null) {
            if (data == null) {
                return;
            }
            final DisplayMetrics metrics = target.getResources().getDisplayMetrics();
            final int value = TypedValue.complexToDimensionPixelOffset(data, metrics);
            if (target instanceof NotificationHeaderView) {
                ((NotificationHeaderView) target).setTopLineExtraMarginEnd(value);
            } else {
+12 −0
Original line number Diff line number Diff line
@@ -116,7 +116,9 @@ import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.internal.util.collections.Sets;
@@ -206,6 +208,16 @@ public class DevicePolicyManagerTest extends DpmTestBase {
    private static final String PROFILE_OFF_SUSPENSION_TEXT = "suspension_text";
    private static final String PROFILE_OFF_SUSPENSION_SOON_TEXT = "suspension_tomorrow_text";

    @BeforeClass
    public static void setUpClass() {
        Notification.DevFlags.sForceDefaults = true;
    }

    @AfterClass
    public static void tearDownClass() {
        Notification.DevFlags.sForceDefaults = false;
    }

    @Before
    public void setUp() throws Exception {

+7 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.devicepolicy;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -33,6 +34,7 @@ import android.os.Handler;
import android.os.UserHandle;
import android.test.mock.MockContext;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.ExceptionUtils;

import androidx.annotation.NonNull;
@@ -174,6 +176,11 @@ public class DpmMockContext extends MockContext {
        binder = new MockBinder();
        resources = mock(Resources.class);
        spiedContext = mock(Context.class);

        // Set up density for notification building
        DisplayMetrics displayMetrics = mock(DisplayMetrics.class);
        displayMetrics.density = 2.25f;
        when(resources.getDisplayMetrics()).thenReturn(displayMetrics);
    }

    @Override
Loading