diff options
22 files changed, 476 insertions, 1732 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 5dd10f10930b..4eda6fefd188 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -20,8 +20,6 @@ import static android.annotation.Dimension.DP; import static android.graphics.drawable.Icon.TYPE_URI; import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; -import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast; - import static java.util.Objects.requireNonNull; import android.annotation.AttrRes; @@ -3701,8 +3699,6 @@ public class Notification implements Parcelable private int mTextColorsAreForBackground = COLOR_INVALID; private int mPrimaryTextColor = COLOR_INVALID; private int mSecondaryTextColor = COLOR_INVALID; - private int mBackgroundColor = COLOR_INVALID; - private int mForegroundColor = COLOR_INVALID; private boolean mRebuildStyledRemoteViews; private boolean mTintActionButtons; @@ -5041,7 +5037,8 @@ public class Notification implements Parcelable private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result) { p.headerless(resId == getBaseLayoutResource() - || resId == getHeadsUpBaseLayoutResource()); + || resId == getHeadsUpBaseLayoutResource() + || resId == R.layout.notification_template_material_media); RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); resetStandardTemplate(contentView); @@ -5092,7 +5089,7 @@ public class Notification implements Parcelable } private CharSequence processTextSpans(CharSequence text) { - if (hasForegroundColor() || mInNightMode) { + if (mInNightMode) { return ContrastColorUtil.clearColorSpans(text); } return text; @@ -5103,10 +5100,6 @@ public class Notification implements Parcelable contentView.setTextColor(id, getPrimaryTextColor(p)); } - private boolean hasForegroundColor() { - return mForegroundColor != COLOR_INVALID; - } - /** * @param p the template params to inflate this with * @return the primary text color @@ -5140,75 +5133,15 @@ public class Notification implements Parcelable || mSecondaryTextColor == COLOR_INVALID || mTextColorsAreForBackground != backgroundColor) { mTextColorsAreForBackground = backgroundColor; - if (!hasForegroundColor() || !isColorized(p)) { - mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext, - backgroundColor, mInNightMode); - mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext, - backgroundColor, mInNightMode); - if (backgroundColor != COLOR_DEFAULT && isColorized(p)) { - mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( - mPrimaryTextColor, backgroundColor, 4.5); - mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( - mSecondaryTextColor, backgroundColor, 4.5); - } - } else { - double backLum = ContrastColorUtil.calculateLuminance(backgroundColor); - double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor); - double contrast = ContrastColorUtil.calculateContrast(mForegroundColor, - backgroundColor); - // We only respect the given colors if worst case Black or White still has - // contrast - boolean backgroundLight = backLum > textLum - && satisfiesTextContrast(backgroundColor, Color.BLACK) - || backLum <= textLum - && !satisfiesTextContrast(backgroundColor, Color.WHITE); - if (contrast < 4.5f) { - if (backgroundLight) { - mSecondaryTextColor = ContrastColorUtil.findContrastColor( - mForegroundColor, - backgroundColor, - true /* findFG */, - 4.5f); - mPrimaryTextColor = ContrastColorUtil.changeColorLightness( - mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT); - } else { - mSecondaryTextColor = - ContrastColorUtil.findContrastColorAgainstDark( - mForegroundColor, - backgroundColor, - true /* findFG */, - 4.5f); - mPrimaryTextColor = ContrastColorUtil.changeColorLightness( - mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK); - } - } else { - mPrimaryTextColor = mForegroundColor; - mSecondaryTextColor = ContrastColorUtil.changeColorLightness( - mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT - : LIGHTNESS_TEXT_DIFFERENCE_DARK); - if (ContrastColorUtil.calculateContrast(mSecondaryTextColor, - backgroundColor) < 4.5f) { - // oh well the secondary is not good enough - if (backgroundLight) { - mSecondaryTextColor = ContrastColorUtil.findContrastColor( - mSecondaryTextColor, - backgroundColor, - true /* findFG */, - 4.5f); - } else { - mSecondaryTextColor - = ContrastColorUtil.findContrastColorAgainstDark( - mSecondaryTextColor, - backgroundColor, - true /* findFG */, - 4.5f); - } - mPrimaryTextColor = ContrastColorUtil.changeColorLightness( - mSecondaryTextColor, backgroundLight - ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT - : -LIGHTNESS_TEXT_DIFFERENCE_DARK); - } - } + mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext, + backgroundColor, mInNightMode); + mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext, + backgroundColor, mInNightMode); + if (backgroundColor != COLOR_DEFAULT && isColorized(p)) { + mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( + mPrimaryTextColor, backgroundColor, 4.5); + mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( + mSecondaryTextColor, backgroundColor, 4.5); } } } @@ -5254,11 +5187,7 @@ public class Notification implements Parcelable result = new TemplateBindResult(); } bindLargeIcon(contentView, p, result); - if (p.mHeaderless) { - // views in the headerless (collapsed) state - result.mHeadingExtraMarginSet.applyToView(contentView, - R.id.notification_headerless_view_column); - } else { + if (!p.mHeaderless) { // views in states with a header (big states) result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header); result.mTitleMarginSet.applyToView(contentView, R.id.title); @@ -5278,6 +5207,8 @@ public class Notification implements Parcelable @NonNull TemplateBindResult result) { final Resources resources = mContext.getResources(); final float density = resources.getDisplayMetrics().density; + final float iconMarginDp = resources.getDimension( + R.dimen.notification_right_icon_content_margin) / density; final float contentMarginDp = resources.getDimension( R.dimen.notification_content_margin_end) / density; final float expanderSizeDp = resources.getDimension( @@ -5299,7 +5230,7 @@ public class Notification implements Parcelable } } } - final float extraMarginEndDpIfVisible = viewWidthDp + contentMarginDp; + final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp; result.setRightIconState(largeIconShown, viewWidthDp, extraMarginEndDpIfVisible, expanderSizeDp); } @@ -5363,7 +5294,7 @@ public class Notification implements Parcelable private void bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft) { - if (showsTimeOrChronometer()) { + if (!p.mHideTime && showsTimeOrChronometer()) { if (hasTextToLeft) { contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); setTextViewColorSecondary(contentView, R.id.time_divider, p); @@ -5394,6 +5325,9 @@ public class Notification implements Parcelable */ private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft) { + if (p.mHideSubText) { + return false; + } CharSequence summaryText = p.summaryText; if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet && mStyle.hasSummaryInHeader()) { @@ -5424,6 +5358,9 @@ public class Notification implements Parcelable */ private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft) { + if (p.mHideSubText) { + return false; + } if (!TextUtils.isEmpty(p.headerTextSecondary)) { contentView.setTextViewText(R.id.header_text_secondary, processTextSpans( processLegacyText(p.headerTextSecondary))); @@ -6654,7 +6591,7 @@ public class Notification implements Parcelable */ private @ColorInt int getUnresolvedBackgroundColor(StandardTemplateParams p) { if (isColorized(p)) { - return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p); + return getRawColor(p); } else { return COLOR_DEFAULT; } @@ -6682,21 +6619,6 @@ public class Notification implements Parcelable } /** - * Set a color palette to be used as the background and textColors - * - * @param backgroundColor the color to be used as the background - * @param foregroundColor the color to be used as the foreground - * - * @hide - */ - public void setColorPalette(@ColorInt int backgroundColor, @ColorInt int foregroundColor) { - mBackgroundColor = backgroundColor; - mForegroundColor = foregroundColor; - mTextColorsAreForBackground = COLOR_INVALID; - ensureColors(mParams.reset().fillTextsFrom(this)); - } - - /** * Forces all styled remoteViews to be built from scratch and not use any cached * RemoteViews. * This is needed for legacy apps that are baking in their remoteviews into the @@ -6752,24 +6674,14 @@ public class Notification implements Parcelable if (mLargeIcon != null || largeIcon != null) { Resources resources = context.getResources(); Class<? extends Style> style = getNotificationStyle(); - int maxWidth = resources.getDimensionPixelSize(isLowRam + int maxSize = resources.getDimensionPixelSize(isLowRam ? R.dimen.notification_right_icon_size_low_ram : R.dimen.notification_right_icon_size); - int maxHeight = maxWidth; - if (MediaStyle.class.equals(style) - || DecoratedMediaCustomViewStyle.class.equals(style)) { - maxHeight = resources.getDimensionPixelSize(isLowRam - ? R.dimen.notification_media_image_max_height_low_ram - : R.dimen.notification_media_image_max_height); - maxWidth = resources.getDimensionPixelSize(isLowRam - ? R.dimen.notification_media_image_max_width_low_ram - : R.dimen.notification_media_image_max_width); - } if (mLargeIcon != null) { - mLargeIcon.scaleDownIfNecessary(maxWidth, maxHeight); + mLargeIcon.scaleDownIfNecessary(maxSize, maxSize); } if (largeIcon != null) { - largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxWidth, maxHeight); + largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize); } } reduceImageSizesForRemoteView(contentView, context, isLowRam); @@ -6856,9 +6768,6 @@ public class Notification implements Parcelable * @hide */ public boolean isColorized() { - if (isColorizedMedia()) { - return true; - } return extras.getBoolean(EXTRA_COLORIZED) && (hasColorizedPermission() || isForegroundService()); } @@ -6872,27 +6781,6 @@ public class Notification implements Parcelable } /** - * @return true if this notification is colorized and it is a media notification - * - * @hide - */ - public boolean isColorizedMedia() { - Class<? extends Style> style = getNotificationStyle(); - if (MediaStyle.class.equals(style)) { - Boolean colorized = (Boolean) extras.get(EXTRA_COLORIZED); - if ((colorized == null || colorized) && hasMediaSession()) { - return true; - } - } else if (DecoratedMediaCustomViewStyle.class.equals(style)) { - if (extras.getBoolean(EXTRA_COLORIZED) && hasMediaSession()) { - return true; - } - } - return false; - } - - - /** * @return true if this is a media notification * * @hide @@ -7180,15 +7068,6 @@ public class Notification implements Parcelable /** * @hide - * @return true if the style positions the progress bar on the second line; false if the - * style hides the progress bar - */ - protected boolean hasProgress() { - return true; - } - - /** - * @hide * @return Whether we should put the summary be put into the notification header */ public boolean hasSummaryInHeader() { @@ -9041,7 +8920,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeContentView(boolean increasedHeight) { - return makeMediaContentView(); + return makeMediaContentView(null /* customContent */); } /** @@ -9049,7 +8928,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeBigContentView() { - return makeMediaBigContentView(); + return makeMediaBigContentView(null /* customContent */); } /** @@ -9057,7 +8936,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { - return makeMediaContentView(); + return makeMediaContentView(null /* customContent */); } /** @hide */ @@ -9127,88 +9006,72 @@ public class Notification implements Parcelable container.setContentDescription(buttonId, action.title); } - private RemoteViews makeMediaContentView() { + /** @hide */ + protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) { + final int numActions = mBuilder.mActions.size(); + final int numActionsToShow = Math.min(mActionsToShowInCompact == null + ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); + if (numActionsToShow > numActions) { + throw new IllegalArgumentException(String.format( + "setShowActionsInCompactView: action %d out of bounds (max %d)", + numActions, numActions - 1)); + } + StandardTemplateParams p = mBuilder.mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) + .hideTime(numActionsToShow > 1) // hide if actions wider than a large icon + .hideSubText(numActionsToShow > 1) // hide if actions wider than a large icon + .hideLargeIcon(numActionsToShow > 0) // large icon or actions; not both .hideProgress(true) .fillTextsFrom(mBuilder); - RemoteViews view = mBuilder.applyStandardTemplate( + TemplateBindResult result = new TemplateBindResult(); + RemoteViews template = mBuilder.applyStandardTemplate( R.layout.notification_template_material_media, p, null /* result */); - final int numActions = mBuilder.mActions.size(); - final int numActionsToShow = mActionsToShowInCompact == null - ? 0 - : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); - if (numActionsToShow > numActions) { - throw new IllegalArgumentException(String.format( - "setShowActionsInCompactView: action %d out of bounds (max %d)", - numActions, numActions - 1)); - } for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) { if (i < numActionsToShow) { final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); - bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p); + bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p); } else { - view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); + template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); } } - handleImage(view); - // handle the content margin - int endMargin = R.dimen.notification_content_margin_end; - if (mBuilder.mN.hasLargeIcon()) { - endMargin = R.dimen.notification_media_image_margin_end; - } - view.setViewLayoutMarginDimen(R.id.notification_main_column, - RemoteViews.MARGIN_END, endMargin); - return view; + // Prevent a swooping expand animation when there are no actions + boolean hasActions = numActionsToShow != 0; + template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE); + + // Add custom view if provided by subclass. + buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result, + DevFlags.DECORATION_PARTIAL); + return template; } - private RemoteViews makeMediaBigContentView() { + /** @hide */ + protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) { 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 - int actionsInCompact = mActionsToShowInCompact == null - ? 0 - : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); - if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) { - return null; - } StandardTemplateParams p = mBuilder.mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_BIG) .hideProgress(true) .fillTextsFrom(mBuilder); - RemoteViews big = mBuilder.applyStandardTemplate( - R.layout.notification_template_material_big_media, p , null /* result */); + TemplateBindResult result = new TemplateBindResult(); + RemoteViews template = mBuilder.applyStandardTemplate( + R.layout.notification_template_material_big_media, p , result); for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) { if (i < actionCount) { - bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p); + bindMediaActionButton(template, + MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p); } else { - big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); + template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); } } - handleImage(big); - return big; - } - - private void handleImage(RemoteViews contentView) { - if (mBuilder.mN.hasLargeIcon()) { - contentView.setViewLayoutMarginDimen(R.id.title, RemoteViews.MARGIN_END, 0); - contentView.setViewLayoutMarginDimen(R.id.text, RemoteViews.MARGIN_END, 0); - } - } - - /** - * @hide - */ - @Override - protected boolean hasProgress() { - return false; + buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result, + DevFlags.DECORATION_PARTIAL); + return template; } } - - /** * Helper class for generating large-format notifications that include a large image attachment. * @@ -9896,9 +9759,7 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeContentView(boolean increasedHeight) { - RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */); - return buildIntoRemoteView(remoteViews, R.id.notification_content_container, - mBuilder.mN.contentView); + return makeMediaContentView(mBuilder.mN.contentView); } /** @@ -9906,24 +9767,10 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeBigContentView() { - RemoteViews customRemoteView = mBuilder.mN.bigContentView != null + RemoteViews customContent = mBuilder.mN.bigContentView != null ? mBuilder.mN.bigContentView : mBuilder.mN.contentView; - return makeBigContentViewWithCustomContent(customRemoteView); - } - - private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) { - RemoteViews remoteViews = super.makeBigContentView(); - if (remoteViews != null) { - return buildIntoRemoteView(remoteViews, R.id.notification_main_column, - customRemoteView); - } else if (customRemoteView != mBuilder.mN.contentView){ - remoteViews = super.makeContentView(false /* increasedHeight */); - return buildIntoRemoteView(remoteViews, R.id.notification_content_container, - customRemoteView); - } else { - return null; - } + return makeMediaBigContentView(customContent); } /** @@ -9931,10 +9778,10 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { - RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null + RemoteViews customContent = mBuilder.mN.headsUpContentView != null ? mBuilder.mN.headsUpContentView : mBuilder.mN.contentView; - return makeBigContentViewWithCustomContent(customRemoteView); + return makeMediaBigContentView(customContent); } /** @@ -9949,18 +9796,21 @@ public class Notification implements Parcelable return false; } - private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id, - RemoteViews customContent) { + private RemoteViews buildIntoRemoteView(RemoteViews template, RemoteViews customContent, + boolean headerless) { if (customContent != null) { // Need to clone customContent before adding, because otherwise it can no longer be // parceled independently of remoteViews. customContent = customContent.clone(); customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams)); - remoteViews.removeAllViews(id); - remoteViews.addView(id, customContent); - remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); + if (headerless) { + template.removeFromParent(R.id.notification_top_line); + } + template.removeAllViews(R.id.notification_main_column); + template.addView(R.id.notification_main_column, customContent); + template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); } - return remoteViews; + return template; } } @@ -12253,6 +12103,8 @@ public class Notification implements Parcelable boolean mHeaderless; boolean mHideAppName; boolean mHideTitle; + boolean mHideSubText; + boolean mHideTime; boolean mHideActions; boolean mHideProgress; boolean mHideSnoozeButton; @@ -12275,6 +12127,8 @@ public class Notification implements Parcelable mHeaderless = false; mHideAppName = false; mHideTitle = false; + mHideSubText = false; + mHideTime = false; mHideActions = false; mHideProgress = false; mHideSnoozeButton = false; @@ -12288,6 +12142,7 @@ public class Notification implements Parcelable summaryText = null; headerTextSecondary = null; maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; + hideLargeIcon = false; allowColorization = true; mReduceHighlights = false; return this; @@ -12312,6 +12167,16 @@ public class Notification implements Parcelable return this; } + public StandardTemplateParams hideSubText(boolean hideSubText) { + mHideSubText = hideSubText; + return this; + } + + public StandardTemplateParams hideTime(boolean hideTime) { + mHideTime = hideTime; + return this; + } + final StandardTemplateParams hideActions(boolean hideActions) { this.mHideActions = hideActions; return this; diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java index 05636de8e8e4..bd20f5bf82fd 100644 --- a/core/java/android/view/NotificationTopLineView.java +++ b/core/java/android/view/NotificationTopLineView.java @@ -26,6 +26,9 @@ import android.widget.RemoteViews; import com.android.internal.R; +import java.util.HashSet; +import java.util.Set; + /** * The top line of content in a notification view. * This includes the text views and badges but excludes the icon and the expander. @@ -34,17 +37,23 @@ import com.android.internal.R; */ @RemoteViews.RemoteView public class NotificationTopLineView extends ViewGroup { + private final OverflowAdjuster mOverflowAdjuster = new OverflowAdjuster(); private final int mGravityY; private final int mChildMinWidth; + private final int mChildHideWidth; @Nullable private View mAppName; @Nullable private View mTitle; private View mHeaderText; + private View mHeaderTextDivider; private View mSecondaryHeaderText; + private View mSecondaryHeaderTextDivider; private OnClickListener mFeedbackListener; private HeaderTouchListener mTouchListener = new HeaderTouchListener(); private View mFeedbackIcon; private int mHeaderTextMarginEnd; + private Set<View> mViewsToDisappear = new HashSet<>(); + private int mMaxAscent; private int mMaxDescent; @@ -66,6 +75,7 @@ public class NotificationTopLineView extends ViewGroup { super(context, attrs, defStyleAttr, defStyleRes); Resources res = getResources(); mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width); + mChildHideWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_hide_width); // NOTE: Implementation only supports TOP, BOTTOM, and CENTER_VERTICAL gravities, // with CENTER_VERTICAL being the default. @@ -88,7 +98,9 @@ public class NotificationTopLineView extends ViewGroup { mAppName = findViewById(R.id.app_name_text); mTitle = findViewById(R.id.title); mHeaderText = findViewById(R.id.header_text); + mHeaderTextDivider = findViewById(R.id.header_text_divider); mSecondaryHeaderText = findViewById(R.id.header_text_secondary); + mSecondaryHeaderTextDivider = findViewById(R.id.header_text_secondary_divider); mFeedbackIcon = findViewById(R.id.feedback); } @@ -125,48 +137,37 @@ public class NotificationTopLineView extends ViewGroup { maxChildHeight = Math.max(maxChildHeight, childHeight); } + mViewsToDisappear.clear(); // Ensure that there is at least enough space for the icons int endMargin = Math.max(mHeaderTextMarginEnd, getPaddingEnd()); if (totalWidth > givenWidth - endMargin) { int overFlow = totalWidth - givenWidth + endMargin; - // First shrink the app name, down to a minimum size - overFlow = shrinkViewForOverflow(heightSpec, overFlow, mAppName, mChildMinWidth); - - // Next, shrink the header text (this usually has subText) - // This shrinks the subtext first, but not all the way (yet!) - overFlow = shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, mChildMinWidth); - - // Next, shrink the secondary header text (this rarely has conversationTitle) - overFlow = shrinkViewForOverflow(heightSpec, overFlow, mSecondaryHeaderText, 0); - - // Next, shrink the title text (this has contentTitle; only in headerless views) - overFlow = shrinkViewForOverflow(heightSpec, overFlow, mTitle, mChildMinWidth); - - // Finally, if there is still overflow, shrink the header down to 0 if still necessary. - shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, 0); + mOverflowAdjuster.resetForOverflow(overFlow, heightSpec) + // First shrink the app name, down to a minimum size + .adjust(mAppName, null, mChildMinWidth) + // Next, shrink the header text (this usually has subText) + // This shrinks the subtext first, but not all the way (yet!) + .adjust(mHeaderText, mHeaderTextDivider, mChildMinWidth) + // Next, shrink the secondary header text (this rarely has conversationTitle) + .adjust(mSecondaryHeaderText, mSecondaryHeaderTextDivider, 0) + // Next, shrink the title text (this has contentTitle; only in headerless views) + .adjust(mTitle, null, mChildMinWidth) + // Next, shrink the header down to 0 if still necessary. + .adjust(mHeaderText, mHeaderTextDivider, 0) + // Finally, shrink the title to 0 if necessary (media is super cramped) + .adjust(mTitle, null, 0) + // Clean up + .finish(); } setMeasuredDimension(givenWidth, wrapHeight ? maxChildHeight : givenHeight); } - private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView, - int minimumWidth) { - if (targetView != null) { - final int oldWidth = targetView.getMeasuredWidth(); - if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) { - // we're still too big - int newSize = Math.max(minimumWidth, oldWidth - overFlow); - int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST); - targetView.measure(childWidthSpec, heightSpec); - overFlow -= oldWidth - newSize; - } - } - return overFlow; - } - @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - int left = getPaddingStart(); + final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; + final int width = getWidth(); + int start = getPaddingStart(); int childCount = getChildCount(); int ownHeight = b - t; int childSpace = ownHeight - mPaddingTop - mPaddingBottom; @@ -182,8 +183,6 @@ public class NotificationTopLineView extends ViewGroup { } int childHeight = child.getMeasuredHeight(); MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); - int layoutLeft; - int layoutRight; // Calculate vertical alignment of the views, accounting for the view baselines int childTop; @@ -219,19 +218,16 @@ public class NotificationTopLineView extends ViewGroup { default: childTop = mPaddingTop; } - - left += params.getMarginStart(); - int right = left + child.getMeasuredWidth(); - layoutLeft = left; - layoutRight = right; - left = right + params.getMarginEnd(); - - if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { - int ltrLeft = layoutLeft; - layoutLeft = getWidth() - layoutRight; - layoutRight = getWidth() - ltrLeft; + if (mViewsToDisappear.contains(child)) { + child.layout(start, childTop, start, childTop + childHeight); + } else { + start += params.getMarginStart(); + int end = start + child.getMeasuredWidth(); + int layoutLeft = isRtl ? width - end : start; + int layoutRight = isRtl ? width - start : end; + start = end + params.getMarginEnd(); + child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight); } - child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight); } updateTouchListener(); } @@ -400,4 +396,83 @@ public class NotificationTopLineView extends ViewGroup { } return mTouchListener.onTouchUp(upX, upY, downX, downY); } + + private final class OverflowAdjuster { + private int mOverflow; + private int mHeightSpec; + private View mRegrowView; + + OverflowAdjuster resetForOverflow(int overflow, int heightSpec) { + mOverflow = overflow; + mHeightSpec = heightSpec; + mRegrowView = null; + return this; + } + + /** + * Shrink the targetView's width by up to overFlow, down to minimumWidth. + * @param targetView the view to shrink the width of + * @param targetDivider a divider view which should be set to 0 width if the targetView is + * @param minimumWidth the minimum width allowed for the targetView + * @return this object + */ + OverflowAdjuster adjust(View targetView, View targetDivider, int minimumWidth) { + if (mOverflow <= 0 || targetView == null || targetView.getVisibility() == View.GONE) { + return this; + } + final int oldWidth = targetView.getMeasuredWidth(); + if (oldWidth <= minimumWidth) { + return this; + } + // we're too big + int newSize = Math.max(minimumWidth, oldWidth - mOverflow); + if (minimumWidth == 0 && newSize < mChildHideWidth + && mRegrowView != null && mRegrowView != targetView) { + // View is so small it's better to hide it entirely (and its divider and margins) + // so we can give that space back to another previously shrunken view. + newSize = 0; + } + + int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST); + targetView.measure(childWidthSpec, mHeightSpec); + mOverflow -= oldWidth - newSize; + + if (newSize == 0) { + mViewsToDisappear.add(targetView); + mOverflow -= getHorizontalMargins(targetView); + if (targetDivider != null && targetDivider.getVisibility() != GONE) { + mViewsToDisappear.add(targetDivider); + int oldDividerWidth = targetDivider.getMeasuredWidth(); + int dividerWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.AT_MOST); + targetDivider.measure(dividerWidthSpec, mHeightSpec); + mOverflow -= (oldDividerWidth + getHorizontalMargins(targetDivider)); + } + } + if (mOverflow < 0 && mRegrowView != null) { + // We're now under-flowing, so regrow the last view. + final int regrowCurrentSize = mRegrowView.getMeasuredWidth(); + final int maxSize = regrowCurrentSize - mOverflow; + int regrowWidthSpec = MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST); + mRegrowView.measure(regrowWidthSpec, mHeightSpec); + finish(); + return this; + } + + if (newSize != 0) { + // if we shrunk this view (but did not completely hide it) store it for potential + // re-growth if we proactively shorten a future view. + mRegrowView = targetView; + } + return this; + } + + void finish() { + resetForOverflow(0, 0); + } + + private int getHorizontalMargins(View view) { + MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams(); + return params.getMarginStart() + params.getMarginEnd(); + } + } } diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 02cbccc77c3f..a5b894dff46d 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -130,12 +130,6 @@ public final class SystemUiDeviceConfigFlags { // Flags related to media notifications /** - * (boolean) If {@code true}, enables the seekbar in compact media notifications. - */ - public static final String COMPACT_MEDIA_SEEKBAR_ENABLED = - "compact_media_notification_seekbar_enabled"; - - /** * (int) Maximum number of days to retain the salt for hashing direct share targets in logging */ public static final String HASH_SALT_MAX_DAYS = "hash_salt_max_days"; diff --git a/core/java/com/android/internal/widget/MediaNotificationView.java b/core/java/com/android/internal/widget/MediaNotificationView.java index f42d5da30b19..8ff3c106d229 100644 --- a/core/java/com/android/internal/widget/MediaNotificationView.java +++ b/core/java/com/android/internal/widget/MediaNotificationView.java @@ -19,31 +19,19 @@ package com.android.internal.widget; import android.annotation.Nullable; import android.content.Context; import android.util.AttributeSet; -import android.view.NotificationHeaderView; -import android.view.View; -import android.view.ViewGroup; import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.RemoteViews; import java.util.ArrayList; /** - * A TextView that can float around an image on the end. + * The Layout class which handles template details for the Notification.MediaStyle * * @hide */ @RemoteViews.RemoteView public class MediaNotificationView extends FrameLayout { - private final int mNotificationContentMarginEnd; - private final int mNotificationContentImageMarginEnd; - private ImageView mRightIcon; - private View mActions; - private NotificationHeaderView mHeader; - private View mMainColumn; - private View mMediaContent; - private int mImagePushIn; private ArrayList<VisibilityChangeListener> mListeners; public MediaNotificationView(Context context) { @@ -58,120 +46,14 @@ public class MediaNotificationView extends FrameLayout { this(context, attrs, defStyleAttr, 0); } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - boolean hasIcon = mRightIcon.getVisibility() != GONE; - if (!hasIcon) { - resetHeaderIndention(); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int mode = MeasureSpec.getMode(widthMeasureSpec); - boolean reMeasure = false; - mImagePushIn = 0; - if (hasIcon && mode != MeasureSpec.UNSPECIFIED) { - int size = MeasureSpec.getSize(widthMeasureSpec); - size = size - mActions.getMeasuredWidth(); - ViewGroup.MarginLayoutParams layoutParams = - (MarginLayoutParams) mRightIcon.getLayoutParams(); - int imageEndMargin = layoutParams.getMarginEnd(); - size -= imageEndMargin; - int fullHeight = mMediaContent.getMeasuredHeight(); - if (size > fullHeight) { - size = fullHeight; - } else if (size < fullHeight) { - size = Math.max(0, size); - mImagePushIn = fullHeight - size; - } - if (layoutParams.width != fullHeight || layoutParams.height != fullHeight) { - layoutParams.width = fullHeight; - layoutParams.height = fullHeight; - mRightIcon.setLayoutParams(layoutParams); - reMeasure = true; - } - - // lets ensure that the main column doesn't run into the image - ViewGroup.MarginLayoutParams params - = (MarginLayoutParams) mMainColumn.getLayoutParams(); - int marginEnd = size + imageEndMargin + mNotificationContentMarginEnd; - if (marginEnd != params.getMarginEnd()) { - params.setMarginEnd(marginEnd); - mMainColumn.setLayoutParams(params); - reMeasure = true; - } - // TODO(b/172652345): validate all this logic (especially positioning of expand button) - // margin for the entire header line - int headerMarginEnd = imageEndMargin; - // margin for the header text (not including the expand button and other icons) - int headerExtraMarginEnd = Math.max(0, - size + imageEndMargin - mHeader.getTopLineBaseMarginEnd()); - if (headerExtraMarginEnd != mHeader.getTopLineExtraMarginEnd()) { - mHeader.setTopLineExtraMarginEnd(headerExtraMarginEnd); - reMeasure = true; - } - params = (MarginLayoutParams) mHeader.getLayoutParams(); - if (params.getMarginEnd() != headerMarginEnd) { - params.setMarginEnd(headerMarginEnd); - mHeader.setLayoutParams(params); - reMeasure = true; - } - if (mHeader.getPaddingEnd() != mNotificationContentImageMarginEnd) { - mHeader.setPaddingRelative(mHeader.getPaddingStart(), - mHeader.getPaddingTop(), - mNotificationContentImageMarginEnd, - mHeader.getPaddingBottom()); - reMeasure = true; - } - } - if (reMeasure) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (mImagePushIn > 0) { - if (this.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { - mImagePushIn *= -1; - } - mRightIcon.layout(mRightIcon.getLeft() + mImagePushIn, mRightIcon.getTop(), - mRightIcon.getRight() + mImagePushIn, mRightIcon.getBottom()); - } - } - - private void resetHeaderIndention() { - if (mHeader.getPaddingEnd() != mNotificationContentMarginEnd) { - mHeader.setPaddingRelative(mHeader.getPaddingStart(), - mHeader.getPaddingTop(), - mNotificationContentMarginEnd, - mHeader.getPaddingBottom()); - } - ViewGroup.MarginLayoutParams headerParams = - (MarginLayoutParams) mHeader.getLayoutParams(); - headerParams.setMarginEnd(0); - if (headerParams.getMarginEnd() != 0) { - headerParams.setMarginEnd(0); - mHeader.setLayoutParams(headerParams); - } - } - public MediaNotificationView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mNotificationContentMarginEnd = context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_content_margin_end); - mNotificationContentImageMarginEnd = context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_content_image_margin_end); } @Override protected void onFinishInflate() { super.onFinishInflate(); - mRightIcon = findViewById(com.android.internal.R.id.right_icon); - mActions = findViewById(com.android.internal.R.id.media_actions); - mHeader = findViewById(com.android.internal.R.id.notification_header); - mMainColumn = findViewById(com.android.internal.R.id.notification_main_column); - mMediaContent = findViewById(com.android.internal.R.id.notification_media_content); } @Override diff --git a/core/res/res/layout/notification_material_media_action.xml b/core/res/res/layout/notification_material_media_action.xml index dd79a0bb1817..5f1b60e8d0f2 100644 --- a/core/res/res/layout/notification_material_media_action.xml +++ b/core/res/res/layout/notification_material_media_action.xml @@ -24,7 +24,6 @@ android:paddingTop="8dp" android:paddingStart="8dp" android:paddingEnd="8dp" - android:layout_marginEnd="2dp" android:gravity="center" android:background="@drawable/notification_material_media_action_background" android:visibility="gone" diff --git a/core/res/res/layout/notification_material_media_seekbar.xml b/core/res/res/layout/notification_material_media_seekbar.xml deleted file mode 100644 index 4aa8acc363fa..000000000000 --- a/core/res/res/layout/notification_material_media_seekbar.xml +++ /dev/null @@ -1,65 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2019 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/notification_media_progress" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:layout_alignParentBottom="true" - > - <SeekBar android:id="@+id/notification_media_progress_bar" - style="@style/Widget.ProgressBar.Horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:maxHeight="3dp" - android:paddingTop="24dp" - android:paddingBottom="24dp" - android:clickable="true" - android:layout_marginBottom="-24dp" - android:layout_marginTop="-12dp" - android:splitTrack="false" - /> - <FrameLayout - android:id="@+id/notification_media_progress_time" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginBottom="11dp" - > - - <!-- width is set to "match_parent" to avoid extra layout calls --> - <TextView android:id="@+id/notification_media_elapsed_time" - style="@style/Widget.DeviceDefault.Notification.Text" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_marginStart="@dimen/notification_content_margin_start" - android:gravity="start" - /> - - <TextView android:id="@+id/notification_media_total_time" - style="@style/Widget.DeviceDefault.Notification.Text" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_marginEnd="@dimen/notification_content_margin_end" - android:gravity="end" - /> - </FrameLayout> -</LinearLayout>
\ No newline at end of file diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index bad9a6ba6184..e644cd55a86a 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -46,20 +46,6 @@ 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:clipToOutline="true" - android:importantForAccessibility="no" - android:scaleType="centerCrop" - /> - <FrameLayout android:id="@+id/alternate_expand_target" android:layout_width="@dimen/notification_content_margin_start" @@ -68,95 +54,116 @@ 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"> - - <include layout="@layout/notification_expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical|end" - /> - - </FrameLayout> - <LinearLayout - android:id="@+id/notification_headerless_view_column" + android:id="@+id/notification_headerless_view_row" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="@dimen/notification_headerless_margin_twoline" - android:layout_marginTop="@dimen/notification_headerless_margin_twoline" - android:orientation="vertical" + android:layout_height="match_parent" + android:layout_marginStart="@dimen/notification_content_margin_start" + android:orientation="horizontal" > - <!-- 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" - android:clipChildren="false" - android:theme="@style/Theme.DeviceDefault.Notification" + <LinearLayout + android:id="@+id/notification_headerless_view_column" + android:layout_width="0px" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:layout_marginBottom="@dimen/notification_headerless_margin_twoline" + android:layout_marginTop="@dimen/notification_headerless_margin_twoline" + android:orientation="vertical" > - <!-- - 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" + <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" - /> + android:layout_height="@dimen/notification_headerless_line_height" + android:clipChildren="false" + android:theme="@style/Theme.DeviceDefault.Notification" + > - <include layout="@layout/notification_top_line_views" /> + <!-- + 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" + /> - </NotificationTopLineView> + <include layout="@layout/notification_top_line_views" /> - <LinearLayout - android:id="@+id/notification_main_column" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/notification_heading_margin_end" - android:layout_marginStart="@dimen/notification_content_margin_start" - android:orientation="vertical" - > + </NotificationTopLineView> - <com.android.internal.widget.NotificationVanishingFrameLayout + <LinearLayout + android:id="@+id/notification_main_column" android:layout_width="match_parent" - android:layout_height="@dimen/notification_headerless_line_height" + android:layout_height="wrap_content" + android:orientation="vertical" > - <!-- This is the simplest way to keep this text vertically centered without using - gravity="center_vertical" which causes jumpiness in expansion animations. --> + + <com.android.internal.widget.NotificationVanishingFrameLayout + android:layout_width="match_parent" + android:layout_height="@dimen/notification_headerless_line_height" + > + <!-- This is the simplest way to keep this text vertically centered without + gravity="center_vertical" which causes jumpiness in expansion animations. --> + <include + layout="@layout/notification_template_text" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_text_height" + android:layout_gravity="center_vertical" + android:layout_marginTop="0dp" + /> + </com.android.internal.widget.NotificationVanishingFrameLayout> + <include - layout="@layout/notification_template_text" + layout="@layout/notification_template_progress" android:layout_width="match_parent" - android:layout_height="@dimen/notification_text_height" - android:layout_gravity="center_vertical" - android:layout_marginTop="0dp" + android:layout_height="@dimen/notification_headerless_line_height" /> - </com.android.internal.widget.NotificationVanishingFrameLayout> - <include - layout="@layout/notification_template_progress" - android:layout_width="match_parent" - android:layout_height="@dimen/notification_headerless_line_height" - /> + </LinearLayout> </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_marginStart="@dimen/notification_right_icon_content_margin" + android:background="@drawable/notification_large_icon_outline" + android:clipToOutline="true" + android:importantForAccessibility="no" + android:scaleType="centerCrop" + /> + + <FrameLayout + android:id="@+id/expand_button_touch_container" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="@dimen/notification_content_margin_end" + > + + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + /> + + </FrameLayout> + </LinearLayout> </com.android.internal.widget.NotificationMaxHeightFrameLayout> diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml index aa20ad36720b..ff64315572ca 100644 --- a/core/res/res/layout/notification_template_material_big_media.xml +++ b/core/res/res/layout/notification_template_material_big_media.xml @@ -20,17 +20,8 @@ android:id="@+id/status_bar_latest_event_content" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="#00000000" android:tag="bigMediaNarrow" > - <!-- The size will actually be determined at runtime --> - <ImageView - android:id="@+id/right_icon" - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_gravity="top|end" - android:scaleType="centerCrop" - /> <include layout="@layout/notification_template_header" @@ -49,46 +40,27 @@ android:id="@+id/notification_main_column" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="46dp" + android:layout_marginTop="@dimen/notification_content_margin_top" android:layout_marginStart="@dimen/notification_content_margin_start" android:layout_marginBottom="@dimen/notification_content_margin" android:layout_marginEnd="@dimen/notification_content_margin_end" android:orientation="vertical" > - <!-- TODO(b/172652345): fix the media style --> - <!--<include layout="@layout/notification_template_part_line1"/>--> - <!--<include layout="@layout/notification_template_text"/>--> - - <TextView android:id="@+id/title" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:textAlignment="viewStart" - /> - - <com.android.internal.widget.ImageFloatingTextView - style="@style/Widget.DeviceDefault.Notification.Text" - android:id="@+id/text" - android:layout_width="match_parent" - android:layout_height="@dimen/notification_text_height" - android:layout_gravity="top" - android:layout_marginTop="0.5dp" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:gravity="top" - android:singleLine="true" - android:textAlignment="viewStart" - /> + <include layout="@layout/notification_template_part_line1"/> + <include layout="@layout/notification_template_text"/> </LinearLayout> <LinearLayout android:id="@+id/media_actions" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="-21dp" + android:paddingStart="44dp" + android:paddingEnd="44dp" + android:paddingBottom="@dimen/media_notification_actions_padding_bottom" + android:gravity="top" android:orientation="horizontal" android:layoutDirection="ltr" - style="@style/NotificationMediaActionContainer" > <include @@ -117,10 +89,8 @@ /> </LinearLayout> - <ViewStub - android:id="@+id/notification_media_seekbar_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - /> </LinearLayout> + + <include layout="@layout/notification_template_right_icon" /> + </com.android.internal.widget.MediaNotificationView> diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml index 542e59df76c0..2991b1706a64 100644 --- a/core/res/res/layout/notification_template_material_media.xml +++ b/core/res/res/layout/notification_template_material_media.xml @@ -19,101 +19,173 @@ android:id="@+id/status_bar_latest_event_content" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="#00000000" + android:layout_height="@dimen/notification_min_height" android:tag="media" > - <ImageView android:id="@+id/right_icon" - android:layout_width="0dp" - android:layout_height="0dp" - android:adjustViewBounds="true" - android:layout_gravity="top|end" + + + <ImageView + android:id="@+id/left_icon" + android:layout_width="@dimen/notification_left_icon_size" + android:layout_height="@dimen/notification_left_icon_size" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="@dimen/notification_left_icon_start" + android:background="@drawable/notification_large_icon_outline" + android:clipToOutline="true" + android:importantForAccessibility="no" android:scaleType="centerCrop" - /> - <include layout="@layout/notification_template_header" - android:layout_width="match_parent" - android:layout_height="@dimen/media_notification_header_height" + android:visibility="gone" + /> + + <com.android.internal.widget.CachingIconView + android:id="@+id/icon" + android:layout_width="@dimen/notification_icon_circle_size" + android:layout_height="@dimen/notification_icon_circle_size" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="@dimen/notification_icon_circle_start" + android:background="@drawable/notification_icon_circle" + android:padding="@dimen/notification_icon_circle_padding" + /> + + <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" /> + <LinearLayout + android:id="@+id/notification_headerless_view_row" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:id="@+id/notification_media_content" + android:layout_height="match_parent" + android:layout_marginStart="@dimen/notification_content_margin_start" + android:orientation="horizontal" > + <LinearLayout - android:id="@+id/notification_main_column" - android:layout_width="match_parent" + android:id="@+id/notification_headerless_view_column" + android:layout_width="0px" android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_marginStart="@dimen/notification_content_margin_start" - android:layout_marginTop="46dp" - android:layout_alignParentTop="true" - android:tag="media" + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:layout_marginBottom="@dimen/notification_headerless_margin_twoline" + android:layout_marginTop="@dimen/notification_headerless_margin_twoline" + android:orientation="vertical" > - <LinearLayout - android:id="@+id/notification_content_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="fill_vertical" - android:layout_weight="1" - android:paddingBottom="@dimen/notification_content_margin" - android:orientation="vertical" + + <NotificationTopLineView + android:id="@+id/notification_top_line" + android:layout_width="wrap_content" + android:layout_height="@dimen/notification_headerless_line_height" + android:clipChildren="false" + android:theme="@style/Theme.DeviceDefault.Notification" > - <!-- TODO(b/172652345): fix the media style --> - <!--<include layout="@layout/notification_template_part_line1"/>--> - <!--<include layout="@layout/notification_template_text"/>--> - <TextView android:id="@+id/title" - android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:textAlignment="viewStart" - /> + <!-- + 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. + --> - <com.android.internal.widget.ImageFloatingTextView - style="@style/Widget.DeviceDefault.Notification.Text" - android:id="@+id/text" - android:layout_width="match_parent" - android:layout_height="@dimen/notification_text_height" - android:layout_gravity="top" - android:layout_marginTop="0.5dp" + <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:gravity="top" android:singleLine="true" android:textAlignment="viewStart" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" /> - </LinearLayout> + + <include layout="@layout/notification_top_line_views" /> + + </NotificationTopLineView> + <LinearLayout - android:id="@+id/media_actions" - android:layout_width="wrap_content" + android:id="@+id/notification_main_column" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="top|end" - android:layout_marginStart="10dp" - android:layoutDirection="ltr" - android:orientation="horizontal" + android:orientation="vertical" > + + <com.android.internal.widget.NotificationVanishingFrameLayout + android:layout_width="match_parent" + android:layout_height="@dimen/notification_headerless_line_height" + > + <!-- This is the simplest way to keep this text vertically centered without + gravity="center_vertical" which causes jumpiness in expansion animations. --> + <include + layout="@layout/notification_template_text" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_text_height" + android:layout_gravity="center_vertical" + android:layout_marginTop="0dp" + /> + </com.android.internal.widget.NotificationVanishingFrameLayout> + <include - layout="@layout/notification_material_media_action" - android:id="@+id/action0" + layout="@layout/notification_template_progress" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_headerless_line_height" + /> + + </LinearLayout> + + </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_marginStart="@dimen/notification_right_icon_content_margin" + android:background="@drawable/notification_large_icon_outline" + android:clipToOutline="true" + android:importantForAccessibility="no" + android:scaleType="centerCrop" + /> + + <LinearLayout + android:id="@+id/media_actions" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layoutDirection="ltr" + android:orientation="horizontal" + > + <include + layout="@layout/notification_material_media_action" + android:id="@+id/action0" /> - <include - layout="@layout/notification_material_media_action" - android:id="@+id/action1" + <include + layout="@layout/notification_material_media_action" + android:id="@+id/action1" /> - <include - layout="@layout/notification_material_media_action" - android:id="@+id/action2" + <include + layout="@layout/notification_material_media_action" + android:id="@+id/action2" /> - </LinearLayout> </LinearLayout> - <ViewStub android:id="@+id/notification_media_seekbar_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - /> + + <FrameLayout + android:id="@+id/expand_button_touch_container" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="@dimen/notification_content_margin_end" + > + + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + /> + + </FrameLayout> + </LinearLayout> </com.android.internal.widget.MediaNotificationView> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 43dbd38d1dc4..afbbe467078c 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -226,9 +226,6 @@ <!-- The margin on the end of the top-line content views (accommodates the expander) --> <dimen name="notification_heading_margin_end">56dp</dimen> - <!-- The margin for text at the end of the image view for media notifications --> - <dimen name="notification_media_image_margin_end">72dp</dimen> - <!-- The height of the notification action list --> <dimen name="notification_action_list_height">60dp</dimen> @@ -345,6 +342,9 @@ <!-- The minimum width of the app name in the header if it shrinks --> <dimen name="notification_header_shrink_min_width">72dp</dimen> + <!-- The minimum width of optional header fields below which the view is simply hidden --> + <dimen name="notification_header_shrink_hide_width">24sp</dimen> + <!-- The size of the media actions in the media notification. --> <dimen name="media_notification_action_button_size">48dp</dimen> @@ -360,9 +360,6 @@ <!-- The absolute height for the header in a media notification. --> <dimen name="media_notification_header_height">@dimen/notification_header_height</dimen> - <!-- The margin of the content to an image--> - <dimen name="notification_content_image_margin_end">8dp</dimen> - <!-- The padding at the end of actions when the snooze and bubble buttons are gone--> <dimen name="snooze_and_bubble_gone_padding_end">12dp</dimen> @@ -483,9 +480,6 @@ <!-- Top padding for notification when text is large and narrow (i.e. it has 3 lines --> <dimen name="notification_top_pad_large_text_narrow">-4dp</dimen> - <!-- Padding for notification icon when drawn with circle around it --> - <dimen name="notification_large_icon_circle_padding">11dp</dimen> - <!-- The margin on top of the text of the notification --> <dimen name="notification_text_margin_top">6dp</dimen> @@ -736,12 +730,10 @@ <dimen name="notification_big_picture_max_height">284dp</dimen> <!-- The maximum width of a big picture in a notification. The images will be reduced to that width in case they are bigger. This value is determined by the standard panel size --> <dimen name="notification_big_picture_max_width">416dp</dimen> - <!-- The maximum height of a image in a media notification. The images will be reduced to that height in case they are bigger. This value is determined by the expanded media template--> - <dimen name="notification_media_image_max_height">140dp</dimen> - <!-- The maximum width of a image in a media notification. The images will be reduced to that width in case they are bigger.--> - <dimen name="notification_media_image_max_width">280dp</dimen> <!-- The size of the right icon --> <dimen name="notification_right_icon_size">48dp</dimen> + <!-- The margin between the right icon and the content. --> + <dimen name="notification_right_icon_content_margin">12dp</dimen> <!-- The top and bottom margin of the right icon in the normal notification states --> <dimen name="notification_right_icon_headerless_margin">20dp</dimen> <!-- The top margin of the right icon in the "big" notification states --> @@ -762,10 +754,6 @@ <dimen name="notification_big_picture_max_height_low_ram">208dp</dimen> <!-- The maximum width of a big picture in a notification. The images will be reduced to that width in case they are bigger. --> <dimen name="notification_big_picture_max_width_low_ram">294dp</dimen> - <!-- The maximum height of a image in a media notification. The images will be reduced to that height in case they are bigger. --> - <dimen name="notification_media_image_max_height_low_ram">100dp</dimen> - <!-- The maximum width of a image in a media notification. The images will be reduced to that width in case they are bigger.--> - <dimen name="notification_media_image_max_width_low_ram">100dp</dimen> <!-- The size of the right icon image when on low ram --> <dimen name="notification_right_icon_size_low_ram">@dimen/notification_right_icon_size</dimen> <!-- The maximum size of the grayscale icon --> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index c7ded0cfa3a2..fbf67e0d84ac 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1481,17 +1481,6 @@ please see styles_device_defaults.xml. <item name="android:windowExitAnimation">@anim/slide_out_down</item> </style> - <!-- The style for the container of media actions in a notification. --> - <!-- @hide --> - <style name="NotificationMediaActionContainer"> - <item name="layout_width">wrap_content</item> - <item name="layout_height">wrap_content</item> - <item name="layout_marginTop">-21dp</item> - <item name="paddingStart">8dp</item> - <item name="paddingBottom">@dimen/media_notification_actions_padding_bottom</item> - <item name="gravity">top</item> - </style> - <!-- The style for normal action button on notification --> <style name="NotificationAction" parent="Widget.Material.Light.Button.Borderless.Small"> <item name="textColor">@color/notification_action_button_text_color</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1d74d85fb9db..d6a6f4dea220 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -200,12 +200,7 @@ <java-symbol type="id" name="action2" /> <java-symbol type="id" name="action3" /> <java-symbol type="id" name="action4" /> - <java-symbol type="id" name="notification_media_seekbar_container" /> <java-symbol type="id" name="notification_media_content" /> - <java-symbol type="id" name="notification_media_progress" /> - <java-symbol type="id" name="notification_media_progress_bar" /> - <java-symbol type="id" name="notification_media_elapsed_time" /> - <java-symbol type="id" name="notification_media_total_time" /> <java-symbol type="id" name="big_picture" /> <java-symbol type="id" name="big_text" /> <java-symbol type="id" name="chronometer" /> @@ -525,7 +520,6 @@ <java-symbol type="dimen" name="notification_top_pad_narrow" /> <java-symbol type="dimen" name="notification_top_pad_large_text" /> <java-symbol type="dimen" name="notification_top_pad_large_text_narrow" /> - <java-symbol type="dimen" name="notification_large_icon_circle_padding" /> <java-symbol type="dimen" name="notification_badge_size" /> <java-symbol type="dimen" name="immersive_mode_cling_width" /> <java-symbol type="dimen" name="accessibility_magnification_indicator_width" /> @@ -1564,7 +1558,6 @@ <java-symbol type="layout" name="immersive_mode_cling" /> <java-symbol type="layout" name="user_switching_dialog" /> <java-symbol type="layout" name="common_tab_settings" /> - <java-symbol type="layout" name="notification_material_media_seekbar" /> <java-symbol type="layout" name="resolver_list_per_profile" /> <java-symbol type="layout" name="chooser_list_per_profile" /> <java-symbol type="layout" name="resolver_empty_states" /> @@ -2921,6 +2914,7 @@ <java-symbol type="drawable" name="ic_expand_bundle" /> <java-symbol type="drawable" name="ic_collapse_bundle" /> <java-symbol type="dimen" name="notification_header_shrink_min_width" /> + <java-symbol type="dimen" name="notification_header_shrink_hide_width" /> <java-symbol type="dimen" name="notification_content_margin_start" /> <java-symbol type="dimen" name="notification_content_margin_end" /> <java-symbol type="dimen" name="notification_heading_margin_end" /> @@ -3010,7 +3004,6 @@ <java-symbol type="string" name="new_sms_notification_content" /> <java-symbol type="dimen" name="media_notification_expanded_image_margin_bottom" /> - <java-symbol type="dimen" name="notification_content_image_margin_end" /> <java-symbol type="bool" name="config_strongAuthRequiredOnBoot" /> @@ -3019,8 +3012,6 @@ <java-symbol type="id" name="aerr_wait" /> - <java-symbol type="id" name="notification_content_container" /> - <java-symbol type="plurals" name="duration_minutes_shortest" /> <java-symbol type="plurals" name="duration_hours_shortest" /> <java-symbol type="plurals" name="duration_days_shortest" /> @@ -3138,7 +3129,6 @@ <java-symbol type="bool" name="config_supportPreRebootSecurityLogs" /> - <java-symbol type="dimen" name="notification_media_image_margin_end" /> <java-symbol type="id" name="notification_action_list_margin_target" /> <java-symbol type="dimen" name="notification_action_disabled_alpha" /> <java-symbol type="id" name="tag_margin_end_when_icon_visible" /> @@ -3461,17 +3451,14 @@ <java-symbol type="dimen" name="notification_big_picture_max_height"/> <java-symbol type="dimen" name="notification_big_picture_max_width"/> - <java-symbol type="dimen" name="notification_media_image_max_width"/> - <java-symbol type="dimen" name="notification_media_image_max_height"/> <java-symbol type="dimen" name="notification_right_icon_size"/> + <java-symbol type="dimen" name="notification_right_icon_content_margin"/> <java-symbol type="dimen" name="notification_actions_icon_drawable_size"/> <java-symbol type="dimen" name="notification_custom_view_max_image_height"/> <java-symbol type="dimen" name="notification_custom_view_max_image_width"/> <java-symbol type="dimen" name="notification_big_picture_max_height_low_ram"/> <java-symbol type="dimen" name="notification_big_picture_max_width_low_ram"/> - <java-symbol type="dimen" name="notification_media_image_max_width_low_ram"/> - <java-symbol type="dimen" name="notification_media_image_max_height_low_ram"/> <java-symbol type="dimen" name="notification_right_icon_size_low_ram"/> <java-symbol type="dimen" name="notification_grayscale_icon_max_size"/> <java-symbol type="dimen" name="notification_custom_view_max_image_height_low_ram"/> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 252938abffdb..0ea63643d24e 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -16,8 +16,6 @@ package android.app; -import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; @@ -99,23 +97,6 @@ public class NotificationTest { } @Test - public void testColorSatisfiedWhenBgDarkTextDarker() { - Notification.Builder builder = getMediaNotification(); - Notification n = builder.build(); - - assertTrue(n.isColorized()); - - // An initial guess where the foreground color is actually darker than an already dark bg - int backgroundColor = 0xff585868; - int initialForegroundColor = 0xff505868; - builder.setColorPalette(backgroundColor, initialForegroundColor); - int primaryTextColor = builder.getPrimaryTextColor(builder.mParams); - assertTrue(satisfiesTextContrast(primaryTextColor, backgroundColor)); - int secondaryTextColor = builder.getSecondaryTextColor(builder.mParams); - assertTrue(satisfiesTextContrast(secondaryTextColor, backgroundColor)); - } - - @Test public void testHasCompletedProgress_noProgress() { Notification n = new Notification.Builder(mContext).build(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index ca3923f06a13..c565a271bdd7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -38,8 +38,6 @@ import android.media.session.PlaybackState; import android.os.AsyncTask; import android.os.Trace; import android.os.UserHandle; -import android.provider.DeviceConfig; -import android.provider.DeviceConfig.Properties; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; @@ -48,7 +46,6 @@ import android.util.Log; import android.view.View; import android.widget.ImageView; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; @@ -147,23 +144,6 @@ public class NotificationMediaManager implements Dumpable { private ImageView mBackdropFront; private ImageView mBackdropBack; - private boolean mShowCompactMediaSeekbar; - private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener = - new DeviceConfig.OnPropertiesChangedListener() { - @Override - public void onPropertiesChanged(Properties properties) { - for (String name : properties.getKeyset()) { - if (SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED.equals(name)) { - String value = properties.getString(name, null); - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: compact media seekbar flag updated: " + value); - } - mShowCompactMediaSeekbar = "true".equals(value); - } - } - } - }; - private final MediaController.Callback mMediaListener = new MediaController.Callback() { @Override public void onPlaybackStateChanged(PlaybackState state) { @@ -231,14 +211,6 @@ public class NotificationMediaManager implements Dumpable { setupNotifPipeline(); mUsingNotifPipeline = true; } - - mShowCompactMediaSeekbar = "true".equals( - DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED)); - - deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mContext.getMainExecutor(), - mPropertiesChangedListener); } private void setupNotifPipeline() { @@ -405,10 +377,6 @@ public class NotificationMediaManager implements Dumpable { return mMediaMetadata; } - public boolean getShowCompactMediaSeekbar() { - return mShowCompactMediaSeekbar; - } - public Icon getMediaIcon() { if (mMediaNotificationKey == null) { return null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java deleted file mode 100644 index f5a76f0499e2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Shader; -import android.graphics.drawable.Drawable; - -/** - * A utility class to colorize bitmaps with a color gradient and a special blending mode - */ -public class ImageGradientColorizer { - public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) { - int width = drawable.getIntrinsicWidth(); - int height = drawable.getIntrinsicHeight(); - int size = Math.min(width, height); - int widthInset = (width - size) / 2; - int heightInset = (height - size) / 2; - drawable = drawable.mutate(); - drawable.setBounds(- widthInset, - heightInset, width - widthInset, height - heightInset); - Bitmap newBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(newBitmap); - - // Values to calculate the luminance of a color - float lr = 0.2126f; - float lg = 0.7152f; - float lb = 0.0722f; - - // Extract the red, green, blue components of the color extraction color in - // float and int form - int tri = Color.red(backgroundColor); - int tgi = Color.green(backgroundColor); - int tbi = Color.blue(backgroundColor); - - float tr = tri / 255f; - float tg = tgi / 255f; - float tb = tbi / 255f; - - // Calculate the luminance of the color extraction color - float cLum = (tr * lr + tg * lg + tb * lb) * 255; - - ColorMatrix m = new ColorMatrix(new float[] { - lr, lg, lb, 0, tri - cLum, - lr, lg, lb, 0, tgi - cLum, - lr, lg, lb, 0, tbi - cLum, - 0, 0, 0, 1, 0, - }); - - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - LinearGradient linearGradient = new LinearGradient(0, 0, size, 0, - new int[] {0, Color.argb(0.5f, 1, 1, 1), Color.BLACK}, - new float[] {0.0f, 0.4f, 1.0f}, Shader.TileMode.CLAMP); - paint.setShader(linearGradient); - Bitmap fadeIn = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - Canvas fadeInCanvas = new Canvas(fadeIn); - drawable.clearColorFilter(); - drawable.draw(fadeInCanvas); - - if (isRtl) { - // Let's flip the gradient - fadeInCanvas.translate(size, 0); - fadeInCanvas.scale(-1, 1); - } - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); - fadeInCanvas.drawPaint(paint); - - Paint coloredPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - coloredPaint.setColorFilter(new ColorMatrixColorFilter(m)); - coloredPaint.setAlpha((int) (0.5f * 255)); - canvas.drawBitmap(fadeIn, 0, 0, coloredPaint); - - linearGradient = new LinearGradient(0, 0, size, 0, - new int[] {0, Color.argb(0.5f, 1, 1, 1), Color.BLACK}, - new float[] {0.0f, 0.6f, 1.0f}, Shader.TileMode.CLAMP); - paint.setShader(linearGradient); - fadeInCanvas.drawPaint(paint); - canvas.drawBitmap(fadeIn, 0, 0, null); - - return newBitmap; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java index 2586e9403e01..732c115571f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java @@ -16,237 +16,30 @@ package com.android.systemui.statusbar.notification; -import android.app.Notification; -import android.content.Context; import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; -import android.util.LayoutDirection; -import androidx.annotation.VisibleForTesting; import androidx.palette.graphics.Palette; -import com.android.internal.util.ContrastColorUtil; -import com.android.settingslib.Utils; - import java.util.List; /** - * A class the processes media notifications and extracts the right text and background colors. + * A gutted class that now contains only a color extraction utility used by the + * MediaArtworkProcessor, which has otherwise supplanted this. + * + * TODO(b/182926117): move this into MediaArtworkProcessor.kt */ public class MediaNotificationProcessor { /** - * The fraction below which we select the vibrant instead of the light/dark vibrant color - */ - private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f; - - /** - * Minimum saturation that a muted color must have if there exists if deciding between two - * colors - */ - private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f; - - /** - * Minimum fraction that any color must have to be picked up as a text color - */ - private static final double MINIMUM_IMAGE_FRACTION = 0.002; - - /** - * The population fraction to select the dominant color as the text color over a the colored - * ones. - */ - private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f; - - /** * The population fraction to select a white or black color as the background over a color. */ private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f; private static final float BLACK_MAX_LIGHTNESS = 0.08f; private static final float WHITE_MIN_LIGHTNESS = 0.90f; private static final int RESIZE_BITMAP_AREA = 150 * 150; - private final ImageGradientColorizer mColorizer; - private final Context mContext; - private final Palette.Filter mBlackWhiteFilter = (rgb, hsl) -> !isWhiteOrBlack(hsl); - - /** - * The context of the notification. This is the app context of the package posting the - * notification. - */ - private final Context mPackageContext; - - public MediaNotificationProcessor(Context context, Context packageContext) { - this(context, packageContext, new ImageGradientColorizer()); - } - - @VisibleForTesting - MediaNotificationProcessor(Context context, Context packageContext, - ImageGradientColorizer colorizer) { - mContext = context; - mPackageContext = packageContext; - mColorizer = colorizer; - } - - /** - * Processes a builder of a media notification and calculates the appropriate colors that should - * be used. - * - * @param notification the notification that is being processed - * @param builder the recovered builder for the notification. this will be modified - */ - public void processNotification(Notification notification, Notification.Builder builder) { - Icon largeIcon = notification.getLargeIcon(); - Bitmap bitmap = null; - Drawable drawable = null; - if (largeIcon != null) { - // We're transforming the builder, let's make sure all baked in RemoteViews are - // rebuilt! - builder.setRebuildStyledRemoteViews(true); - drawable = largeIcon.loadDrawable(mPackageContext); - int backgroundColor = 0; - if (notification.isColorizedMedia()) { - int width = drawable.getIntrinsicWidth(); - int height = drawable.getIntrinsicHeight(); - int area = width * height; - if (area > RESIZE_BITMAP_AREA) { - double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area); - width = (int) (factor * width); - height = (int) (factor * height); - } - bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, width, height); - drawable.draw(canvas); - - Palette.Builder paletteBuilder = generateArtworkPaletteBuilder(bitmap); - Palette palette = paletteBuilder.generate(); - Palette.Swatch backgroundSwatch = findBackgroundSwatch(palette); - backgroundColor = backgroundSwatch.getRgb(); - // we want most of the full region again, slightly shifted to the right - float textColorStartWidthFraction = 0.4f; - paletteBuilder.setRegion((int) (bitmap.getWidth() * textColorStartWidthFraction), 0, - bitmap.getWidth(), - bitmap.getHeight()); - // We're not filtering on white or black - if (!isWhiteOrBlack(backgroundSwatch.getHsl())) { - final float backgroundHue = backgroundSwatch.getHsl()[0]; - paletteBuilder.addFilter((rgb, hsl) -> { - // at least 10 degrees hue difference - float diff = Math.abs(hsl[0] - backgroundHue); - return diff > 10 && diff < 350; - }); - } - paletteBuilder.addFilter(mBlackWhiteFilter); - palette = paletteBuilder.generate(); - int foregroundColor = selectForegroundColor(backgroundColor, palette); - builder.setColorPalette(backgroundColor, foregroundColor); - } else { - backgroundColor = Utils.getColorAttr(mContext, android.R.attr.colorBackground) - .getDefaultColor(); - } - Bitmap colorized = mColorizer.colorize(drawable, backgroundColor, - mContext.getResources().getConfiguration().getLayoutDirection() == - LayoutDirection.RTL); - builder.setLargeIcon(Icon.createWithBitmap(colorized)); - } - } - - /** - * Select a foreground color depending on whether the background color is dark or light - * @param backgroundColor Background color to coordinate with - * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder} - * @return foreground color - */ - public static int selectForegroundColor(int backgroundColor, Palette palette) { - if (ContrastColorUtil.isColorLight(backgroundColor)) { - return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(), - palette.getVibrantSwatch(), - palette.getDarkMutedSwatch(), - palette.getMutedSwatch(), - palette.getDominantSwatch(), - Color.BLACK); - } else { - return selectForegroundColorForSwatches(palette.getLightVibrantSwatch(), - palette.getVibrantSwatch(), - palette.getLightMutedSwatch(), - palette.getMutedSwatch(), - palette.getDominantSwatch(), - Color.WHITE); - } - } - - private static int selectForegroundColorForSwatches(Palette.Swatch moreVibrant, - Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch, - Palette.Swatch dominantSwatch, int fallbackColor) { - Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant); - if (coloredCandidate == null) { - coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch); - } - if (coloredCandidate != null) { - if (dominantSwatch == coloredCandidate) { - return coloredCandidate.getRgb(); - } else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation() - < POPULATION_FRACTION_FOR_DOMINANT - && dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) { - return dominantSwatch.getRgb(); - } else { - return coloredCandidate.getRgb(); - } - } else if (hasEnoughPopulation(dominantSwatch)) { - return dominantSwatch.getRgb(); - } else { - return fallbackColor; - } - } - - private static Palette.Swatch selectMutedCandidate(Palette.Swatch first, - Palette.Swatch second) { - boolean firstValid = hasEnoughPopulation(first); - boolean secondValid = hasEnoughPopulation(second); - if (firstValid && secondValid) { - float firstSaturation = first.getHsl()[1]; - float secondSaturation = second.getHsl()[1]; - float populationFraction = first.getPopulation() / (float) second.getPopulation(); - if (firstSaturation * populationFraction > secondSaturation) { - return first; - } else { - return second; - } - } else if (firstValid) { - return first; - } else if (secondValid) { - return second; - } - return null; - } - - private static Palette.Swatch selectVibrantCandidate(Palette.Swatch first, - Palette.Swatch second) { - boolean firstValid = hasEnoughPopulation(first); - boolean secondValid = hasEnoughPopulation(second); - if (firstValid && secondValid) { - int firstPopulation = first.getPopulation(); - int secondPopulation = second.getPopulation(); - if (firstPopulation / (float) secondPopulation - < POPULATION_FRACTION_FOR_MORE_VIBRANT) { - return second; - } else { - return first; - } - } else if (firstValid) { - return first; - } else if (secondValid) { - return second; - } - return null; - } - private static boolean hasEnoughPopulation(Palette.Swatch swatch) { - // We want a fraction that is at least 1% of the image - return swatch != null - && (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION); + private MediaNotificationProcessor() { } /** @@ -279,7 +72,7 @@ public class MediaNotificationProcessor { List<Palette.Swatch> swatches = palette.getSwatches(); float highestNonWhitePopulation = -1; Palette.Swatch second = null; - for (Palette.Swatch swatch: swatches) { + for (Palette.Swatch swatch : swatches) { if (swatch != dominantSwatch && swatch.getPopulation() > highestNonWhitePopulation && !isWhiteOrBlack(swatch.getHsl())) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 6cf5c303149c..815cfb39ea2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -668,7 +668,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView && expandedView.findViewById(com.android.internal.R.id.media_actions) != null; boolean isMessagingLayout = contractedView instanceof MessagingLayout; boolean isCallLayout = contractedView instanceof CallLayout; - boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar(); if (customView && beforeS && !mIsSummaryWithChildren) { if (beforeN) { @@ -678,12 +677,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } else { smallHeight = mMaxSmallHeightBeforeS; } - } else if (isMediaLayout) { - // TODO(b/172652345): MediaStyle notifications currently look broken when we enforce - // the standard notification height, so we have to afford them more vertical space to - // make sure we don't crop them terribly. We actually need to revisit this and give - // them a headerless design, then remove this hack. - smallHeight = showCompactMediaSeekbar ? mMaxSmallHeightMedia : mMaxSmallHeightBeforeS; } else if (isMessagingLayout) { // TODO(b/173204301): MessagingStyle notifications currently look broken when we enforce // the standard notification height, so we have to afford them more vertical space to diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 58b87cd2f492..73c4b054fd4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -40,13 +40,11 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ImageMessageConsumer; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.media.MediaDataManagerKt; import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.ConversationNotificationProcessor; import com.android.systemui.statusbar.notification.InflationException; -import com.android.systemui.statusbar.notification.MediaNotificationProcessor; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.phone.StatusBar; @@ -799,13 +797,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder // For all of our templates, we want it to be RTL packageContext = new RtlEnabledContext(packageContext); } - Notification notification = sbn.getNotification(); - if (notification.isMediaNotification() && !(mIsMediaInQS - && MediaDataManagerKt.isMediaNotification(sbn))) { - MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, - packageContext); - processor.processNotification(notification, recoveredBuilder); - } if (mEntry.getRanking().isConversation()) { mConversationProcessor.processNotification(mEntry, recoveredBuilder); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java index 2535e5ddc3d1..c75cd782c3e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java @@ -16,341 +16,26 @@ package com.android.systemui.statusbar.notification.row.wrapper; -import static com.android.systemui.Dependency.MAIN_HANDLER; - -import android.annotation.Nullable; -import android.app.Notification; import android.content.Context; -import android.content.res.ColorStateList; -import android.media.MediaMetadata; -import android.media.session.MediaController; -import android.media.session.MediaSession; -import android.media.session.PlaybackState; -import android.metrics.LogMaker; -import android.os.Handler; -import android.text.format.DateUtils; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewStub; -import android.widget.SeekBar; -import android.widget.TextView; -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.widget.MediaNotificationView; -import com.android.systemui.Dependency; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.TransformableView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import java.util.Timer; -import java.util.TimerTask; - /** * Wraps a notification containing a media template */ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateViewWrapper { - private static final long PROGRESS_UPDATE_INTERVAL = 1000; // 1s - private static final String COMPACT_MEDIA_TAG = "media"; - private final Handler mHandler = Dependency.get(MAIN_HANDLER); - private Timer mSeekBarTimer; private View mActions; - private SeekBar mSeekBar; - private TextView mSeekBarElapsedTime; - private TextView mSeekBarTotalTime; - private long mDuration = 0; - private MediaController mMediaController; - private MediaMetadata mMediaMetadata; - private NotificationMediaManager mMediaManager; - private View mSeekBarView; - private Context mContext; - private MetricsLogger mMetricsLogger; - private boolean mIsViewVisible; - - @VisibleForTesting - protected SeekBar.OnSeekBarChangeListener mSeekListener = - new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - if (mMediaController != null) { - mMediaController.getTransportControls().seekTo(mSeekBar.getProgress()); - mMetricsLogger.write(newLog(MetricsEvent.TYPE_UPDATE)); - } - } - }; - - private MediaNotificationView.VisibilityChangeListener mVisibilityListener = - new MediaNotificationView.VisibilityChangeListener() { - @Override - public void onAggregatedVisibilityChanged(boolean isVisible) { - mIsViewVisible = isVisible; - if (isVisible && mMediaController != null) { - // Restart timer if we're currently playing and didn't already have one going - PlaybackState state = mMediaController.getPlaybackState(); - if (state != null && state.getState() == PlaybackState.STATE_PLAYING - && mSeekBarTimer == null && mSeekBarView != null - && mSeekBarView.getVisibility() != View.GONE) { - startTimer(); - } - } else { - clearTimer(); - } - } - }; - - private View.OnAttachStateChangeListener mAttachStateListener = - new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - } - - @Override - public void onViewDetachedFromWindow(View v) { - mIsViewVisible = false; - } - }; - - private MediaController.Callback mMediaCallback = new MediaController.Callback() { - @Override - public void onSessionDestroyed() { - clearTimer(); - mMediaController.unregisterCallback(this); - if (mView instanceof MediaNotificationView) { - ((MediaNotificationView) mView).removeVisibilityListener(mVisibilityListener); - mView.removeOnAttachStateChangeListener(mAttachStateListener); - } - } - - @Override - public void onPlaybackStateChanged(@Nullable PlaybackState state) { - if (state == null) { - return; - } - - if (state.getState() != PlaybackState.STATE_PLAYING) { - // Update the UI once, in case playback info changed while we were paused - updatePlaybackUi(state); - clearTimer(); - } else if (mSeekBarTimer == null && mSeekBarView != null - && mSeekBarView.getVisibility() != View.GONE) { - startTimer(); - } - } - - @Override - public void onMetadataChanged(@Nullable MediaMetadata metadata) { - if (mMediaMetadata == null || !mMediaMetadata.equals(metadata)) { - mMediaMetadata = metadata; - updateDuration(); - } - } - }; protected NotificationMediaTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { super(ctx, view, row); - mContext = ctx; - mMediaManager = Dependency.get(NotificationMediaManager.class); - mMetricsLogger = Dependency.get(MetricsLogger.class); } private void resolveViews() { mActions = mView.findViewById(com.android.internal.R.id.media_actions); - mIsViewVisible = mView.isShown(); - - final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras - .getParcelable(Notification.EXTRA_MEDIA_SESSION); - - boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar(); - if (token == null || (COMPACT_MEDIA_TAG.equals(mView.getTag()) && !showCompactSeekbar)) { - if (mSeekBarView != null) { - mSeekBarView.setVisibility(View.GONE); - } - return; - } - - // Check for existing media controller and clean up / create as necessary - boolean shouldUpdateListeners = false; - if (mMediaController == null || !mMediaController.getSessionToken().equals(token)) { - if (mMediaController != null) { - mMediaController.unregisterCallback(mMediaCallback); - } - mMediaController = new MediaController(mContext, token); - shouldUpdateListeners = true; - } - - mMediaMetadata = mMediaController.getMetadata(); - if (mMediaMetadata != null) { - long duration = mMediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); - if (duration <= 0) { - // Don't include the seekbar if this is a livestream - if (mSeekBarView != null && mSeekBarView.getVisibility() != View.GONE) { - mSeekBarView.setVisibility(View.GONE); - mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE)); - clearTimer(); - } else if (mSeekBarView == null && shouldUpdateListeners) { - // Only log if the controller changed, otherwise we would log multiple times for - // the same notification when user pauses/resumes - mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE)); - } - return; - } else if (mSeekBarView != null && mSeekBarView.getVisibility() == View.GONE) { - // Otherwise, make sure the seekbar is visible - mSeekBarView.setVisibility(View.VISIBLE); - mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN)); - updateDuration(); - startTimer(); - } - } - - // Inflate the seekbar template - ViewStub stub = mView.findViewById(R.id.notification_media_seekbar_container); - if (stub instanceof ViewStub) { - LayoutInflater layoutInflater = LayoutInflater.from(stub.getContext()); - stub.setLayoutInflater(layoutInflater); - stub.setLayoutResource(R.layout.notification_material_media_seekbar); - mSeekBarView = stub.inflate(); - mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN)); - - mSeekBar = mSeekBarView.findViewById(R.id.notification_media_progress_bar); - mSeekBar.setOnSeekBarChangeListener(mSeekListener); - - mSeekBarElapsedTime = mSeekBarView.findViewById(R.id.notification_media_elapsed_time); - mSeekBarTotalTime = mSeekBarView.findViewById(R.id.notification_media_total_time); - - shouldUpdateListeners = true; - } - - if (shouldUpdateListeners) { - if (mView instanceof MediaNotificationView) { - MediaNotificationView mediaView = (MediaNotificationView) mView; - mediaView.addVisibilityListener(mVisibilityListener); - mView.addOnAttachStateChangeListener(mAttachStateListener); - } - - if (mSeekBarTimer == null) { - if (mMediaController != null && canSeekMedia(mMediaController.getPlaybackState())) { - // Log initial state, since it will not be updated - mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, 1)); - } else { - setScrubberVisible(false); - } - updateDuration(); - startTimer(); - mMediaController.registerCallback(mMediaCallback); - } - } - updateSeekBarTint(mSeekBarView); - } - - private void startTimer() { - clearTimer(); - if (mIsViewVisible) { - mSeekBarTimer = new Timer(true /* isDaemon */); - mSeekBarTimer.schedule(new TimerTask() { - @Override - public void run() { - mHandler.post(mOnUpdateTimerTick); - } - }, 0, PROGRESS_UPDATE_INTERVAL); - } - } - - private void clearTimer() { - if (mSeekBarTimer != null) { - mSeekBarTimer.cancel(); - mSeekBarTimer.purge(); - mSeekBarTimer = null; - } - } - - @Override - public void setRemoved() { - clearTimer(); - if (mMediaController != null) { - mMediaController.unregisterCallback(mMediaCallback); - } - if (mView instanceof MediaNotificationView) { - ((MediaNotificationView) mView).removeVisibilityListener(mVisibilityListener); - mView.removeOnAttachStateChangeListener(mAttachStateListener); - } - } - - private boolean canSeekMedia(@Nullable PlaybackState state) { - if (state == null) { - return false; - } - - long actions = state.getActions(); - return ((actions & PlaybackState.ACTION_SEEK_TO) != 0); - } - - private void setScrubberVisible(boolean isVisible) { - if (mSeekBar == null || mSeekBar.isEnabled() == isVisible) { - return; - } - - mSeekBar.getThumb().setAlpha(isVisible ? 255 : 0); - mSeekBar.setEnabled(isVisible); - mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, isVisible ? 1 : 0)); - } - - private void updateDuration() { - if (mMediaMetadata != null && mSeekBar != null) { - long duration = mMediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); - if (mDuration != duration) { - mDuration = duration; - mSeekBar.setMax((int) mDuration); - mSeekBarTotalTime.setText(millisecondsToTimeString(duration)); - } - } - } - - protected final Runnable mOnUpdateTimerTick = new Runnable() { - @Override - public void run() { - if (mMediaController != null && mSeekBar != null) { - PlaybackState playbackState = mMediaController.getPlaybackState(); - if (playbackState != null) { - updatePlaybackUi(playbackState); - } else { - clearTimer(); - } - } else { - clearTimer(); - } - } - }; - - private void updatePlaybackUi(PlaybackState state) { - if (mSeekBar == null || mSeekBarElapsedTime == null) { - return; - } - - long position = state.getPosition(); - mSeekBar.setProgress((int) position); - - mSeekBarElapsedTime.setText(millisecondsToTimeString(position)); - - // Update scrubber in case available actions have changed - setScrubberVisible(canSeekMedia(state)); - } - - private String millisecondsToTimeString(long milliseconds) { - long seconds = milliseconds / 1000; - String text = DateUtils.formatElapsedTime(seconds); - return text; } @Override @@ -361,28 +46,6 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi super.onContentUpdated(row); } - private void updateSeekBarTint(View seekBarContainer) { - if (seekBarContainer == null) { - return; - } - - if (this.getNotificationHeader() == null) { - return; - } - - int tintColor = getOriginalIconColor(); - mSeekBarElapsedTime.setTextColor(tintColor); - mSeekBarTotalTime.setTextColor(tintColor); - mSeekBarTotalTime.setShadowLayer(1.5f, 1.5f, 1.5f, mBackgroundColor); - - ColorStateList tintList = ColorStateList.valueOf(tintColor); - mSeekBar.setThumbTintList(tintList); - tintList = tintList.withAlpha(192); // 75% - mSeekBar.setProgressTintList(tintList); - tintList = tintList.withAlpha(128); // 50% - mSeekBar.setProgressBackgroundTintList(tintList); - } - @Override protected void updateTransformedTypes() { // This also clears the existing types @@ -394,36 +57,7 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi } @Override - public boolean isDimmable() { - return getCustomBackgroundColor() == 0; - } - - @Override public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) { return true; } - - /** - * Returns an initialized LogMaker for logging changes to the seekbar - * @return new LogMaker - */ - private LogMaker newLog(int event) { - String packageName = mRow.getEntry().getSbn().getPackageName(); - - return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR) - .setType(event) - .setPackageName(packageName); - } - - /** - * Returns an initialized LogMaker for logging changes with subtypes - * @return new LogMaker - */ - private LogMaker newLog(int event, int subtype) { - String packageName = mRow.getEntry().getSbn().getPackageName(); - return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR) - .setType(event) - .setSubtype(subtype) - .setPackageName(packageName); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java index e6287e7063d3..aeb5b037be0c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java @@ -18,31 +18,18 @@ package com.android.systemui.statusbar.notification; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertNotSame; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - import android.annotation.Nullable; -import android.app.Notification; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.drawable.Drawable; import android.test.suitebuilder.annotation.SmallTest; -import android.widget.RemoteViews; import androidx.palette.graphics.Palette; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; -import com.android.systemui.tests.R; import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,17 +45,8 @@ public class MediaNotificationProcessorTest extends SysuiTestCase { */ private static final int COLOR_TOLERANCE = 8; - private MediaNotificationProcessor mProcessor; - private Bitmap mBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); - private ImageGradientColorizer mColorizer; @Nullable private Bitmap mArtwork; - @Before - public void setUp() { - mColorizer = spy(new TestableColorizer(mBitmap)); - mProcessor = new MediaNotificationProcessor(getContext(), getContext(), mColorizer); - } - @After public void tearDown() { if (mArtwork != null) { @@ -78,53 +56,6 @@ public class MediaNotificationProcessorTest extends SysuiTestCase { } @Test - public void testColorizedWithLargeIcon() { - Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon( - R.drawable.ic_person) - .setContentTitle("Title") - .setLargeIcon(mBitmap) - .setContentText("Text"); - Notification notification = builder.build(); - mProcessor.processNotification(notification, builder); - verify(mColorizer).colorize(any(), anyInt(), anyBoolean()); - } - - @Test - public void testNotColorizedWithoutLargeIcon() { - Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon( - R.drawable.ic_person) - .setContentTitle("Title") - .setContentText("Text"); - Notification notification = builder.build(); - mProcessor.processNotification(notification, builder); - verifyZeroInteractions(mColorizer); - } - - @Test - public void testRemoteViewsReset() { - Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon( - R.drawable.ic_person) - .setContentTitle("Title") - .setStyle(new Notification.MediaStyle()) - .setLargeIcon(mBitmap) - .setContentText("Text"); - Notification notification = builder.build(); - RemoteViews remoteViews = new RemoteViews(getContext().getPackageName(), - R.layout.custom_view_dark); - notification.contentView = remoteViews; - notification.bigContentView = remoteViews; - notification.headsUpContentView = remoteViews; - mProcessor.processNotification(notification, builder); - verify(mColorizer).colorize(any(), anyInt(), anyBoolean()); - RemoteViews contentView = builder.createContentView(); - assertNotSame(contentView, remoteViews); - contentView = builder.createBigContentView(); - assertNotSame(contentView, remoteViews); - contentView = builder.createHeadsUpContentView(); - assertNotSame(contentView, remoteViews); - } - - @Test public void findBackgroundSwatch_white() { // Given artwork that is completely white. mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); @@ -153,17 +84,4 @@ public class MediaNotificationProcessorTest extends SysuiTestCase { assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual)); assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual)); } - - public static class TestableColorizer extends ImageGradientColorizer { - private final Bitmap mBitmap; - - private TestableColorizer(Bitmap bitmap) { - mBitmap = bitmap; - } - - @Override - public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) { - return mBitmap; - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java deleted file mode 100644 index fbe4d7315baa..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.row.wrapper; - -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.app.Notification; -import android.media.MediaMetadata; -import android.media.session.MediaSession; -import android.media.session.PlaybackState; -import android.provider.Settings; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; -import android.view.View; -import android.widget.RemoteViews; -import android.widget.SeekBar; - -import androidx.test.filters.SmallTest; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.NotificationTestHelper; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -public class NotificationMediaTemplateViewWrapperTest extends SysuiTestCase { - - private ExpandableNotificationRow mRow; - private Notification mNotif; - private View mView; - private NotificationMediaTemplateViewWrapper mWrapper; - - @Mock - private MetricsLogger mMetricsLogger; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - allowTestableLooperAsMainThread(); - - mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); - - // These tests are for regular media style notifications, not controls in quick settings - Settings.System.putInt(mContext.getContentResolver(), "qs_media_player", 0); - } - - private void makeTestNotification(long duration, boolean allowSeeking) throws Exception { - Notification.Builder builder = new Notification.Builder(mContext) - .setSmallIcon(R.drawable.ic_person) - .setContentTitle("Title") - .setContentText("Text"); - - MediaMetadata metadata = new MediaMetadata.Builder() - .putLong(MediaMetadata.METADATA_KEY_DURATION, duration) - .build(); - MediaSession session = new MediaSession(mContext, "TEST_CHANNEL"); - session.setMetadata(metadata); - - PlaybackState playbackState = new PlaybackState.Builder() - .setActions(allowSeeking ? PlaybackState.ACTION_SEEK_TO : 0) - .build(); - - session.setPlaybackState(playbackState); - - builder.setStyle(new Notification.MediaStyle() - .setMediaSession(session.getSessionToken()) - ); - - mNotif = builder.build(); - assertTrue(mNotif.hasMediaSession()); - - NotificationTestHelper helper = new NotificationTestHelper( - mContext, - mDependency, - TestableLooper.get(this)); - mRow = helper.createRow(mNotif); - - RemoteViews views = new RemoteViews(mContext.getPackageName(), - com.android.internal.R.layout.notification_template_material_big_media); - mView = views.apply(mContext, null); - mWrapper = new NotificationMediaTemplateViewWrapper(mContext, - mView, mRow); - mWrapper.onContentUpdated(mRow); - } - - @Test - public void testLogging_NoSeekbar() throws Exception { - // Media sessions with duration <= 0 should not include a seekbar - makeTestNotification(0, false); - - verify(mMetricsLogger).write(argThat(logMaker -> - logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR - && logMaker.getType() == MetricsEvent.TYPE_CLOSE - )); - - verify(mMetricsLogger, times(0)).write(argThat(logMaker -> - logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR - && logMaker.getType() == MetricsEvent.TYPE_OPEN - )); - } - - @Test - public void testLogging_HasSeekbarNoScrubber() throws Exception { - // Media sessions that do not support seeking should have a seekbar, but no scrubber - makeTestNotification(1000, false); - - verify(mMetricsLogger).write(argThat(logMaker -> - logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR - && logMaker.getType() == MetricsEvent.TYPE_OPEN - )); - - // Ensure the callback runs at least once - mWrapper.mOnUpdateTimerTick.run(); - - verify(mMetricsLogger).write(argThat(logMaker -> - logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR - && logMaker.getType() == MetricsEvent.TYPE_DETAIL - && logMaker.getSubtype() == 0 - )); - } - - @Test - public void testLogging_HasSeekbarAndScrubber() throws Exception { - makeTestNotification(1000, true); - - verify(mMetricsLogger).write(argThat(logMaker -> - logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR - && logMaker.getType() == MetricsEvent.TYPE_OPEN - )); - - verify(mMetricsLogger).write(argThat(logMaker -> - logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR - && logMaker.getType() == MetricsEvent.TYPE_DETAIL - && logMaker.getSubtype() == 1 - )); - } - - @Test - public void testLogging_UpdateSeekbar() throws Exception { - makeTestNotification(1000, true); - - SeekBar seekbar = mView.findViewById( - com.android.internal.R.id.notification_media_progress_bar); - assertTrue(seekbar != null); - - mWrapper.mSeekListener.onStopTrackingTouch(seekbar); - - verify(mMetricsLogger).write(argThat(logMaker -> - logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR - && logMaker.getType() == MetricsEvent.TYPE_UPDATE)); - } -} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java index b9ffd65ee20e..a37d5c8a956a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java @@ -31,9 +31,7 @@ import android.app.NotificationManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.media.session.MediaSession; import android.os.Build; -import android.os.Process; import android.os.UserHandle; import android.provider.Settings; import android.service.notification.StatusBarNotification; @@ -73,7 +71,6 @@ public class NotificationComparatorTest extends UiServiceTestCase { private NotificationRecord mRecordMinCallNonInterruptive; private NotificationRecord mRecordMinCall; private NotificationRecord mRecordHighCall; - private NotificationRecord mRecordDefaultMedia; private NotificationRecord mRecordEmail; private NotificationRecord mRecordInlineReply; private NotificationRecord mRecordSms; @@ -139,15 +136,6 @@ public class NotificationComparatorTest extends UiServiceTestCase { new UserHandle(userId), "", 1999), getDefaultChannel()); mRecordHighCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); - Notification n3 = new Notification.Builder(mContext, TEST_CHANNEL_ID) - .setStyle(new Notification.MediaStyle() - .setMediaSession(new MediaSession.Token(Process.myUid(), null))) - .build(); - mRecordDefaultMedia = new NotificationRecord(mContext, new StatusBarNotification(pkg2, - pkg2, 1, "media", uid2, uid2, n3, new UserHandle(userId), - "", 1499), getDefaultChannel()); - mRecordDefaultMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT); - Notification n4 = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setStyle(new Notification.MessagingStyle("sender!")).build(); mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2, @@ -218,7 +206,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { .setStyle(new Notification.MediaStyle()) .build(); mNoMediaSessionMedia = new NotificationRecord(mContext, new StatusBarNotification( - pkg2, pkg2, 1, "cheater", uid2, uid2, n12, new UserHandle(userId), + pkg2, pkg2, 1, "media", uid2, uid2, n12, new UserHandle(userId), "", 9258), getDefaultChannel()); mNoMediaSessionMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT); @@ -247,7 +235,6 @@ public class NotificationComparatorTest extends UiServiceTestCase { final List<NotificationRecord> expected = new ArrayList<>(); expected.add(mRecordColorizedCall); expected.add(mRecordColorized); - expected.add(mRecordDefaultMedia); expected.add(mRecordHighCall); expected.add(mRecordInlineReply); if (mRecordSms != null) { |