diff options
7 files changed, 448 insertions, 136 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 297b9ff1af16..587a7cc98909 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -102,6 +102,7 @@ import java.lang.annotation.RetentionPolicy; 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 class Notification implements Parcelable */ 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_SHOW_LIGHTS, FLAG_ONGOING_EVENT, FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE, FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY, @@ -5098,7 +5104,7 @@ public class Notification implements Parcelable 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); @@ -5426,7 +5432,7 @@ public class Notification implements Parcelable 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, @@ -5519,6 +5525,71 @@ public class Notification implements Parcelable 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. * @@ -5531,11 +5602,13 @@ public class Notification implements Parcelable */ 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() @@ -5555,18 +5628,30 @@ public class Notification implements Parcelable 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; @@ -5660,11 +5745,14 @@ public class Notification implements Parcelable */ 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; @@ -5676,8 +5764,7 @@ public class Notification implements Parcelable .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) .fillTextsFrom(this) .setMaxRemoteInputHistory(1); - return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), - p, + return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p, null /* result */); } @@ -6595,11 +6682,7 @@ public class Notification implements Parcelable */ @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; } @@ -6607,6 +6690,57 @@ public class Notification implements Parcelable 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.removeAllViewsExceptId(R.id.notification_top_line_container, + R.id.notification_main_column); + } + 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. @@ -7812,7 +7946,7 @@ public class Notification implements Parcelable 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) @@ -8627,7 +8761,8 @@ public class Notification implements Parcelable 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 */); @@ -8676,7 +8811,8 @@ public class Notification implements Parcelable } 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 */); @@ -8770,29 +8906,39 @@ public class Notification implements Parcelable 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; } @@ -8800,38 +8946,37 @@ public class Notification implements Parcelable 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; } } @@ -11158,8 +11303,9 @@ public class Notification implements Parcelable int mViewType = VIEW_TYPE_UNSPECIFIED; boolean mHeaderless; - boolean mHasCustomContent; - boolean hasProgress = true; + boolean mHideTitle; + boolean mHideActions; + boolean mHideProgress; CharSequence title; CharSequence text; CharSequence headerTextSecondary; @@ -11172,8 +11318,9 @@ public class Notification implements Parcelable 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; @@ -11185,9 +11332,7 @@ public class Notification implements Parcelable } 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) { @@ -11200,13 +11345,18 @@ public class Notification implements Parcelable 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; } @@ -11262,6 +11412,16 @@ public class Notification implements Parcelable 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; + } } /** @@ -11271,7 +11431,67 @@ public class Notification implements Parcelable * @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 @@ -11291,5 +11511,37 @@ public class Notification implements Parcelable 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 69ca28a38fb6..9db7ca0cad6b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14630,6 +14630,34 @@ public final class Settings { 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/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index 000638475a10..a045e15b5478 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,43 +103,57 @@ 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" /> - <!-- extends ViewGroup --> - <NotificationTopLineView - android:id="@+id/notification_top_line" - android:layout_width="wrap_content" - android:layout_height="@dimen/notification_headerless_line_height" - android:layout_marginEnd="@dimen/notification_heading_margin_end" - android:layout_marginStart="@dimen/notification_content_margin_start" + <!-- + Because RemoteViews doesn't allow us to remove specific views, this container allows us + to remove the NotificationTopLineView without removing other padding items. + --> + <FrameLayout + android:id="@+id/notification_top_line_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" android:clipChildren="false" - android:theme="@style/Theme.DeviceDefault.Notification" > - <!-- - NOTE: The notification_top_line_views layout contains the app_name_text. - In order to include the title view at the beginning, the Notification.Builder - has logic to hide that view whenever this title view is to be visible. - --> - - <TextView - android:id="@+id/title" + <!-- extends ViewGroup --> + <NotificationTopLineView + android:id="@+id/notification_top_line" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/notification_header_separating_margin" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:singleLine="true" - android:textAlignment="viewStart" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" - /> - - <include layout="@layout/notification_top_line_views" /> - - </NotificationTopLineView> + android:layout_height="@dimen/notification_headerless_line_height" + android:layout_marginEnd="@dimen/notification_heading_margin_end" + android:layout_marginStart="@dimen/notification_content_margin_start" + android:clipChildren="false" + android:theme="@style/Theme.DeviceDefault.Notification" + > + + <!-- + NOTE: The notification_top_line_views layout contains the app_name_text. + In order to include the title view at the beginning, the Notification.Builder + has logic to hide that view whenever this title view is to be visible. + --> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:ellipsize="marquee" + android:fadingEdge="horizontal" + android:singleLine="true" + android:textAlignment="viewStart" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" + /> + + <include layout="@layout/notification_top_line_views" /> + + </NotificationTopLineView> + + </FrameLayout> <LinearLayout android:id="@+id/notification_main_column" @@ -135,6 +188,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 +196,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 5b19a486881a..fbcf2cbf54f5 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -301,12 +301,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 fa664519152b..beb0059d096e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2876,6 +2876,9 @@ <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_top_line_container" /> + <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" /> @@ -2897,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 c1c1d65fb9d7..e22f2649d540 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -217,6 +217,7 @@ public class SettingsBackupTest { 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 @@ public class SettingsBackupTest { 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 28ee9358737e..97201f5c9a34 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { /** 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { 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); |