Merge "New MediaStyle template:" into sc-dev

This commit is contained in:
Jeff DeCew
2021-03-23 02:14:06 +00:00
committed by Android (Google) Code Review
22 changed files with 484 additions and 1740 deletions

View File

@@ -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;
}
@@ -6681,21 +6618,6 @@ public class Notification implements Parcelable
&& targetSdkVersion < Build.VERSION_CODES.O;
}
/**
* 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.
@@ -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());
}
@@ -6871,27 +6780,6 @@ public class Notification implements Parcelable
return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
}
/**
* @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
*
@@ -7178,15 +7066,6 @@ public class Notification implements Parcelable
return mBuilder.build();
}
/**
* @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
@@ -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() {
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
.hideProgress(true)
.fillTextsFrom(mBuilder);
RemoteViews view = mBuilder.applyStandardTemplate(
R.layout.notification_template_material_media, p,
null /* result */);
/** @hide */
protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
final int numActions = mBuilder.mActions.size();
final int numActionsToShow = mActionsToShowInCompact == null
? 0
: Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
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);
TemplateBindResult result = new TemplateBindResult();
RemoteViews template = mBuilder.applyStandardTemplate(
R.layout.notification_template_material_media, p,
null /* result */);
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;

View File

@@ -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();
}
}
}

View File

@@ -129,12 +129,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
*/

View File

@@ -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

View File

@@ -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"

View File

@@ -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>

View File

@@ -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"
>
<!--
NOTE: The notification_top_line_views layout contains the app_name_text.
In order to include the title view at the beginning, the Notification.Builder
has logic to hide that view whenever this title view is to be visible.
-->
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
/>
<include layout="@layout/notification_top_line_views" />
</NotificationTopLineView>
<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:layout_marginEnd="@dimen/notification_heading_margin_end"
android:layout_marginStart="@dimen/notification_content_margin_start"
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"
>
<com.android.internal.widget.NotificationVanishingFrameLayout
android:layout_width="match_parent"
<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"
>
<!-- This is the simplest way to keep this text vertically centered without using
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_progress"
<!--
NOTE: The notification_top_line_views layout contains the app_name_text.
In order to include the title view at the beginning, the Notification.Builder
has logic to hide that view whenever this title view is to be visible.
-->
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
/>
<include layout="@layout/notification_top_line_views" />
</NotificationTopLineView>
<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"
>
<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_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>

View File

@@ -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>

View File

@@ -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"
>
<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"
>
<!--
NOTE: The notification_top_line_views layout contains the app_name_text.
In order to include the title view at the beginning, the Notification.Builder
has logic to hide that view whenever this title view is to be visible.
-->
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
/>
<include layout="@layout/notification_top_line_views" />
</NotificationTopLineView>
<LinearLayout
android:id="@+id/notification_content_container"
android:id="@+id/notification_main_column"
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"
>
<!-- 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"
<com.android.internal.widget.NotificationVanishingFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:textAlignment="viewStart"
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_progress"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_headerless_line_height"
/>
<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"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
</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/action2"
/>
</LinearLayout>
<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="top|end"
android:layout_marginStart="10dp"
android:layoutDirection="ltr"
android:orientation="horizontal"
>
<include
layout="@layout/notification_material_media_action"
android:id="@+id/action0"
android:layout_gravity="center_vertical|end"
/>
<include
layout="@layout/notification_material_media_action"
android:id="@+id/action1"
/>
<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>
</LinearLayout>
</com.android.internal.widget.MediaNotificationView>

View File

@@ -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 -->

View File

@@ -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>

View File

@@ -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"/>

View File

@@ -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;
@@ -98,23 +96,6 @@ public class NotificationTest {
assertFalse(n.isColorized());
}
@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();

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -16,50 +16,21 @@
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.
*/
@@ -67,186 +38,8 @@ public class MediaNotificationProcessor {
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())) {

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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
@@ -393,37 +56,8 @@ 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);
}
}

View File

@@ -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) {
@@ -77,53 +55,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.
@@ -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;
}
}
}

View File

@@ -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));
}
}

View File

@@ -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) {