return false;
}
+
+ /**
+ * @return true if this is a media notification
+ *
+ * @hide
+ */
+ public boolean isMediaNotification() {
+ Class<? extends Style> style = getNotificationStyle();
+ if (MediaStyle.class.equals(style)) {
+ return true;
+ } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
+ return true;
+ }
+ return false;
+ }
+
private boolean hasLargeIcon() {
return mLargeIcon != null || largeIcon != null;
}
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.RelativeLayout;
import android.widget.RemoteViews;
/**
@RemoteViews.RemoteView
public class MediaNotificationView extends FrameLayout {
- private final int mMaxImageSize;
- private final int mImageMinTopMargin;
+ private final int mSmallImageSize;
private final int mNotificationContentMarginEnd;
private final int mNotificationContentImageMarginEnd;
private ImageView mRightIcon;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int mode = MeasureSpec.getMode(widthMeasureSpec);
boolean hasIcon = mRightIcon.getVisibility() != GONE;
+ if (!hasIcon) {
+ resetHeaderIndention();
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int mode = MeasureSpec.getMode(widthMeasureSpec);
+ boolean reMeasure = false;
if (hasIcon && mode != MeasureSpec.UNSPECIFIED) {
- measureChild(mActions, widthMeasureSpec, heightMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
size = size - mActions.getMeasuredWidth();
ViewGroup.MarginLayoutParams layoutParams =
(MarginLayoutParams) mRightIcon.getLayoutParams();
int imageEndMargin = layoutParams.getMarginEnd();
size -= imageEndMargin;
- size = Math.min(size, mMaxImageSize);
- size = Math.max(size, mRightIcon.getMinimumWidth());
- layoutParams.width = size;
- layoutParams.height = size;
- mRightIcon.setLayoutParams(layoutParams);
+ int fullHeight = getMeasuredHeight();
+ if (size < fullHeight) {
+ size = mSmallImageSize;
+ } else {
+ size = fullHeight;
+ }
+ if (layoutParams.width != size || layoutParams.height != size) {
+ layoutParams.width = size;
+ layoutParams.height = size;
+ mRightIcon.setLayoutParams(layoutParams);
+ reMeasure = true;
+ }
// lets ensure that the main column doesn't run into the image
- ViewGroup.MarginLayoutParams mainParams
+ ViewGroup.MarginLayoutParams params
= (MarginLayoutParams) mMainColumn.getLayoutParams();
int marginEnd = size + imageEndMargin + mNotificationContentMarginEnd;
- if (marginEnd != mainParams.getMarginEnd()) {
- mainParams.setMarginEnd(marginEnd);
- mMainColumn.setLayoutParams(mainParams);
+ if (marginEnd != params.getMarginEnd()) {
+ params.setMarginEnd(marginEnd);
+ mMainColumn.setLayoutParams(params);
+ reMeasure = true;
}
-
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- ViewGroup.MarginLayoutParams iconParams =
- (MarginLayoutParams) mRightIcon.getLayoutParams();
- int topMargin = getMeasuredHeight() - mRightIcon.getMeasuredHeight()
- - iconParams.bottomMargin;
- // If the topMargin is high enough we can also remove the header constraint!
- boolean reMeasure = false;
- if (!hasIcon || topMargin >= mImageMinTopMargin) {
- reMeasure = resetHeaderIndention();
- } else {
- int paddingEnd = mNotificationContentImageMarginEnd;
- ViewGroup.MarginLayoutParams headerParams =
- (MarginLayoutParams) mHeader.getLayoutParams();
- int newMarginEnd = mRightIcon.getMeasuredWidth() + iconParams.getMarginEnd();
- if (headerParams.getMarginEnd() != newMarginEnd) {
- headerParams.setMarginEnd(newMarginEnd);
- mHeader.setLayoutParams(headerParams);
+ int headerMarginEnd = size + imageEndMargin;
+ params = (MarginLayoutParams) mHeader.getLayoutParams();
+ if (params.getMarginEnd() != headerMarginEnd) {
+ params.setMarginEnd(headerMarginEnd);
+ mHeader.setLayoutParams(params);
reMeasure = true;
}
- if (mHeader.getPaddingEnd() != paddingEnd) {
+ if (mHeader.getPaddingEnd() != mNotificationContentImageMarginEnd) {
mHeader.setPaddingRelative(mHeader.getPaddingStart(),
mHeader.getPaddingTop(),
- paddingEnd,
+ mNotificationContentImageMarginEnd,
mHeader.getPaddingBottom());
reMeasure = true;
}
}
if (reMeasure) {
- measureChildWithMargins(mHeader, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
- private boolean resetHeaderIndention() {
- boolean remeasure = false;
+ private void resetHeaderIndention() {
if (mHeader.getPaddingEnd() != mNotificationContentMarginEnd) {
mHeader.setPaddingRelative(mHeader.getPaddingStart(),
mHeader.getPaddingTop(),
mNotificationContentMarginEnd,
mHeader.getPaddingBottom());
- remeasure = true;
}
ViewGroup.MarginLayoutParams headerParams =
(MarginLayoutParams) mHeader.getLayoutParams();
if (headerParams.getMarginEnd() != 0) {
headerParams.setMarginEnd(0);
mHeader.setLayoutParams(headerParams);
- remeasure = true;
}
- return remeasure;
}
public MediaNotificationView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mMaxImageSize = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.media_notification_expanded_image_max_size);
- mImageMinTopMargin = (int) (context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_top)
- + getResources().getDisplayMetrics().density * 2);
+ mSmallImageSize = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.media_notification_expanded_image_small_size);
mNotificationContentMarginEnd = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_end);
mNotificationContentImageMarginEnd = context.getResources().getDimensionPixelSize(
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"
android:layout_width="match_parent"
android:layout_height="@dimen/media_notification_header_height"
<!-- media buttons will be added here -->
</LinearLayout>
</LinearLayout>
-
- <ImageView android:id="@+id/right_icon"
- android:layout_width="@dimen/media_notification_expanded_image_max_size"
- android:layout_height="@dimen/media_notification_expanded_image_max_size"
- android:minWidth="40dp"
- android:layout_marginEnd="16dp"
- android:layout_marginBottom="20dp"
- android:layout_gravity="bottom|end"
- android:scaleType="centerCrop"
- />
</com.android.internal.widget.MediaNotificationView>
android:background="#00000000"
android:tag="media"
>
+ <ImageView android:id="@+id/right_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:adjustViewBounds="true"
+ android:layout_gravity="top|end"
+ android:scaleType="centerCrop"
+ />
<include layout="@layout/notification_template_header"
android:layout_width="match_parent"
android:layout_height="@dimen/media_notification_header_height" />
<!-- media buttons will be added here -->
</LinearLayout>
</LinearLayout>
- <include layout="@layout/notification_template_right_icon" />
</FrameLayout>
<!-- The minimum height of the content if there are at least two lines or a picture-->
<dimen name="notification_min_content_height">41dp</dimen>
- <!-- The maximum size of the image in the expanded media notification -->
- <dimen name="media_notification_expanded_image_max_size">94dp</dimen>
+ <!-- The small size of the image if the height drawing doesn't work anymore -->
+ <dimen name="media_notification_expanded_image_small_size">72dp</dimen>
<!-- The maximum size of the image in the expanded media notification -->
<dimen name="media_notification_expanded_image_margin_bottom">20dp</dimen>
<java-symbol type="string" name="new_sms_notification_title" />
<java-symbol type="string" name="new_sms_notification_content" />
- <java-symbol type="dimen" name="media_notification_expanded_image_max_size" />
+ <java-symbol type="dimen" name="media_notification_expanded_image_small_size" />
<java-symbol type="dimen" name="media_notification_expanded_image_margin_bottom" />
<java-symbol type="dimen" name="media_notification_header_height" />
<java-symbol type="dimen" name="notification_content_image_margin_end" />
--- /dev/null
+/*
+ * 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.BitmapShader;
+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.Xfermode;
+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) {
+ 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,
+ });
+
+ drawable.setColorFilter(new ColorMatrixColorFilter(m));
+ drawable.draw(canvas);
+ 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);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
+ fadeInCanvas.drawPaint(paint);
+ canvas.drawBitmap(fadeIn, 0, 0, null);
+
+ linearGradient = new LinearGradient(0, 0, size, 0,
+ new int[] {backgroundColor, Color.argb(0.5f, tr, tg, tb), 0},
+ new float[] {0.0f, 0.6f, 1.0f}, Shader.TileMode.CLAMP);
+ paint.setShader(linearGradient);
+ paint.setXfermode(null);
+ canvas.drawPaint(paint);
+ return newBitmap;
+ }
+}
import android.support.v4.graphics.ColorUtils;
import android.support.v7.graphics.Palette;
+import com.android.systemui.R;
+
import java.util.List;
/**
private static final float BLACK_MAX_LIGHTNESS = 0.08f;
private static final float WHITE_MIN_LIGHTNESS = 0.92f;
private static final int RESIZE_BITMAP_AREA = 150 * 150;
+ private final ImageGradientColorizer mColorizer;
+ private final Context mContext;
private float[] mFilteredBackgroundHsl = null;
private 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 mContext;
+ private final Context mPackageContext;
+ private boolean mIsLowPriority;
- public MediaNotificationProcessor(Context context) {
+ public MediaNotificationProcessor(Context context, Context packageContext) {
mContext = context;
+ mPackageContext = packageContext;
+ mColorizer = new ImageGradientColorizer();
}
/**
public void processNotification(Notification notification, Notification.Builder builder) {
Icon largeIcon = notification.getLargeIcon();
Bitmap bitmap = null;
+ Drawable drawable = null;
if (largeIcon != null) {
- Drawable drawable = largeIcon.loadDrawable(mContext);
- 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);
- }
- if (bitmap != null) {
- // for the background we only take the left side of the image to ensure
- // a smooth transition
- Palette.Builder paletteBuilder = Palette.from(bitmap)
- .setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight())
- .clearFilters() // we want all colors, red / white / black ones too!
- .resizeBitmapArea(RESIZE_BITMAP_AREA);
- Palette palette = paletteBuilder.generate();
- int backgroundColor = findBackgroundColorAndFilter(palette);
- // we want the full region again
- paletteBuilder.setRegion(0, 0, bitmap.getWidth(), bitmap.getHeight());
- if (mFilteredBackgroundHsl != null) {
- paletteBuilder.addFilter((rgb, hsl) -> {
- // at least 10 degrees hue difference
- float diff = Math.abs(hsl[0] - mFilteredBackgroundHsl[0]);
- return diff > 10 && diff < 350;
- });
- }
- paletteBuilder.addFilter(mBlackWhiteFilter);
- palette = paletteBuilder.generate();
- int foregroundColor;
- if (ColorUtils.calculateLuminance(backgroundColor) > 0.5) {
- Palette.Swatch first = palette.getDarkVibrantSwatch();
- Palette.Swatch second = palette.getVibrantSwatch();
- if (first != null && second != null) {
- int firstPopulation = first.getPopulation();
- int secondPopulation = second.getPopulation();
- if (firstPopulation / secondPopulation
- < POPULATION_FRACTION_FOR_MORE_VIBRANT) {
- foregroundColor = second.getRgb();
- } else {
- foregroundColor = first.getRgb();
- }
- } else if (first != null) {
- foregroundColor = first.getRgb();
- } else if (second != null) {
- foregroundColor = second.getRgb();
- } else {
- first = palette.getMutedSwatch();
- second = palette.getDarkMutedSwatch();
+ 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);
+
+ // for the background we only take the left side of the image to ensure
+ // a smooth transition
+ Palette.Builder paletteBuilder = Palette.from(bitmap)
+ .setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight())
+ .clearFilters() // we want all colors, red / white / black ones too!
+ .resizeBitmapArea(RESIZE_BITMAP_AREA);
+ Palette palette = paletteBuilder.generate();
+ backgroundColor = findBackgroundColorAndFilter(palette);
+ // we want the full region again
+ paletteBuilder.setRegion(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ if (mFilteredBackgroundHsl != null) {
+ paletteBuilder.addFilter((rgb, hsl) -> {
+ // at least 10 degrees hue difference
+ float diff = Math.abs(hsl[0] - mFilteredBackgroundHsl[0]);
+ return diff > 10 && diff < 350;
+ });
+ }
+ paletteBuilder.addFilter(mBlackWhiteFilter);
+ palette = paletteBuilder.generate();
+ int foregroundColor;
+ if (ColorUtils.calculateLuminance(backgroundColor) > 0.5) {
+ Palette.Swatch first = palette.getDarkVibrantSwatch();
+ Palette.Swatch second = palette.getVibrantSwatch();
if (first != null && second != null) {
- float firstSaturation = first.getHsl()[1];
- float secondSaturation = second.getHsl()[1];
- if (firstSaturation > secondSaturation) {
- foregroundColor = first.getRgb();
- } else {
+ int firstPopulation = first.getPopulation();
+ int secondPopulation = second.getPopulation();
+ if (firstPopulation / secondPopulation
+ < POPULATION_FRACTION_FOR_MORE_VIBRANT) {
foregroundColor = second.getRgb();
+ } else {
+ foregroundColor = first.getRgb();
}
} else if (first != null) {
foregroundColor = first.getRgb();
} else if (second != null) {
foregroundColor = second.getRgb();
} else {
- foregroundColor = Color.BLACK;
- }
- }
- } else {
- Palette.Swatch first = palette.getLightVibrantSwatch();
- Palette.Swatch second = palette.getVibrantSwatch();
- if (first != null && second != null) {
- int firstPopulation = first.getPopulation();
- int secondPopulation = second.getPopulation();
- if (firstPopulation / secondPopulation
- < POPULATION_FRACTION_FOR_MORE_VIBRANT) {
- foregroundColor = second.getRgb();
- } else {
- foregroundColor = first.getRgb();
+ first = palette.getMutedSwatch();
+ second = palette.getDarkMutedSwatch();
+ if (first != null && second != null) {
+ float firstSaturation = first.getHsl()[1];
+ float secondSaturation = second.getHsl()[1];
+ if (firstSaturation > secondSaturation) {
+ foregroundColor = first.getRgb();
+ } else {
+ foregroundColor = second.getRgb();
+ }
+ } else if (first != null) {
+ foregroundColor = first.getRgb();
+ } else if (second != null) {
+ foregroundColor = second.getRgb();
+ } else {
+ foregroundColor = Color.BLACK;
+ }
}
- } else if (first != null) {
- foregroundColor = first.getRgb();
- } else if (second != null) {
- foregroundColor = second.getRgb();
} else {
- first = palette.getMutedSwatch();
- second = palette.getLightMutedSwatch();
+ Palette.Swatch first = palette.getLightVibrantSwatch();
+ Palette.Swatch second = palette.getVibrantSwatch();
if (first != null && second != null) {
- float firstSaturation = first.getHsl()[1];
- float secondSaturation = second.getHsl()[1];
- if (firstSaturation > secondSaturation) {
- foregroundColor = first.getRgb();
- } else {
+ int firstPopulation = first.getPopulation();
+ int secondPopulation = second.getPopulation();
+ if (firstPopulation / secondPopulation
+ < POPULATION_FRACTION_FOR_MORE_VIBRANT) {
foregroundColor = second.getRgb();
+ } else {
+ foregroundColor = first.getRgb();
}
} else if (first != null) {
foregroundColor = first.getRgb();
} else if (second != null) {
foregroundColor = second.getRgb();
} else {
- foregroundColor = Color.WHITE;
+ first = palette.getMutedSwatch();
+ second = palette.getLightMutedSwatch();
+ if (first != null && second != null) {
+ float firstSaturation = first.getHsl()[1];
+ float secondSaturation = second.getHsl()[1];
+ if (firstSaturation > secondSaturation) {
+ foregroundColor = first.getRgb();
+ } else {
+ foregroundColor = second.getRgb();
+ }
+ } else if (first != null) {
+ foregroundColor = first.getRgb();
+ } else if (second != null) {
+ foregroundColor = second.getRgb();
+ } else {
+ foregroundColor = Color.WHITE;
+ }
}
}
+ builder.setColorPalette(backgroundColor, foregroundColor);
+ } else {
+ int id = mIsLowPriority
+ ? R.color.notification_material_background_low_priority_color
+ : R.color.notification_material_background_color;
+ backgroundColor = mContext.getColor(id);
}
- builder.setColorPalette(backgroundColor, foregroundColor);
+ Bitmap colorized = mColorizer.colorize(drawable, backgroundColor);
+ builder.setLargeIcon(Icon.createWithBitmap(colorized));
}
}
private boolean isWhite(float[] hslColor) {
return hslColor[2] >= WHITE_MIN_LIGHTNESS;
}
+
+ public void setIsLowPriority(boolean isLowPriority) {
+ mIsLowPriority = isLowPriority;
+ }
}
mSbn.getNotification());
mPackageContext = mSbn.getPackageContext(mContext);
Notification notification = mSbn.getNotification();
- if (notification.isColorizedMedia()) {
- MediaNotificationProcessor processor = new MediaNotificationProcessor(
+ if (notification.isMediaNotification()) {
+ MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
mPackageContext);
+ processor.setIsLowPriority(mIsLowPriority);
processor.processNotification(notification, recoveredBuilder);
}
return recoveredBuilder;