diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8167622ff13c4..ec2336f2a4501 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -24,6 +24,7 @@ import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast; import static java.util.Objects.requireNonNull; +import android.annotation.AttrRes; import android.annotation.ColorInt; import android.annotation.ColorRes; import android.annotation.DimenRes; @@ -98,6 +99,7 @@ import android.widget.RemoteViews; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; @@ -3648,11 +3650,6 @@ public class Notification implements Parcelable private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; - /** - * A neutral color color that can be used for icons. - */ - private int mNeutralColor = COLOR_INVALID; - /** * Caches an instance of StandardTemplateParams. Note that this may have been used before, * so make sure to call {@link StandardTemplateParams#reset()} before using it. @@ -3666,6 +3663,7 @@ public class Notification implements Parcelable private boolean mRebuildStyledRemoteViews; private boolean mTintActionButtons; + private boolean mTintWithThemeAccent; private boolean mInNightMode; /** @@ -3701,6 +3699,7 @@ public class Notification implements Parcelable mContext = context; Resources res = mContext.getResources(); mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); + mTintWithThemeAccent = res.getBoolean(R.bool.config_tintNotificationsWithTheme); if (res.getBoolean(R.bool.config_enableNightMode)) { Configuration currentConfig = res.getConfiguration(); @@ -4891,12 +4890,10 @@ public class Notification implements Parcelable } private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) { - // TODO(b/180334837): Get buy-in on this color, or make sure to give this the - // accent color, while still accommodating the colorized state. contentView.setDrawableTint( R.id.phishing_alert, false /* targetBackground */, - getPrimaryTextColor(p), + getErrorColor(p), PorterDuff.Mode.SRC_ATOP); } @@ -4943,7 +4940,7 @@ public class Notification implements Parcelable contentView.setDrawableTint( R.id.alerted_icon, false /* targetBackground */, - getNeutralColor(p), + getHeaderIconColor(p), PorterDuff.Mode.SRC_ATOP); } @@ -5057,64 +5054,44 @@ public class Notification implements Parcelable return text; } - private void setTextViewColorPrimary(RemoteViews contentView, int id, + private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p) { - ensureColors(p); - contentView.setTextColor(id, mPrimaryTextColor); + contentView.setTextColor(id, getPrimaryTextColor(p)); } private boolean hasForegroundColor() { return mForegroundColor != COLOR_INVALID; } - /** - * Return the primary text color using the existing template params - * @hide - */ - @VisibleForTesting - public int getPrimaryTextColor() { - return getPrimaryTextColor(mParams); - } - /** * @param p the template params to inflate this with * @return the primary text color * @hide */ @VisibleForTesting - public int getPrimaryTextColor(StandardTemplateParams p) { + public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) { ensureColors(p); return mPrimaryTextColor; } - /** - * Return the secondary text color using the existing template params - * @hide - */ - @VisibleForTesting - public int getSecondaryTextColor() { - return getSecondaryTextColor(mParams); - } - /** * @param p the template params to inflate this with * @return the secondary text color * @hide */ @VisibleForTesting - public int getSecondaryTextColor(StandardTemplateParams p) { + public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) { ensureColors(p); return mSecondaryTextColor; } - private void setTextViewColorSecondary(RemoteViews contentView, int id, + private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p) { - ensureColors(p); - contentView.setTextColor(id, mSecondaryTextColor); + contentView.setTextColor(id, getSecondaryTextColor(p)); } private void ensureColors(StandardTemplateParams p) { - int backgroundColor = getBackgroundColor(p); + int backgroundColor = getUnresolvedBackgroundColor(p); if (mPrimaryTextColor == COLOR_INVALID || mSecondaryTextColor == COLOR_INVALID || mTextColorsAreForBackground != backgroundColor) { @@ -5217,7 +5194,7 @@ public class Notification implements Parcelable R.id.progress, ColorStateList.valueOf(mContext.getColor( R.color.notification_progress_background_color))); if (getRawColor(p) != COLOR_DEFAULT) { - int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p); + int color = getAccentColor(p); ColorStateList colorStateList = ColorStateList.valueOf(color); contentView.setProgressTintList(R.id.progress, colorStateList); contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList); @@ -5326,11 +5303,14 @@ public class Notification implements Parcelable } private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) { - int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p); - contentView.setDrawableTint(R.id.expand_button, false, color, - PorterDuff.Mode.SRC_ATOP); - contentView.setInt(R.id.expand_button, "setOriginalNotificationColor", - color); + contentView.setInt( + R.id.expand_button, "setDefaultTextColor", getPrimaryTextColor(p)); + contentView.setInt( + R.id.expand_button, "setDefaultPillColor", getProtectionColor(p)); + contentView.setInt( + R.id.expand_button, "setHighlightTextColor", getBackgroundColor(p)); + contentView.setInt( + R.id.expand_button, "setHighlightPillColor", getAccentColor(p)); } private void bindHeaderChronometerAndTime(RemoteViews contentView, @@ -5461,11 +5441,7 @@ public class Notification implements Parcelable } contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE); contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); - if (isColorized(p)) { - setTextViewColorPrimary(contentView, R.id.app_name_text, p); - } else { - contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); - } + contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); return true; } @@ -5555,6 +5531,10 @@ public class Notification implements Parcelable resetStandardTemplateWithActions(big); bindSnoozeAction(big, p); + // color the snooze and bubble actions with the theme color + ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p)); + big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor); + big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor); boolean validRemoteInput = false; @@ -5604,8 +5584,7 @@ public class Notification implements Parcelable showSpinner ? View.VISIBLE : View.GONE); big.setProgressIndeterminateTintList( R.id.notification_material_reply_progress, - ColorStateList.valueOf( - isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p))); + ColorStateList.valueOf(getAccentColor(p))); if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) && p.maxRemoteInputHistory > 1) { @@ -6021,14 +6000,14 @@ public class Notification implements Parcelable // change the background bgColor CharSequence title = action.title; ColorStateList[] outResultColor = new ColorStateList[1]; - int background = resolveBackgroundColor(p); + int background = getBackgroundColor(p); if (isLegacy()) { title = ContrastColorUtil.clearColorSpans(title); } else { title = ensureColorSpanContrast(title, background, outResultColor); } button.setTextViewText(R.id.action0, processTextSpans(title)); - int textColor = getPrimaryTextColor(p); + final int textColor; boolean hasColorOverride = outResultColor[0] != null; if (hasColorOverride) { // There's a span spanning the full text, let's take it and use it as the @@ -6036,9 +6015,11 @@ public class Notification implements Parcelable background = outResultColor[0].getDefaultColor(); textColor = ContrastColorUtil.resolvePrimaryColor(mContext, background, mInNightMode); - } else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p) - && mTintActionButtons && !mInNightMode) { - textColor = resolveContrastColor(p); + } else if (mTintActionButtons && !mInNightMode + && getRawColor(p) != COLOR_DEFAULT && !isColorized(p)) { + textColor = getAccentColor(p); + } else { + textColor = getPrimaryTextColor(p); } button.setTextColor(R.id.action0, textColor); // We only want about 20% alpha for the ripple @@ -6056,11 +6037,7 @@ public class Notification implements Parcelable } else { button.setTextViewText(R.id.action0, processTextSpans( processLegacyText(action.title))); - if (isColorized(p)) { - setTextViewColorPrimary(button, R.id.action0, p); - } else if (getRawColor(p) != COLOR_DEFAULT && mTintActionButtons) { - button.setTextColor(R.id.action0, resolveContrastColor(p)); - } + button.setTextColor(R.id.action0, getStandardActionColor(p)); } // CallStyle notifications add action buttons which don't actually exist in mActions, // so we have to omit the index in that case. @@ -6170,9 +6147,9 @@ public class Notification implements Parcelable private void processSmallIconColor(Icon smallIcon, RemoteViews contentView, StandardTemplateParams p) { boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon); - int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p); + int color = getSmallIconColor(p); contentView.setInt(R.id.icon, "setBackgroundColor", - resolveBackgroundColor(p)); + getBackgroundColor(p)); contentView.setInt(R.id.icon, "setOriginalIconColor", colorable ? color : COLOR_INVALID); } @@ -6187,7 +6164,7 @@ public class Notification implements Parcelable if (largeIcon != null && isLegacy() && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { // resolve color will fall back to the default when legacy - int color = resolveContrastColor(p); + int color = getContrastColor(p); contentView.setInt(R.id.icon, "setOriginalIconColor", color); } } @@ -6198,14 +6175,94 @@ public class Notification implements Parcelable } } - int resolveContrastColor(StandardTemplateParams p) { + /** + * Gets the standard action button color + */ + private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) { + return mTintActionButtons || isColorized(p) ? getAccentColor(p) : getNeutralColor(p); + } + + /** + * Gets a neutral color that can be used for icons or similar that should not stand out. + */ + private @ColorInt int getHeaderIconColor(StandardTemplateParams p) { + return isColorized(p) ? getSecondaryTextColor(p) : getNeutralColor(p); + } + + /** + * Gets the foreground color of the small icon. If the notification is colorized, this + * is the primary text color, otherwise it's the contrast-adjusted app-provided color. + */ + private @ColorInt int getSmallIconColor(StandardTemplateParams p) { + return isColorized(p) ? getPrimaryTextColor(p) : getContrastColor(p); + } + + /** + * Gets the accent color for colored UI elements. If we're tinting with the theme + * accent, this is the theme accent color, otherwise this would be identical to + * {@link #getSmallIconColor(StandardTemplateParams)}. + */ + private @ColorInt int getAccentColor(StandardTemplateParams p) { + if (isColorized(p)) { + return getPrimaryTextColor(p); + } + if (mTintWithThemeAccent) { + int color = obtainThemeColor(R.attr.colorAccent, COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; + } + } + return getContrastColor(p); + } + + /** + * Gets the "surface protection" color from the theme, or a variant of the normal background + * color when colorized, or when not using theme color tints. + */ + private @ColorInt int getProtectionColor(StandardTemplateParams p) { + if (mTintWithThemeAccent && !isColorized(p)) { + int color = obtainThemeColor(R.attr.colorBackgroundFloating, COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; + } + } + // TODO(b/181048615): What color should we use for the expander pill when colorized + return ColorUtils.blendARGB(getPrimaryTextColor(p), getBackgroundColor(p), 0.8f); + } + + /** + * Gets the theme's error color, or the primary text color for colorized notifications. + */ + private @ColorInt int getErrorColor(StandardTemplateParams p) { + if (!isColorized(p)) { + int color = obtainThemeColor(R.attr.colorError, COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; + } + } + return getPrimaryTextColor(p); + } + + /** + * Gets the theme's background color + */ + private @ColorInt int getDefaultBackgroundColor() { + return obtainThemeColor(R.attr.colorBackground, + mInNightMode ? Color.BLACK : Color.WHITE); + } + + /** + * Gets the contrast-adjusted version of the color provided by the app. + */ + private @ColorInt int getContrastColor(StandardTemplateParams p) { int rawColor = getRawColor(p); if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) { return mCachedContrastColor; } int color; - int background = obtainBackgroundColor(); + // TODO: Maybe use getBackgroundColor(p) instead -- but doing so could break the cache + int background = getDefaultBackgroundColor(); if (rawColor == COLOR_DEFAULT) { ensureColors(p); color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode); @@ -6224,28 +6281,29 @@ public class Notification implements Parcelable /** * Return the raw color of this Notification, which doesn't necessarily satisfy contrast. * - * @see #resolveContrastColor(StandardTemplateParams) for the contrasted color + * @see #getContrastColor(StandardTemplateParams) for the contrasted color * @param p the template params to inflate this with */ - private int getRawColor(StandardTemplateParams p) { + private @ColorInt int getRawColor(StandardTemplateParams p) { if (p.forceDefaultColor) { return COLOR_DEFAULT; } return mN.color; } - int resolveNeutralColor() { - if (mNeutralColor != COLOR_INVALID) { - return mNeutralColor; - } - int background = obtainBackgroundColor(); - mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background, + /** + * Gets a neutral palette color; this is a contrast-satisfied version of the default color. + * @param p the template params to inflate this with + */ + private @ColorInt int getNeutralColor(StandardTemplateParams p) { + int background = getBackgroundColor(p); + int neutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode); - if (Color.alpha(mNeutralColor) < 255) { + if (Color.alpha(neutralColor) < 255) { // alpha doesn't go well for color filters, so let's blend it manually - mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background); + neutralColor = ContrastColorUtil.compositeColors(neutralColor, background); } - return mNeutralColor; + return neutralColor; } /** @@ -6389,8 +6447,11 @@ public class Notification implements Parcelable return mN; } - private @ColorInt int obtainBackgroundColor() { - int defaultColor = mInNightMode ? Color.BLACK : Color.WHITE; + /** + * Returns the color for the given Theme.DeviceDefault.DayNight attribute, or + * defValue if that could not be completed + */ + private @ColorInt int obtainThemeColor(@AttrRes int attrRes, @ColorInt int defaultColor) { Resources.Theme theme = mContext.getTheme(); if (theme == null) { // Running unit tests with mocked context @@ -6398,7 +6459,7 @@ public class Notification implements Parcelable } theme = new ContextThemeWrapper(mContext, R.style.Theme_DeviceDefault_DayNight) .getTheme(); - TypedArray ta = theme.obtainStyledAttributes(new int[]{R.attr.colorBackground}); + TypedArray ta = theme.obtainStyledAttributes(new int[]{attrRes}); if (ta == null) { return defaultColor; } @@ -6517,7 +6578,11 @@ public class Notification implements Parcelable return R.layout.notification_material_action_tombstone; } - private int getBackgroundColor(StandardTemplateParams p) { + /** + * Gets the background color, with {@link #COLOR_DEFAULT} being a valid return value, + * which must be resolved by the caller before being used. + */ + private @ColorInt int getUnresolvedBackgroundColor(StandardTemplateParams p) { if (isColorized(p)) { return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p); } else { @@ -6526,33 +6591,17 @@ public class Notification implements Parcelable } /** - * Gets a neutral color that can be used for icons or similar that should not stand out. - * @param p the template params to inflate this with + * Same as {@link #getUnresolvedBackgroundColor(StandardTemplateParams)} except that it + * also resolves the default color to the background. */ - private int getNeutralColor(StandardTemplateParams p) { - if (isColorized(p)) { - return getSecondaryTextColor(p); - } else { - return resolveNeutralColor(); - } - } - - /** - * Same as getBackgroundColor but also resolved the default color to the background. - * @param p the template params to inflate this with - */ - private int resolveBackgroundColor(StandardTemplateParams p) { - int backgroundColor = getBackgroundColor(p); + private @ColorInt int getBackgroundColor(StandardTemplateParams p) { + int backgroundColor = getUnresolvedBackgroundColor(p); if (backgroundColor == COLOR_DEFAULT) { - backgroundColor = obtainBackgroundColor(); + backgroundColor = getDefaultBackgroundColor(); } return backgroundColor; } - private boolean shouldTintActionButtons() { - return mTintActionButtons; - } - private boolean textColorsNeedInversion() { if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) { return false; @@ -6570,7 +6619,7 @@ public class Notification implements Parcelable * * @hide */ - public void setColorPalette(int backgroundColor, int foregroundColor) { + public void setColorPalette(@ColorInt int backgroundColor, @ColorInt int foregroundColor) { mBackgroundColor = backgroundColor; mForegroundColor = foregroundColor; mTextColorsAreForBackground = COLOR_INVALID; @@ -8200,16 +8249,14 @@ public class Notification implements Parcelable TypedValue.COMPLEX_UNIT_DIP); } contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", - mBuilder.isColorized(p) - ? mBuilder.getPrimaryTextColor(p) - : mBuilder.resolveContrastColor(p)); + mBuilder.getSmallIconColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor", mBuilder.getPrimaryTextColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor", mBuilder.getSecondaryTextColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, "setNotificationBackgroundColor", - mBuilder.resolveBackgroundColor(p)); + mBuilder.getBackgroundColor(p)); contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", isCollapsed); contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", @@ -8964,14 +9011,7 @@ public class Notification implements Parcelable // If the action buttons should not be tinted, then just use the default // notification color. Otherwise, just use the passed-in color. - Resources resources = mBuilder.mContext.getResources(); - Configuration currentConfig = resources.getConfiguration(); - boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized(p) - ? getActionColor(p) - : ContrastColorUtil.resolveColor(mBuilder.mContext, - Notification.COLOR_DEFAULT, inNightMode); + int tintColor = mBuilder.getStandardActionColor(p); container.setDrawableTint(buttonId, false, tintColor, PorterDuff.Mode.SRC_ATOP); @@ -9027,11 +9067,6 @@ public class Notification implements Parcelable return view; } - private int getActionColor(StandardTemplateParams p) { - return mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p) - : mBuilder.resolveContrastColor(p); - } - private RemoteViews makeMediaBigContentView() { final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); // Dont add an expanded view if there is no more content to be revealed @@ -9373,7 +9408,6 @@ public class Notification implements Parcelable .hideLargeIcon(true) .text(text) .summaryText(mBuilder.processLegacyText(mVerificationText)); - // TODO(b/179178086): hide the snooze button RemoteViews contentView = mBuilder.applyStandardTemplate( mBuilder.getCallLayoutResource(), p, null /* result */); @@ -9390,11 +9424,9 @@ public class Notification implements Parcelable // Bind some custom CallLayout properties contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", - mBuilder.isColorized(p) - ? mBuilder.getPrimaryTextColor(p) - : mBuilder.resolveContrastColor(p)); + mBuilder.getSmallIconColor(p)); contentView.setInt(R.id.status_bar_latest_event_content, - "setNotificationBackgroundColor", mBuilder.resolveBackgroundColor(p)); + "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p)); contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", mBuilder.mN.mLargeIcon); contentView.setBundle(R.id.status_bar_latest_event_content, "setData", diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 1b1e0bfb3a582..8ecc80946141d 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -32,7 +32,6 @@ import android.app.Person; import android.app.RemoteInputHistoryItem; import android.content.Context; import android.content.res.ColorStateList; -import android.graphics.Color; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.GradientDrawable; @@ -62,11 +61,9 @@ import android.widget.RemoteViews; import android.widget.TextView; import com.android.internal.R; -import com.android.internal.graphics.ColorUtils; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.function.Consumer; @@ -118,7 +115,6 @@ public class ConversationLayout extends FrameLayout private ViewGroup mExpandButtonAndContentContainer; private NotificationExpandButton mExpandButton; private MessagingLinearLayout mImageMessageContainer; - private int mExpandButtonExpandedTopMargin; private int mBadgedSideMargins; private int mConversationAvatarSize; private int mConversationAvatarSizeExpanded; @@ -147,7 +143,6 @@ public class ConversationLayout extends FrameLayout private int mFacePileProtectionWidth; private int mFacePileProtectionWidthExpanded; private boolean mImportantConversation; - private TextView mUnreadBadge; private View mFeedbackIcon; private float mMinTouchSize; private Icon mConversationIcon; @@ -245,8 +240,6 @@ public class ConversationLayout extends FrameLayout mContentContainer = findViewById(R.id.notification_action_list_margin_target); mExpandButtonAndContentContainer = findViewById(R.id.expand_button_and_content_container); mExpandButton = findViewById(R.id.expand_button); - mExpandButtonExpandedTopMargin = getResources().getDimensionPixelSize( - R.dimen.conversation_expand_button_top_margin_expanded); mNotificationHeaderExpandedPadding = getResources().getDimensionPixelSize( R.dimen.conversation_header_expanded_padding_end); mContentMarginEnd = getResources().getDimensionPixelSize( @@ -286,7 +279,6 @@ public class ConversationLayout extends FrameLayout mAppName.setOnVisibilityChangedListener((visibility) -> { onAppNameVisibilityChanged(); }); - mUnreadBadge = findViewById(R.id.conversation_unread_count); mConversationContentStart = getResources().getDimensionPixelSize( R.dimen.conversation_content_start); mInternalButtonPadding @@ -426,17 +418,7 @@ public class ConversationLayout extends FrameLayout /** @hide */ public void setUnreadCount(int unreadCount) { - boolean visible = mIsCollapsed && unreadCount > 1; - mUnreadBadge.setVisibility(visible ? VISIBLE : GONE); - if (visible) { - CharSequence text = unreadCount >= 100 - ? getResources().getString(R.string.unread_convo_overflow, 99) - : String.format(Locale.getDefault(), "%d", unreadCount); - mUnreadBadge.setText(text); - mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor)); - boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f; - mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE); - } + mExpandButton.setNumber(unreadCount); } private void addRemoteInputHistoryToMessages( @@ -1132,15 +1114,16 @@ public class ConversationLayout extends FrameLayout } private void updateExpandButton() { - int gravity; - int topMargin = 0; + int buttonGravity; + int containerHeight; ViewGroup newContainer; if (mIsCollapsed) { - gravity = Gravity.CENTER; + buttonGravity = Gravity.CENTER; + containerHeight = ViewGroup.LayoutParams.WRAP_CONTENT; newContainer = mExpandButtonAndContentContainer; } else { - gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; - topMargin = mExpandButtonExpandedTopMargin; + buttonGravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; + containerHeight = ViewGroup.LayoutParams.MATCH_PARENT; newContainer = this; } mExpandButton.setExpanded(!mIsCollapsed); @@ -1149,14 +1132,14 @@ public class ConversationLayout extends FrameLayout // content when collapsed, but allows the content to flow under it when expanded. if (newContainer != mExpandButtonContainer.getParent()) { ((ViewGroup) mExpandButtonContainer.getParent()).removeView(mExpandButtonContainer); + mExpandButtonContainer.getLayoutParams().height = containerHeight; newContainer.addView(mExpandButtonContainer); } // update if the expand button is centered LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mExpandButton.getLayoutParams(); - layoutParams.gravity = gravity; - layoutParams.topMargin = topMargin; + layoutParams.gravity = buttonGravity; mExpandButton.setLayoutParams(layoutParams); } @@ -1210,6 +1193,7 @@ public class ConversationLayout extends FrameLayout mExpandButtonContainer.setVisibility(GONE); mConversationIconContainer.setOnClickListener(null); } + mExpandButton.setVisibility(VISIBLE); updateContentEndPaddings(); } diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java index 8add34f328bfe..a5b757bc54721 100644 --- a/core/java/com/android/internal/widget/NotificationExpandButton.java +++ b/core/java/com/android/internal/widget/NotificationExpandButton.java @@ -16,30 +16,42 @@ package com.android.internal.widget; -import static com.android.internal.widget.ColoredIconHelper.applyGrayTint; - +import android.annotation.ColorInt; import android.annotation.Nullable; import android.content.Context; +import android.content.res.ColorStateList; import android.graphics.Rect; import android.util.AttributeSet; import android.view.RemotableViewMethod; +import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Button; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RemoteViews; +import android.widget.TextView; import com.android.internal.R; +import java.util.Locale; + /** * An expand button in a notification */ @RemoteViews.RemoteView -public class NotificationExpandButton extends ImageView { +public class NotificationExpandButton extends FrameLayout { - private final int mMinTouchTargetSize; + private View mPillView; + private TextView mNumberView; + private ImageView mIconView; private boolean mExpanded; - private int mOriginalNotificationColor; + private int mNumber; + private int mDefaultPillColor; + private int mDefaultTextColor; + private int mHighlightPillColor; + private int mHighlightTextColor; + private boolean mDisallowColor; public NotificationExpandButton(Context context) { this(context, null, 0, 0); @@ -57,7 +69,14 @@ public class NotificationExpandButton extends ImageView { public NotificationExpandButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mMinTouchTargetSize = (int) (getResources().getDisplayMetrics().density * 48 + 0.5); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mPillView = findViewById(R.id.expand_button_pill); + mNumberView = findViewById(R.id.expand_button_number); + mIconView = findViewById(R.id.expand_button_icon); } /** @@ -72,7 +91,6 @@ public class NotificationExpandButton extends ImageView { } else { super.getBoundsOnScreen(outRect, clipToParent); } - extendRectToMinTouchSize(outRect); } /** @@ -89,32 +107,12 @@ public class NotificationExpandButton extends ImageView { return super.pointInView(localX, localY, slop); } - @RemotableViewMethod - public void setOriginalNotificationColor(int color) { - mOriginalNotificationColor = color; - } - - public int getOriginalNotificationColor() { - return mOriginalNotificationColor; - } - /** - * Set the button's color filter: to gray if true, otherwise colored. - * If this button has no original color, this has no effect. + * Disable the use of the accent colors for this view, if true. */ public void setGrayedOut(boolean shouldApply) { - applyGrayTint(mContext, getDrawable(), shouldApply, mOriginalNotificationColor); - } - - private void extendRectToMinTouchSize(Rect rect) { - if (rect.width() < mMinTouchTargetSize) { - rect.left = rect.centerX() - mMinTouchTargetSize / 2; - rect.right = rect.left + mMinTouchTargetSize; - } - if (rect.height() < mMinTouchTargetSize) { - rect.top = rect.centerY() - mMinTouchTargetSize / 2; - rect.bottom = rect.top + mMinTouchTargetSize; - } + mDisallowColor = shouldApply; + updateColors(); } @Override @@ -129,10 +127,10 @@ public class NotificationExpandButton extends ImageView { @RemotableViewMethod public void setExpanded(boolean expanded) { mExpanded = expanded; - updateExpandButton(); + updateExpandedState(); } - private void updateExpandButton() { + private void updateExpandedState() { int drawableId; int contentDescriptionId; if (mExpanded) { @@ -142,8 +140,87 @@ public class NotificationExpandButton extends ImageView { drawableId = R.drawable.ic_expand_notification; contentDescriptionId = R.string.expand_button_content_description_collapsed; } - setImageDrawable(getContext().getDrawable(drawableId)); - setColorFilter(mOriginalNotificationColor); setContentDescription(mContext.getText(contentDescriptionId)); + mIconView.setImageDrawable(getContext().getDrawable(drawableId)); + + // changing the expanded state can affect the number display + updateNumber(); + } + + private void updateNumber() { + if (shouldShowNumber()) { + CharSequence text = mNumber >= 100 + ? getResources().getString(R.string.unread_convo_overflow, 99) + : String.format(Locale.getDefault(), "%d", mNumber); + mNumberView.setText(text); + mNumberView.setVisibility(VISIBLE); + } else { + mNumberView.setVisibility(GONE); + } + + // changing number can affect the color + updateColors(); + } + + private void updateColors() { + if (shouldShowNumber() && !mDisallowColor) { + mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor)); + mIconView.setColorFilter(mHighlightTextColor); + mNumberView.setTextColor(mHighlightTextColor); + } else { + mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor)); + mIconView.setColorFilter(mDefaultTextColor); + mNumberView.setTextColor(mDefaultTextColor); + } + } + + private boolean shouldShowNumber() { + return !mExpanded && mNumber > 1; + } + + /** + * Set the color used for the expand chevron and the text + */ + @RemotableViewMethod + public void setDefaultTextColor(int color) { + mDefaultTextColor = color; + updateColors(); + } + + /** + * Sets the color used to for the expander when there is no number shown + */ + @RemotableViewMethod + public void setDefaultPillColor(@ColorInt int color) { + mDefaultPillColor = color; + updateColors(); + } + + /** + * Set the color used for the expand chevron and the text + */ + @RemotableViewMethod + public void setHighlightTextColor(int color) { + mHighlightTextColor = color; + updateColors(); + } + + /** + * Sets the color used to highlight the expander when there is a number shown + */ + @RemotableViewMethod + public void setHighlightPillColor(@ColorInt int color) { + mHighlightPillColor = color; + updateColors(); + } + + /** + * Sets the number shown inside the expand button. + * This only appears when the expand button is collapsed, and when greater than 1. + */ + @RemotableViewMethod + public void setNumber(int number) { + mNumber = number; + updateNumber(); } } diff --git a/core/res/res/drawable/conversation_unread_bg.xml b/core/res/res/drawable/expand_button_pill_bg.xml similarity index 89% rename from core/res/res/drawable/conversation_unread_bg.xml rename to core/res/res/drawable/expand_button_pill_bg.xml index d3e00cfbf8b13..f95044a7fe76e 100644 --- a/core/res/res/drawable/conversation_unread_bg.xml +++ b/core/res/res/drawable/expand_button_pill_bg.xml @@ -14,6 +14,6 @@ ~ limitations under the License. --> - + \ No newline at end of file diff --git a/core/res/res/drawable/ic_collapse_notification.xml b/core/res/res/drawable/ic_collapse_notification.xml index ca4f0ed27a0b3..a06ec9fc813f2 100644 --- a/core/res/res/drawable/ic_collapse_notification.xml +++ b/core/res/res/drawable/ic_collapse_notification.xml @@ -21,8 +21,5 @@ Copyright (C) 2020 The Android Open Source Project android:viewportHeight="24.0"> - + android:pathData="M18.59,15.41L20.0,14.0l-8.0,-8.0 -8.0,8.0 1.41,1.41L12.0,8.83"/> \ No newline at end of file diff --git a/core/res/res/drawable/ic_expand_notification.xml b/core/res/res/drawable/ic_expand_notification.xml index a080ce43cfeca..160a9c2c1970a 100644 --- a/core/res/res/drawable/ic_expand_notification.xml +++ b/core/res/res/drawable/ic_expand_notification.xml @@ -21,8 +21,5 @@ Copyright (C) 2014 The Android Open Source Project android:viewportHeight="24.0"> - + android:pathData="M5.41,8.59L4.0,10.0l8.0,8.0 8.0,-8.0 -1.41,-1.41L12.0,15.17"/> \ No newline at end of file diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml new file mode 100644 index 0000000000000..f92e6d63cbe73 --- /dev/null +++ b/core/res/res/layout/notification_expand_button.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 1de1d049197c8..150734eee50cc 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -75,15 +75,10 @@ android:importantForAccessibility="no" /> - diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index b83611bcc1770..bad9a6ba6184e 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -74,14 +74,10 @@ android:layout_height="match_parent" android:layout_gravity="end"> - diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml index 471d874c59f53..7b52ec30abe64 100644 --- a/core/res/res/layout/notification_template_material_call.xml +++ b/core/res/res/layout/notification_template_material_call.xml @@ -72,15 +72,10 @@ - diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml index f3aa54066c92d..42fb4a26dd3b6 100644 --- a/core/res/res/layout/notification_template_material_conversation.xml +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -104,11 +104,10 @@ @@ -118,34 +117,16 @@ android:forceHasOverlappingRendering="false" android:layout_width="40dp" android:layout_height="40dp" - android:layout_marginEnd="11dp" + android:layout_marginStart="@dimen/conversation_image_start_margin" android:spacing="0dp" android:layout_gravity="center" android:clipToPadding="false" android:clipChildren="false" /> - - - diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b0a4c6e027cd6..5ffc4f0939b15 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3957,6 +3957,10 @@ color supplied by the Notification.Builder if present. --> true + + true + false diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index b7dd98bca3ed9..f390462aeac90 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -248,7 +248,7 @@ 64dp - 48dp + 56dp 20dp @@ -314,10 +314,10 @@ 4dp - 48dp + 56dp - - 1dp + + 24dp @@ -746,7 +746,7 @@ 20dp - 16dp + 20dp @dimen/notification_icon_circle_size @@ -777,13 +777,10 @@ 28dp 80dp - - 80dp - - 18dp - - 13dp + + 80dp + + 12dp 36dp diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 829c5abd40234..6723469f17b46 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1893,6 +1893,7 @@ + @@ -2893,6 +2894,9 @@ + + + @@ -2913,7 +2917,6 @@ - @@ -4034,7 +4037,6 @@ - @@ -4055,7 +4057,6 @@ - diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 76917612b9107..b0b91bd5177cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -22,6 +22,8 @@ import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; @@ -30,6 +32,7 @@ import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.NotificationHeaderView; @@ -1234,6 +1237,7 @@ public class NotificationContentView extends FrameLayout { mCachedHeadsUpRemoteInput = null; } + private RemoteInputView applyRemoteInput(View view, NotificationEntry entry, boolean hasRemoteInput, PendingIntent existingPendingIntent, RemoteInputView cachedView, NotificationViewWrapper wrapper) { @@ -1271,6 +1275,15 @@ public class NotificationContentView extends FrameLayout { if (color == Notification.COLOR_DEFAULT) { color = mContext.getColor(R.color.default_remote_input_background); } + if (mContext.getResources().getBoolean( + com.android.internal.R.bool.config_tintNotificationsWithTheme)) { + Resources.Theme theme = new ContextThemeWrapper(mContext, + com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme(); + TypedArray ta = theme.obtainStyledAttributes( + new int[]{com.android.internal.R.attr.colorAccent}); + color = ta.getColor(0, color); + ta.recycle(); + } existing.setBackgroundColor(ContrastColorUtil.ensureTextBackgroundColor(color, mContext.getColor(R.color.remote_input_text_enabled), mContext.getColor(R.color.remote_input_hint))); @@ -1342,12 +1355,10 @@ public class NotificationContentView extends FrameLayout { && isPersonWithShortcut && entry.getBubbleMetadata() != null; if (showButton) { - Drawable d = mContext.getResources().getDrawable(entry.isBubble() + // explicitly resolve drawable resource using SystemUI's theme + Drawable d = mContext.getDrawable(entry.isBubble() ? R.drawable.bubble_ic_stop_bubble : R.drawable.bubble_ic_create_bubble); - mContainingNotification.updateNotificationColor(); - final int tint = mContainingNotification.getNotificationColor(); - d.setTint(tint); String contentDescription = mContext.getResources().getString(entry.isBubble() ? R.string.notification_conversation_unbubble @@ -1381,9 +1392,8 @@ public class NotificationContentView extends FrameLayout { return; } + // explicitly resolve drawable resource using SystemUI's theme Drawable snoozeDrawable = mContext.getDrawable(R.drawable.ic_snooze); - mContainingNotification.updateNotificationColor(); - snoozeDrawable.setTint(mContainingNotification.getNotificationColor()); snoozeButton.setImageDrawable(snoozeDrawable); final NotificationSnooze snoozeGuts = (NotificationSnooze) LayoutInflater.from(mContext)