Merge changes from topic "decorate_custom_notifs"

* changes:
  Improve RemoteViews to simplify notification view hierarchy.
  Fully custom view notifications now receive minimal decoration when targeting S.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e56274a..5fbc948 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -102,6 +102,7 @@
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -645,6 +646,11 @@
      */
     public static final int FLAG_IMMEDIATE_FGS_DISPLAY = 0x00002000;
 
+    private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
+            BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
+            DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
+            MessagingStyle.class);
+
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
             FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE,
@@ -5099,7 +5105,7 @@
             final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
             final int progress = ex.getInt(EXTRA_PROGRESS, 0);
             final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
-            if (p.hasProgress && (max != 0 || ind)) {
+            if (!p.mHideProgress && (max != 0 || ind)) {
                 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
                 contentView.setProgressBar(
                         R.id.progress, max, progress, ind);
@@ -5427,7 +5433,7 @@
             int N = nonContextualActions.size();
             boolean emphazisedMode = mN.fullScreenIntent != null;
             big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
-            if (N > 0) {
+            if (N > 0 && !p.mHideActions) {
                 big.setViewVisibility(R.id.actions_container, View.VISIBLE);
                 big.setViewVisibility(R.id.actions, View.VISIBLE);
                 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
@@ -5520,6 +5526,71 @@
             return createContentView(false /* increasedheight */ );
         }
 
+        // 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. While it's possible
+        // apps can detect the change, it's most likely that the changes will simply result in
+        // visual regressions.
+        @SuppressWarnings("AndroidFrameworkCompatChange")
+        private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) {
+            // Custom views which come from a platform style class are safe, and thus do not need to
+            // be wrapped.  Any subclass of those styles has the opportunity to make arbitrary
+            // changes to the RemoteViews, and thus can't be trusted as a fully vetted view.
+            if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) {
+                return false;
+            }
+            final ContentResolver contentResolver = mContext.getContentResolver();
+            final int decorationType = DevFlags.getFullyCustomViewNotifDecoration(contentResolver);
+            return decorationType != DevFlags.DECORATION_NONE
+                    && (DevFlags.shouldBackportSNotifRules(contentResolver)
+                    || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S);
+        }
+
+        private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) {
+            int decorationType =
+                    DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver());
+            StandardTemplateParams p = mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+                    .decorationType(decorationType)
+                    .fillTextsFrom(this);
+            TemplateBindResult result = new TemplateBindResult();
+            RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result);
+            buildCustomContentIntoTemplate(mContext, standard, customContent,
+                    p, result, decorationType);
+            return standard;
+        }
+
+        private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) {
+            int decorationType =
+                    DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver());
+            StandardTemplateParams p = mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                    .decorationType(decorationType)
+                    .fillTextsFrom(this);
+            TemplateBindResult result = new TemplateBindResult();
+            RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
+                    p, result);
+            buildCustomContentIntoTemplate(mContext, standard, customContent,
+                    p, result, decorationType);
+            return standard;
+        }
+
+        private RemoteViews minimallyDecoratedHeadsUpContentView(
+                @NonNull RemoteViews customContent) {
+            int decorationType =
+                    DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver());
+            StandardTemplateParams p = mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
+                    .decorationType(decorationType)
+                    .fillTextsFrom(this);
+            TemplateBindResult result = new TemplateBindResult();
+            RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(),
+                    p, result);
+            buildCustomContentIntoTemplate(mContext, standard, customContent,
+                    p, result, decorationType);
+            return standard;
+        }
+
         /**
          * Construct a RemoteViews for the smaller content view.
          *
@@ -5532,11 +5603,13 @@
          */
         public RemoteViews createContentView(boolean increasedHeight) {
             if (mN.contentView != null && useExistingRemoteView()) {
-                return mN.contentView;
+                return fullyCustomViewRequiresDecoration(false /* fromStyle */)
+                        ? minimallyDecoratedContentView(mN.contentView) : mN.contentView;
             } else if (mStyle != null) {
                 final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
                 if (styleView != null) {
-                    return styleView;
+                    return fullyCustomViewRequiresDecoration(true /* fromStyle */)
+                            ? minimallyDecoratedContentView(styleView) : styleView;
                 }
             }
             StandardTemplateParams p = mParams.reset()
@@ -5556,18 +5629,30 @@
         public RemoteViews createBigContentView() {
             RemoteViews result = null;
             if (mN.bigContentView != null && useExistingRemoteView()) {
-                return mN.bigContentView;
+                return fullyCustomViewRequiresDecoration(false /* fromStyle */)
+                        ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView;
             }
             if (mStyle != null) {
                 result = mStyle.makeBigContentView();
                 hideLine1Text(result);
+                if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) {
+                    result = minimallyDecoratedBigContentView(result);
+                }
             }
-            if (result == null && bigContentViewRequired()) {
-                StandardTemplateParams p = mParams.reset()
-                        .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
-                        .fillTextsFrom(this);
-                result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
-                        null /* result */);
+            if (result == null) {
+                if (bigContentViewRequired()) {
+                    StandardTemplateParams p = mParams.reset()
+                            .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                            .fillTextsFrom(this);
+                    result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
+                            null /* result */);
+                } else if (DevFlags.shouldBackportSNotifRules(mContext.getContentResolver())
+                        && useExistingRemoteView()
+                        && fullyCustomViewRequiresDecoration(false /* fromStyle */)) {
+                    // This "backport" logic is a special case to handle the UNDO style of notif
+                    // so that we can see what that will look like when the app targets S.
+                    result = minimallyDecoratedBigContentView(mN.contentView);
+                }
             }
             makeHeaderExpanded(result);
             return result;
@@ -5661,11 +5746,14 @@
          */
         public RemoteViews createHeadsUpContentView(boolean increasedHeight) {
             if (mN.headsUpContentView != null && useExistingRemoteView()) {
-                return mN.headsUpContentView;
+                return fullyCustomViewRequiresDecoration(false /* fromStyle */)
+                        ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView)
+                        : mN.headsUpContentView;
             } else if (mStyle != null) {
                 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight);
                 if (styleView != null) {
-                    return styleView;
+                    return fullyCustomViewRequiresDecoration(true /* fromStyle */)
+                            ? minimallyDecoratedHeadsUpContentView(styleView) : styleView;
                 }
             } else if (mActions.size() == 0) {
                 return null;
@@ -5677,8 +5765,7 @@
                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
                     .fillTextsFrom(this)
                     .setMaxRemoteInputHistory(1);
-            return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(),
-                    p,
+            return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p,
                     null /* result */);
         }
 
@@ -6596,11 +6683,7 @@
      */
     @SystemApi
     public static Class<? extends Style> getNotificationStyleClass(String templateClass) {
-        Class<? extends Style>[] classes = new Class[] {
-                BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
-                DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
-                MessagingStyle.class };
-        for (Class<? extends Style> innerClass : classes) {
+        for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) {
             if (templateClass.equals(innerClass.getName())) {
                 return innerClass;
             }
@@ -6608,6 +6691,56 @@
         return null;
     }
 
+    private static void buildCustomContentIntoTemplate(@NonNull Context context,
+            @NonNull RemoteViews template, @Nullable RemoteViews customContent,
+            @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result,
+            int decorationType) {
+        int childIndex = -1;
+        if (customContent != null) {
+            // Need to clone customContent before adding, because otherwise it can no longer be
+            // parceled independently of remoteViews.
+            customContent = customContent.clone();
+            if (p.mHeaderless) {
+                if (decorationType <= DevFlags.DECORATION_PARTIAL) {
+                    template.removeFromParent(R.id.notification_top_line);
+                }
+                if (decorationType != DevFlags.DECORATION_FULL_COMPATIBLE) {
+                    // Change the max content size from 60dp (the compatible size) to 48dp
+                    // (the constrained size).  This is done by increasing the minimum margin
+                    // (implemented as top/bottom margins) and decreasing the extra margin
+                    // (implemented as the height of shrinkable top/bottom views in the column).
+                    template.setViewLayoutMarginDimen(
+                            R.id.notification_headerless_view_column,
+                            RemoteViews.MARGIN_TOP,
+                            R.dimen.notification_headerless_margin_constrained_minimum);
+                    template.setViewLayoutMarginDimen(
+                            R.id.notification_headerless_view_column,
+                            RemoteViews.MARGIN_BOTTOM,
+                            R.dimen.notification_headerless_margin_constrained_minimum);
+                    template.setViewLayoutHeightDimen(
+                            R.id.notification_headerless_margin_extra_top,
+                            R.dimen.notification_headerless_margin_constrained_extra);
+                    template.setViewLayoutHeightDimen(
+                            R.id.notification_headerless_margin_extra_bottom,
+                            R.dimen.notification_headerless_margin_constrained_extra);
+                }
+            } else {
+                // also update the end margin to account for the large icon or expander
+                Resources resources = context.getResources();
+                result.mTitleMarginSet.applyToView(template, R.id.notification_main_column,
+                        resources.getDimension(R.dimen.notification_content_margin_end)
+                                / resources.getDisplayMetrics().density);
+            }
+            template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
+            template.addView(R.id.notification_main_column, customContent, 0 /* index */);
+            template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
+            childIndex = 0;
+        }
+        template.setIntTag(R.id.notification_main_column,
+                com.android.internal.R.id.notification_custom_view_index_tag,
+                childIndex);
+    }
+
     /**
      * An object that can apply a rich notification style to a {@link Notification.Builder}
      * object.
@@ -7813,7 +7946,7 @@
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(isCollapsed ? StandardTemplateParams.VIEW_TYPE_NORMAL
                             : StandardTemplateParams.VIEW_TYPE_BIG)
-                    .hasProgress(false)
+                    .hideProgress(true)
                     .title(conversationTitle)
                     .text(null)
                     .hideLargeIcon(hideRightIcons || isOneToOne)
@@ -8628,7 +8761,8 @@
         private RemoteViews makeMediaContentView() {
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
-                    .hasProgress(false).fillTextsFrom(mBuilder);
+                    .hideProgress(true)
+                    .fillTextsFrom(mBuilder);
             RemoteViews view = mBuilder.applyStandardTemplate(
                     R.layout.notification_template_material_media, p,
                     null /* result */);
@@ -8677,7 +8811,8 @@
             }
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
-                    .hasProgress(false).fillTextsFrom(mBuilder);
+                    .hideProgress(true)
+                    .fillTextsFrom(mBuilder);
             RemoteViews big = mBuilder.applyStandardTemplate(
                     R.layout.notification_template_material_big_media, p , null /* result */);
 
@@ -8771,29 +8906,39 @@
             RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null
                     ? mBuilder.mN.contentView
                     : mBuilder.mN.headsUpContentView;
+            if (headsUpContentView == null) {
+                return null;  // no custom view; use the default behavior
+            }
             if (mBuilder.mActions.size() == 0) {
                return makeStandardTemplateWithCustomContent(headsUpContentView);
             }
+            int decorationType = getDecorationType();
             TemplateBindResult result = new TemplateBindResult();
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
-                    .hasCustomContent(headsUpContentView != null)
+                    .decorationType(decorationType)
                     .fillTextsFrom(mBuilder);
             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
                     mBuilder.getHeadsUpBaseLayoutResource(), p, result);
-            buildIntoRemoteViewContent(remoteViews, headsUpContentView, result, true);
+            buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView,
+                    p, result, decorationType);
             return remoteViews;
         }
 
         private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) {
+            if (customContent == null) {
+                return null;  // no custom view; use the default behavior
+            }
+            int decorationType = getDecorationType();
             TemplateBindResult result = new TemplateBindResult();
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
-                    .hasCustomContent(customContent != null)
+                    .decorationType(decorationType)
                     .fillTextsFrom(mBuilder);
             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
                     mBuilder.getBaseLayoutResource(), p, result);
-            buildIntoRemoteViewContent(remoteViews, customContent, result, true);
+            buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
+                    p, result, decorationType);
             return remoteViews;
         }
 
@@ -8801,38 +8946,37 @@
             RemoteViews bigContentView = mBuilder.mN.bigContentView == null
                     ? mBuilder.mN.contentView
                     : mBuilder.mN.bigContentView;
+            if (bigContentView == null) {
+                return null;  // no custom view; use the default behavior
+            }
+            int decorationType = getDecorationType();
             TemplateBindResult result = new TemplateBindResult();
             StandardTemplateParams p = mBuilder.mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
-                    .hasCustomContent(bigContentView != null)
+                    .decorationType(decorationType)
                     .fillTextsFrom(mBuilder);
             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
                     mBuilder.getBigBaseLayoutResource(), p, result);
-            buildIntoRemoteViewContent(remoteViews, bigContentView, result, false);
+            buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
+                    p, result, decorationType);
             return remoteViews;
         }
 
-        private void buildIntoRemoteViewContent(RemoteViews remoteViews,
-                RemoteViews customContent, TemplateBindResult result, boolean headerless) {
-            int childIndex = -1;
-            if (customContent != null) {
-                // Need to clone customContent before adding, because otherwise it can no longer be
-                // parceled independently of remoteViews.
-                customContent = customContent.clone();
-                remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
-                remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */);
-                remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
-                childIndex = 0;
-            }
-            remoteViews.setIntTag(R.id.notification_main_column,
-                    com.android.internal.R.id.notification_custom_view_index_tag,
-                    childIndex);
-            if (!headerless) {
-                // 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.getDimension(R.dimen.notification_content_margin_end)
-                                / resources.getDisplayMetrics().density);
+        // 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. While it's possible
+        // apps can detect the change, it's most likely that the changes will simply result in
+        // visual regressions.
+        @SuppressWarnings("AndroidFrameworkCompatChange")
+        private int getDecorationType() {
+            ContentResolver contentResolver = mBuilder.mContext.getContentResolver();
+            if (mBuilder.mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S
+                    || DevFlags.shouldBackportSNotifRules(contentResolver)) {
+                return DevFlags.getDecoratedCustomViewNotifDecoration(contentResolver);
+            } else {
+                // For apps that don't target S, this decoration provides the closest behavior to R,
+                // but doesn't fit with the design guidelines for S.
+                return DevFlags.DECORATION_FULL_COMPATIBLE;
             }
         }
 
@@ -11159,8 +11303,9 @@
 
         int mViewType = VIEW_TYPE_UNSPECIFIED;
         boolean mHeaderless;
-        boolean mHasCustomContent;
-        boolean hasProgress = true;
+        boolean mHideTitle;
+        boolean mHideActions;
+        boolean mHideProgress;
         CharSequence title;
         CharSequence text;
         CharSequence headerTextSecondary;
@@ -11173,8 +11318,9 @@
         final StandardTemplateParams reset() {
             mViewType = VIEW_TYPE_UNSPECIFIED;
             mHeaderless = false;
-            mHasCustomContent = false;
-            hasProgress = true;
+            mHideTitle = false;
+            mHideActions = false;
+            mHideProgress = false;
             title = null;
             text = null;
             summaryText = null;
@@ -11186,9 +11332,7 @@
         }
 
         final boolean hasTitle() {
-            // We hide the title when the notification is a decorated custom view so that decorated
-            // custom views always have to include their own title.
-            return !TextUtils.isEmpty(title) && !mHasCustomContent;
+            return !TextUtils.isEmpty(title) && !mHideTitle;
         }
 
         final StandardTemplateParams viewType(int viewType) {
@@ -11201,13 +11345,18 @@
             return this;
         }
 
-        final StandardTemplateParams hasProgress(boolean hasProgress) {
-            this.hasProgress = hasProgress;
+        final StandardTemplateParams hideActions(boolean hideActions) {
+            this.mHideActions = hideActions;
             return this;
         }
 
-        final StandardTemplateParams hasCustomContent(boolean hasCustomContent) {
-            this.mHasCustomContent = hasCustomContent;
+        final StandardTemplateParams hideProgress(boolean hideProgress) {
+            this.mHideProgress = hideProgress;
+            return this;
+        }
+
+        final StandardTemplateParams hideTitle(boolean hideTitle) {
+            this.mHideTitle = hideTitle;
             return this;
         }
 
@@ -11263,6 +11412,16 @@
             this.maxRemoteInputHistory = maxRemoteInputHistory;
             return this;
         }
+
+        public StandardTemplateParams decorationType(int decorationType) {
+            boolean hideTitle = decorationType <= DevFlags.DECORATION_FULL_COMPATIBLE;
+            boolean hideOtherFields = decorationType <= DevFlags.DECORATION_MINIMAL;
+            hideTitle(hideTitle);
+            hideLargeIcon(hideOtherFields);
+            hideProgress(hideOtherFields);
+            hideActions(hideOtherFields);
+            return this;
+        }
     }
 
     /**
@@ -11272,7 +11431,67 @@
      * @hide
      */
     public static class DevFlags {
+
+        /**
+         * Notifications will not be decorated.  The custom content will be shown as-is.
+         *
+         * <p>NOTE: This option is not available for notifications with DecoratedCustomViewStyle,
+         * as that API contract includes decorations that this does not provide.
+         */
+        public static final int DECORATION_NONE = 0;
+
+        /**
+         * Notifications will be minimally decorated with ONLY an icon and expander as follows:
+         * <li>A large icon is never shown.
+         * <li>A progress bar is never shown.
+         * <li>The expanded and heads up states do not show actions, even if provided.
+         * <li>The collapsed state gives the app's custom content 48dp of vertical space.
+         * <li>The collapsed state does NOT include the top line of views,
+         * like the alerted icon or work profile badge.
+         *
+         * <p>NOTE: This option is not available for notifications with DecoratedCustomViewStyle,
+         * as that API contract includes decorations that this does not provide.
+         */
+        public static final int DECORATION_MINIMAL = 1;
+
+        /**
+         * Notifications will be partially decorated with AT LEAST an icon and expander as follows:
+         * <li>A large icon is shown if provided.
+         * <li>A progress bar is shown if provided and enough space remains below the content.
+         * <li>Actions are shown in the expanded and heads up states.
+         * <li>The collapsed state gives the app's custom content 48dp of vertical space.
+         * <li>The collapsed state does NOT include the top line of views,
+         * like the alerted icon or work profile badge.
+         */
+        public static final int DECORATION_PARTIAL = 2;
+
+        /**
+         * Notifications will be fully decorated as follows:
+         * <li>A large icon is shown if provided.
+         * <li>A progress bar is shown if provided and enough space remains below the content.
+         * <li>Actions are shown in the expanded and heads up states.
+         * <li>The collapsed state gives the app's custom content 40dp of vertical space.
+         * <li>The collapsed state DOES include the top line of views,
+         * like the alerted icon or work profile badge.
+         * <li>The collapsed state's top line views will never show the title text.
+         */
+        public static final int DECORATION_FULL_COMPATIBLE = 3;
+
+        /**
+         * Notifications will be fully decorated as follows:
+         * <li>A large icon is shown if provided.
+         * <li>A progress bar is shown if provided and enough space remains below the content.
+         * <li>Actions are shown in the expanded and heads up states.
+         * <li>The collapsed state gives the app's custom content 20dp of vertical space.
+         * <li>The collapsed state DOES include the top line of views
+         * like the alerted icon or work profile badge.
+         * <li>The collapsed state's top line views will contain the title text if provided.
+         */
+        public static final int DECORATION_FULL_CONSTRAINED = 4;
+
         private static final boolean DEFAULT_BACKPORT_S_NOTIF_RULES = false;
+        private static final int DEFAULT_FULLY_CUSTOM_DECORATION = DECORATION_MINIMAL;
+        private static final int DEFAULT_DECORATED_DECORATION = DECORATION_PARTIAL;
 
         /**
          * Used by unit tests to force that this class returns its default values, which is required
@@ -11292,5 +11511,37 @@
             return Settings.Global.getInt(contentResolver, Settings.Global.BACKPORT_S_NOTIF_RULES,
                         DEFAULT_BACKPORT_S_NOTIF_RULES ? 1 : 0) == 1;
         }
+
+        /**
+         * @return the decoration type to be applied to notifications with fully custom view.
+         * @hide
+         */
+        public static int getFullyCustomViewNotifDecoration(
+                @NonNull ContentResolver contentResolver) {
+            if (sForceDefaults) {
+                return DEFAULT_FULLY_CUSTOM_DECORATION;
+            }
+            final int decoration = Settings.Global.getInt(contentResolver,
+                    Settings.Global.FULLY_CUSTOM_VIEW_NOTIF_DECORATION,
+                    DEFAULT_FULLY_CUSTOM_DECORATION);
+            // clamp to a valid decoration value
+            return Math.max(DECORATION_NONE, Math.min(decoration, DECORATION_FULL_CONSTRAINED));
+        }
+
+        /**
+         * @return the decoration type to be applied to notifications with DecoratedCustomViewStyle.
+         * @hide
+         */
+        public static int getDecoratedCustomViewNotifDecoration(
+                @NonNull ContentResolver contentResolver) {
+            if (sForceDefaults) {
+                return DEFAULT_DECORATED_DECORATION;
+            }
+            final int decoration = Settings.Global.getInt(contentResolver,
+                    Settings.Global.DECORATED_CUSTOM_VIEW_NOTIF_DECORATION,
+                    DEFAULT_DECORATED_DECORATION);
+            // clamp to a valid decoration value (and don't allow decoration to be NONE or MINIMAL)
+            return Math.max(DECORATION_PARTIAL, Math.min(decoration, DECORATION_FULL_CONSTRAINED));
+        }
     }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 69ca28a..9db7ca0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14630,6 +14630,34 @@
         public static final String BACKPORT_S_NOTIF_RULES = "backport_s_notif_rules";
 
         /**
+         * The decoration to put on fully custom views that target S.
+         *
+         * <p>Values are:
+         * <br>0: DECORATION_NONE: no decorations.
+         * <br>1: DECORATION_MINIMAL: most minimal template; just the icon and the expander.
+         * <br>2: DECORATION_PARTIAL: basic template without the top line.
+         * <br>3: DECORATION_FULL_COMPATIBLE: basic template with the top line; 40dp of height.
+         * <br>4: DECORATION_FULL_CONSTRAINED: basic template with the top line;  28dp of height.
+         * <p>See {@link android.app.Notification.DevFlags} for more details.
+         * @hide
+         */
+        public static final String FULLY_CUSTOM_VIEW_NOTIF_DECORATION =
+                "fully_custom_view_notif_decoration";
+
+        /**
+         * The decoration to put on decorated custom views that target S.
+         *
+         * <p>Values are:
+         * <br>2: DECORATION_PARTIAL: basic template without the top line.
+         * <br>3: DECORATION_FULL_COMPATIBLE: basic template with the top line; 40dp of height.
+         * <br>4: DECORATION_FULL_CONSTRAINED: basic template with the top line;  28dp of height.
+         * <p>See {@link android.app.Notification.DevFlags} for more details.
+         * @hide
+         */
+        public static final String DECORATED_CUSTOM_VIEW_NOTIF_DECORATION =
+                "decorated_custom_view_notif_decoration";
+
+        /**
          * Block untrusted touches mode.
          *
          * Can be one of:
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 220a31c..8dafc5d 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -75,6 +75,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewManager;
+import android.view.ViewParent;
 import android.view.ViewStub;
 import android.widget.AdapterView.OnItemClickListener;
 
@@ -177,6 +179,7 @@
     private static final int OVERRIDE_TEXT_COLORS_TAG = 20;
     private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21;
     private static final int SET_INT_TAG_TAG = 22;
+    private static final int REMOVE_FROM_PARENT_ACTION_TAG = 23;
 
     /** @hide **/
     @IntDef(prefix = "MARGIN_", value = {
@@ -1831,6 +1834,75 @@
     }
 
     /**
+     * Action to remove a view from its parent.
+     */
+    private class RemoveFromParentAction extends Action {
+
+        RemoveFromParentAction(@IdRes int viewId) {
+            this.viewId = viewId;
+        }
+
+        RemoveFromParentAction(Parcel parcel) {
+            viewId = parcel.readInt();
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(viewId);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+            final View target = root.findViewById(viewId);
+
+            if (target == null || target == root) {
+                return;
+            }
+
+            ViewParent parent = target.getParent();
+            if (parent instanceof ViewManager) {
+                ((ViewManager) parent).removeView(target);
+            }
+        }
+
+        @Override
+        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
+            // In the async implementation, update the view tree so that subsequent calls to
+            // findViewById return the correct view.
+            root.createTree();
+            ViewTree target = root.findViewTreeById(viewId);
+
+            if (target == null || target == root) {
+                return ACTION_NOOP;
+            }
+
+            ViewTree parent = root.findViewTreeParentOf(target);
+            if (parent == null || !(parent.mRoot instanceof ViewManager)) {
+                return ACTION_NOOP;
+            }
+            final ViewManager parentVg = (ViewManager) parent.mRoot;
+
+            parent.mChildren.remove(target);
+            return new RuntimeAction() {
+                @Override
+                public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
+                        throws ActionException {
+                    parentVg.removeView(target.mRoot);
+                }
+            };
+        }
+
+        @Override
+        public int getActionTag() {
+            return REMOVE_FROM_PARENT_ACTION_TAG;
+        }
+
+        @Override
+        public int mergeBehavior() {
+            return MERGE_APPEND;
+        }
+    }
+
+    /**
      * Helper action to set compound drawables on a TextView. Supports relative
      * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
      */
@@ -2537,6 +2609,8 @@
                 return new SetRippleDrawableColor(parcel);
             case SET_INT_TAG_TAG:
                 return new SetIntTagAction(parcel);
+            case REMOVE_FROM_PARENT_ACTION_TAG:
+                return new RemoveFromParentAction(parcel);
             default:
                 throw new ActionException("Tag " + tag + " not found");
         }
@@ -2675,6 +2749,18 @@
     }
 
     /**
+     * Removes the {@link View} specified by the {@code viewId} from its parent {@link ViewManager}.
+     * This will do nothing if the viewId specifies the root view of this RemoteViews.
+     *
+     * @param viewId The id of the {@link View} to remove from its parent.
+     *
+     * @hide
+     */
+    public void removeFromParent(@IdRes int viewId) {
+        addAction(new RemoveFromParentAction(viewId));
+    }
+
+    /**
      * Equivalent to calling {@link AdapterViewAnimator#showNext()}
      *
      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
@@ -4025,6 +4111,22 @@
             return null;
         }
 
+        public ViewTree findViewTreeParentOf(ViewTree child) {
+            if (mChildren == null) {
+                return null;
+            }
+            for (ViewTree tree : mChildren) {
+                if (tree == child) {
+                    return this;
+                }
+                ViewTree result = tree.findViewTreeParentOf(child);
+                if (result != null) {
+                    return result;
+                }
+            }
+            return null;
+        }
+
         public void replaceView(View v) {
             mRoot = v;
             mChildren = null;
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 0006384..41be36b 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -45,6 +45,45 @@
         android:padding="@dimen/notification_icon_circle_padding"
         />
 
+    <ImageView
+        android:id="@+id/right_icon"
+        android:layout_width="@dimen/notification_right_icon_size"
+        android:layout_height="@dimen/notification_right_icon_size"
+        android:layout_gravity="center_vertical|end"
+        android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+        android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+        android:layout_marginEnd="@dimen/notification_header_expand_icon_size"
+        android:background="@drawable/notification_large_icon_outline"
+        android:importantForAccessibility="no"
+        android:scaleType="centerCrop"
+        />
+
+    <FrameLayout
+        android:id="@+id/alternate_expand_target"
+        android:layout_width="@dimen/notification_content_margin_start"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:importantForAccessibility="no"
+        />
+
+    <FrameLayout
+        android:id="@+id/expand_button_touch_container"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="end">
+
+        <com.android.internal.widget.NotificationExpandButton
+            android:id="@+id/expand_button"
+            android:layout_width="@dimen/notification_header_expand_icon_size"
+            android:layout_height="@dimen/notification_header_expand_icon_size"
+            android:layout_gravity="center_vertical|end"
+            android:contentDescription="@string/expand_button_content_description_collapsed"
+            android:paddingTop="@dimen/notification_expand_button_padding_top"
+            android:scaleType="center"
+            />
+
+    </FrameLayout>
+
     <LinearLayout
         android:id="@+id/notification_headerless_view_column"
         android:layout_width="match_parent"
@@ -64,6 +103,7 @@
         variant is 56dp and the 2- and 3-line variants are both 76dp.
         -->
         <FrameLayout
+            android:id="@+id/notification_headerless_margin_extra_top"
             android:layout_width="match_parent"
             android:layout_height="@dimen/notification_headerless_margin_extra"
             android:layout_weight="1"
@@ -135,6 +175,7 @@
         variant is 56dp and the 2- and 3-line variants are both 76dp.
         -->
         <FrameLayout
+            android:id="@+id/notification_headerless_margin_extra_bottom"
             android:layout_width="match_parent"
             android:layout_height="@dimen/notification_headerless_margin_extra"
             android:layout_weight="1"
@@ -142,43 +183,4 @@
 
     </LinearLayout>
 
-    <ImageView
-        android:id="@+id/right_icon"
-        android:layout_width="@dimen/notification_right_icon_size"
-        android:layout_height="@dimen/notification_right_icon_size"
-        android:layout_gravity="center_vertical|end"
-        android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
-        android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
-        android:layout_marginEnd="@dimen/notification_header_expand_icon_size"
-        android:background="@drawable/notification_large_icon_outline"
-        android:importantForAccessibility="no"
-        android:scaleType="centerCrop"
-        />
-
-    <FrameLayout
-        android:id="@+id/alternate_expand_target"
-        android:layout_width="@dimen/notification_content_margin_start"
-        android:layout_height="match_parent"
-        android:layout_gravity="start"
-        android:importantForAccessibility="no"
-        />
-
-    <FrameLayout
-        android:id="@+id/expand_button_touch_container"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_gravity="end">
-
-        <com.android.internal.widget.NotificationExpandButton
-            android:id="@+id/expand_button"
-            android:layout_width="@dimen/notification_header_expand_icon_size"
-            android:layout_height="@dimen/notification_header_expand_icon_size"
-            android:layout_gravity="center_vertical|end"
-            android:contentDescription="@string/expand_button_content_description_collapsed"
-            android:paddingTop="@dimen/notification_expand_button_padding_top"
-            android:scaleType="center"
-            />
-
-    </FrameLayout>
-
 </com.android.internal.widget.NotificationMaxHeightFrameLayout>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index c326a40..3ae2131 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -305,12 +305,18 @@
     <!-- The top padding for the notification expand button. -->
     <dimen name="notification_expand_button_padding_top">1dp</dimen>
 
-    <!-- minimum vertical margin for the headerless notification content -->
+    <!-- minimum vertical margin for the headerless notification content, when cap = 60dp -->
     <dimen name="notification_headerless_margin_minimum">8dp</dimen>
 
-    <!-- extra vertical margin for the headerless notification content -->
+    <!-- extra vertical margin for the headerless notification content, when cap = 60dp -->
     <dimen name="notification_headerless_margin_extra">10dp</dimen>
 
+    <!-- minimum vertical margin for the headerless notification content, when cap = 48dp -->
+    <dimen name="notification_headerless_margin_constrained_minimum">14dp</dimen>
+
+    <!-- extra vertical margin for the headerless notification content, when cap = 48dp -->
+    <dimen name="notification_headerless_margin_constrained_extra">4dp</dimen>
+
     <!-- The height of each of the 1 or 2 lines in the headerless notification template -->
     <dimen name="notification_headerless_line_height">20sp</dimen>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2df9684..31b4edd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2877,6 +2877,8 @@
   <java-symbol type="id" name="alternate_expand_target" />
   <java-symbol type="id" name="notification_header" />
   <java-symbol type="id" name="notification_top_line" />
+  <java-symbol type="id" name="notification_headerless_margin_extra_top" />
+  <java-symbol type="id" name="notification_headerless_margin_extra_bottom" />
   <java-symbol type="id" name="time_divider" />
   <java-symbol type="id" name="header_text_divider" />
   <java-symbol type="id" name="header_text_secondary_divider" />
@@ -2898,6 +2900,8 @@
   <java-symbol type="dimen" name="notification_header_icon_size" />
   <java-symbol type="dimen" name="notification_header_app_name_margin_start" />
   <java-symbol type="dimen" name="notification_header_separating_margin" />
+  <java-symbol type="dimen" name="notification_headerless_margin_constrained_minimum" />
+  <java-symbol type="dimen" name="notification_headerless_margin_constrained_extra" />
   <java-symbol type="string" name="default_notification_channel_label" />
   <java-symbol type="string" name="importance_from_user" />
   <java-symbol type="string" name="importance_from_person" />
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c1c1d65..e22f264 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -217,6 +217,7 @@
                     Settings.Global.DEBUG_APP,
                     Settings.Global.DEBUG_VIEW_ATTRIBUTES,
                     Settings.Global.DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE,
+                    Settings.Global.DECORATED_CUSTOM_VIEW_NOTIF_DECORATION,
                     Settings.Global.DEFAULT_DNS_SERVER,
                     Settings.Global.DEFAULT_INSTALL_LOCATION,
                     Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA,
@@ -285,6 +286,7 @@
                     Settings.Global.WIFI_ON_WHEN_PROXY_DISCONNECTED,
                     Settings.Global.FSTRIM_MANDATORY_INTERVAL,
                     Settings.Global.FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED,
+                    Settings.Global.FULLY_CUSTOM_VIEW_NOTIF_DECORATION,
                     Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
                     Settings.Global.GLOBAL_HTTP_PROXY_HOST,
                     Settings.Global.GLOBAL_HTTP_PROXY_PAC,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 28ee935..97201f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -134,12 +134,14 @@
     /** Shows or hides feedback indicator */
     @Override
     public void showFeedbackIcon(boolean show, Pair<Integer, Integer> resIds) {
-        mFeedbackIcon.setVisibility(show ? View.VISIBLE : View.GONE);
-        if (show) {
-            if (mFeedbackIcon instanceof ImageButton) {
-                ((ImageButton) mFeedbackIcon).setImageResource(resIds.first);
+        if (mFeedbackIcon != null) {
+            mFeedbackIcon.setVisibility(show ? View.VISIBLE : View.GONE);
+            if (show) {
+                if (mFeedbackIcon instanceof ImageButton) {
+                    ((ImageButton) mFeedbackIcon).setImageResource(resIds.first);
+                }
+                mFeedbackIcon.setContentDescription(mView.getContext().getString(resIds.second));
             }
-            mFeedbackIcon.setContentDescription(mView.getContext().getString(resIds.second));
         }
     }
 
@@ -263,7 +265,9 @@
         mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon);
         mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_EXPANDER,
                 mExpandButton);
-        mTransformationHelper.addViewTransformingToSimilar(mWorkProfileImage);
+        if (mWorkProfileImage != null) {
+            mTransformationHelper.addViewTransformingToSimilar(mWorkProfileImage);
+        }
         if (mIsLowPriority && mHeaderText != null) {
             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
                     mHeaderText);