diff --git a/api/current.txt b/api/current.txt index f8de1a4a11370..8adf4b26ac36c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5050,6 +5050,7 @@ package android.app { field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText"; field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; + field public static final java.lang.String EXTRA_COLORIZED = "android.colorized"; field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions"; field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; @@ -5215,6 +5216,7 @@ package android.app { method public android.app.Notification.Builder setChannel(java.lang.String); method public android.app.Notification.Builder setChronometerCountDown(boolean); method public android.app.Notification.Builder setColor(int); + method public android.app.Notification.Builder setColorized(boolean); method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews); method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence); method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent); diff --git a/api/system-current.txt b/api/system-current.txt index aa6f095501598..b319c752f0cde 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5211,6 +5211,7 @@ package android.app { field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText"; field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; + field public static final java.lang.String EXTRA_COLORIZED = "android.colorized"; field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions"; field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; @@ -5378,6 +5379,7 @@ package android.app { method public android.app.Notification.Builder setChannel(java.lang.String); method public android.app.Notification.Builder setChronometerCountDown(boolean); method public android.app.Notification.Builder setColor(int); + method public android.app.Notification.Builder setColorized(boolean); method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews); method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence); method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent); diff --git a/api/test-current.txt b/api/test-current.txt index 0505bdebaeed9..24673e1747bf9 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5060,6 +5060,7 @@ package android.app { field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText"; field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; + field public static final java.lang.String EXTRA_COLORIZED = "android.colorized"; field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions"; field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; @@ -5225,6 +5226,7 @@ package android.app { method public android.app.Notification.Builder setChannel(java.lang.String); method public android.app.Notification.Builder setChronometerCountDown(boolean); method public android.app.Notification.Builder setColor(int); + method public android.app.Notification.Builder setColorized(boolean); method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews); method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence); method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index b4fe6b84b2cc5..34d2039988e68 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -71,7 +71,6 @@ import android.widget.RemoteViews; import com.android.internal.R; import com.android.internal.util.ArrayUtils; import com.android.internal.util.NotificationColorUtil; -import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -981,6 +980,12 @@ public class Notification implements Parcelable */ public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; + /** + * {@link #extras} key: whether the notification should be colorized as + * supplied to {@link Builder#setColorized(boolean)}}. + */ + public static final String EXTRA_COLORIZED = "android.colorized"; + /** * {@link #extras} key: the user that built the notification. * @@ -2438,6 +2443,8 @@ public class Notification implements Parcelable private ArrayList mActions = new ArrayList(MAX_ACTION_BUTTONS); private ArrayList mPersonList = new ArrayList(); private NotificationColorUtil mColorUtil; + private boolean mIsLegacy; + private boolean mIsLegacyInitialized; private boolean mColorUtilInited = false; /** @@ -2456,6 +2463,10 @@ public class Notification implements Parcelable * so make sure to call {@link StandardTemplateParams#reset()} before using it. */ StandardTemplateParams mParams = new StandardTemplateParams(); + private int mTextColorsAreForBackground = COLOR_INVALID; + private int mPrimaryTextColor = COLOR_INVALID; + private int mSecondaryTextColor = COLOR_INVALID; + private int mActionBarColor = COLOR_INVALID; /** * Constructs a new Builder with the defaults: @@ -2540,7 +2551,7 @@ public class Notification implements Parcelable private NotificationColorUtil getColorUtil() { if (!mColorUtilInited) { mColorUtilInited = true; - if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) { + if (isLegacy() || isColorized()) { mColorUtil = NotificationColorUtil.getInstance(mContext); } } @@ -3024,6 +3035,22 @@ public class Notification implements Parcelable return this; } + /** + * Set whether this notification should be colorized. When set, the color set with + * {@link #setColor(int)} will be used as the background color of this notification. + *

+ * The coloring will only be applied if the notification is ongoing. + * This should only be used for high priority ongoing tasks like navigation, an ongoing + * call, or other similarly high-priority events for the user. + * + * @see Builder#setOngoing(boolean) + * @see Builder#setColor(int) + */ + public Builder setColorized(boolean colorize) { + mN.extras.putBoolean(EXTRA_COLORIZED, colorize); + return this; + } + /** * Set this flag if you would only like the sound, vibrate * and ticker to be played if the notification is not already showing. @@ -3389,6 +3416,10 @@ public class Notification implements Parcelable if (profileBadge != null) { contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE); + if (isColorized()) { + contentView.setDrawableParameters(R.id.profile_badge, false, -1, + getPrimaryTextColor(), PorterDuff.Mode.SRC_ATOP, -1); + } } } @@ -3446,13 +3477,14 @@ public class Notification implements Parcelable resetStandardTemplate(contentView); final Bundle ex = mN.extras; - + updateBackgroundColor(contentView); bindNotificationHeader(contentView, p.ambient); bindLargeIcon(contentView); boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex); if (p.title != null) { contentView.setViewVisibility(R.id.title, View.VISIBLE); contentView.setTextViewText(R.id.title, p.title); + setTextViewColorPrimary(contentView, R.id.title); contentView.setViewLayoutWidth(R.id.title, showProgress ? ViewGroup.LayoutParams.WRAP_CONTENT : ViewGroup.LayoutParams.MATCH_PARENT); @@ -3461,6 +3493,7 @@ public class Notification implements Parcelable int textId = showProgress ? com.android.internal.R.id.text_line_1 : com.android.internal.R.id.text; contentView.setTextViewText(textId, p.text); + setTextViewColorSecondary(contentView, textId); contentView.setViewVisibility(textId, View.VISIBLE); } @@ -3469,6 +3502,52 @@ public class Notification implements Parcelable return contentView; } + private void setTextViewColorPrimary(RemoteViews contentView, int id) { + ensureColors(); + contentView.setTextColor(id, mPrimaryTextColor); + } + + private int getPrimaryTextColor() { + ensureColors(); + return mPrimaryTextColor; + } + + private int getActionBarColor() { + ensureColors(); + return mActionBarColor; + } + + private void setTextViewColorSecondary(RemoteViews contentView, int id) { + ensureColors(); + contentView.setTextColor(id, mSecondaryTextColor); + } + + private void ensureColors() { + int backgroundColor = getBackgroundColor(); + if (mPrimaryTextColor == COLOR_INVALID + || mSecondaryTextColor == COLOR_INVALID + || mActionBarColor == COLOR_INVALID + || mTextColorsAreForBackground != backgroundColor) { + mTextColorsAreForBackground = backgroundColor; + mPrimaryTextColor = NotificationColorUtil.resolvePrimaryColor( + mContext, backgroundColor); + mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor( + mContext, backgroundColor); + mActionBarColor = NotificationColorUtil.resolveActionBarColor(backgroundColor); + } + } + + private void updateBackgroundColor(RemoteViews contentView) { + if (isColorized()) { + contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", + getBackgroundColor()); + } else { + // Clear it! + contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource", + 0); + } + } + /** * @param remoteView the remote view to update the minheight in * @param hasMinHeight does it have a mimHeight @@ -3535,15 +3614,17 @@ public class Notification implements Parcelable } private void bindExpandButton(RemoteViews contentView) { - contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveContrastColor(), + int color = isColorized() ? getPrimaryTextColor() : resolveContrastColor(); + contentView.setDrawableParameters(R.id.expand_button, false, -1, color, PorterDuff.Mode.SRC_ATOP, -1); contentView.setInt(R.id.notification_header, "setOriginalNotificationColor", - resolveContrastColor()); + color); } private void bindHeaderChronometerAndTime(RemoteViews contentView) { if (showsTimeOrChronometer()) { contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); + setTextViewColorSecondary(contentView, R.id.time_divider); if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) { contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); contentView.setLong(R.id.chronometer, "setBase", @@ -3551,9 +3632,11 @@ public class Notification implements Parcelable contentView.setBoolean(R.id.chronometer, "setStarted", true); boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN); contentView.setChronometerCountDown(R.id.chronometer, countsDown); + setTextViewColorSecondary(contentView, R.id.chronometer); } else { contentView.setViewVisibility(R.id.time, View.VISIBLE); contentView.setLong(R.id.time, "setTime", mN.when); + setTextViewColorSecondary(contentView, R.id.time); } } else { // We still want a time to be set but gone, such that we can show and hide it @@ -3576,8 +3659,10 @@ public class Notification implements Parcelable if (headerText != null) { // TODO: Remove the span entirely to only have the string with propper formating. contentView.setTextViewText(R.id.header_text, processLegacyText(headerText)); + setTextViewColorSecondary(contentView, R.id.header_text); contentView.setViewVisibility(R.id.header_text, View.VISIBLE); contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); + setTextViewColorSecondary(contentView, R.id.header_text_divider); } } @@ -3616,8 +3701,12 @@ public class Notification implements Parcelable } private void bindHeaderAppName(RemoteViews contentView, boolean ambient) { contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); - contentView.setTextColor(R.id.app_name_text, - ambient ? resolveAmbientColor() : resolveContrastColor()); + if (isColorized()) { + setTextViewColorPrimary(contentView, R.id.app_name_text); + } else { + contentView.setTextColor(R.id.app_name_text, + ambient ? resolveAmbientColor() : resolveContrastColor()); + } } private void bindSmallIcon(RemoteViews contentView, boolean ambient) { @@ -3675,6 +3764,11 @@ public class Notification implements Parcelable big.setViewVisibility(R.id.actions, View.VISIBLE); if (p.ambient) { big.setInt(R.id.actions, "setBackgroundColor", Color.TRANSPARENT); + } else if (isColorized()) { + big.setInt(R.id.actions, "setBackgroundColor", getActionBarColor()); + } else { + big.setInt(R.id.actions, "setBackgroundColor", mContext.getColor( + R.color.notification_action_list)); } big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, R.dimen.notification_action_list_height); @@ -3696,15 +3790,18 @@ public class Notification implements Parcelable && replyText.length > 0 && !TextUtils.isEmpty(replyText[0])) { big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_1, replyText[0]); + setTextViewColorSecondary(big, R.id.notification_material_reply_text_1); if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1])) { big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_2, replyText[1]); + setTextViewColorSecondary(big, R.id.notification_material_reply_text_2); if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2])) { big.setViewVisibility( R.id.notification_material_reply_text_3, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_3, replyText[2]); + setTextViewColorSecondary(big, R.id.notification_material_reply_text_3); } } } @@ -3930,6 +4027,7 @@ public class Notification implements Parcelable if (action.mRemoteInputs != null) { button.setRemoteInputs(R.id.action0, action.mRemoteInputs); } + // TODO: handle emphasized mode / actions right if (emphazisedMode) { // change the background bgColor int bgColor = mContext.getColor(oddAction ? R.color.notification_action_list @@ -3945,16 +4043,19 @@ public class Notification implements Parcelable title = ensureColorSpanContrast(title, bgColor, outResultColor); } button.setTextViewText(R.id.action0, title); + setTextViewColorPrimary(button, R.id.action0); if (outResultColor != null && outResultColor[0] != null) { // We need to set the text color as well since changing a text to uppercase // clears its spans. button.setTextColor(R.id.action0, outResultColor[0]); - } else if (mN.color != COLOR_DEFAULT) { + } else if (mN.color != COLOR_DEFAULT && !isColorized()) { button.setTextColor(R.id.action0,resolveContrastColor()); } } else { button.setTextViewText(R.id.action0, processLegacyText(action.title)); - if (mN.color != COLOR_DEFAULT) { + if (isColorized()) { + setTextViewColorPrimary(button, R.id.action0); + } else if (mN.color != COLOR_DEFAULT) { button.setTextColor(R.id.action0, ambient ? resolveAmbientColor() : resolveContrastColor()); } @@ -4073,11 +4174,16 @@ public class Notification implements Parcelable * doesn't create material notifications by itself) app. */ private boolean isLegacy() { - return getColorUtil() != null; + if (!mIsLegacyInitialized) { + mIsLegacy = mContext.getApplicationInfo().targetSdkVersion + < Build.VERSION_CODES.LOLLIPOP; + mIsLegacyInitialized = true; + } + return mIsLegacy; } private CharSequence processLegacyText(CharSequence charSequence) { - if (isLegacy()) { + if (isLegacy() || isColorized()) { return getColorUtil().invertCharSequenceColors(charSequence); } else { return charSequence; @@ -4339,6 +4445,37 @@ public class Notification implements Parcelable private int getActionTombstoneLayoutResource() { return R.layout.notification_material_action_tombstone; } + + private int getBackgroundColor() { + if (isColorized()) { + return mN.color; + } else { + return COLOR_DEFAULT; + } + } + + private boolean isColorized() { + return mN.isColorized(); + } + } + + /** + * @return whether this notification is ongoing and can't be dismissed by the user. + */ + private boolean isOngoing() { + final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE + | Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR; + return (flags & ongoingFlags) != 0; + } + + /** + * @return true if this notification is colorized. This also factors in wheather the + * notification is ongoing. + * + * @hide + */ + public boolean isColorized() { + return extras.getBoolean(EXTRA_COLORIZED) && isOngoing(); } private boolean hasLargeIcon() { @@ -4672,6 +4809,7 @@ public class Notification implements Parcelable RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource()); if (mSummaryTextSet) { contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText)); + mBuilder.setTextViewColorSecondary(contentView, R.id.text); contentView.setViewVisibility(R.id.text, View.VISIBLE); } mBuilder.setContentMinHeight(contentView, mBuilder.mN.hasLargeIcon()); @@ -4827,6 +4965,7 @@ public class Notification implements Parcelable static void applyBigTextContentView(Builder builder, RemoteViews contentView, CharSequence bigTextText) { contentView.setTextViewText(R.id.big_text, bigTextText); + builder.setTextViewColorSecondary(contentView, R.id.big_text); contentView.setViewVisibility(R.id.big_text, TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE); contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines(builder)); @@ -5070,7 +5209,7 @@ public class Notification implements Parcelable : (m == null) ? null : m.mSender; CharSequence text = (m == null) ? null - : mConversationTitle != null ? makeMessageLine(m) : m.mText; + : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText; return mBuilder.applyStandardTemplateWithActions(mBuilder.getBaseLayoutResource(), mBuilder.mParams.reset().hasProgress(false).title(title).text(text)); @@ -5108,7 +5247,7 @@ public class Notification implements Parcelable CharSequence text; if (hasTitle) { bigTitle = title; - text = makeMessageLine(mMessages.get(0)); + text = makeMessageLine(mMessages.get(0), mBuilder); } else { bigTitle = mMessages.get(0).mSender; text = mMessages.get(0).mText; @@ -5146,7 +5285,7 @@ public class Notification implements Parcelable Message m = mHistoricMessages.get(firstHistoricMessage + i); int rowId = rowIds[i]; - contentView.setTextViewText(rowId, makeMessageLine(m)); + contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder)); if (contractedMessage == m) { contractedChildId = rowId; @@ -5161,7 +5300,8 @@ public class Notification implements Parcelable int rowId = rowIds[i]; contentView.setViewVisibility(rowId, View.VISIBLE); - contentView.setTextViewText(rowId, makeMessageLine(m)); + contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder)); + mBuilder.setTextViewColorSecondary(contentView, rowId); if (contractedMessage == m) { contractedChildId = rowId; @@ -5183,17 +5323,22 @@ public class Notification implements Parcelable return contentView; } - private CharSequence makeMessageLine(Message m) { + private CharSequence makeMessageLine(Message m, Builder builder) { BidiFormatter bidi = BidiFormatter.getInstance(); SpannableStringBuilder sb = new SpannableStringBuilder(); + boolean colorize = builder.isColorized(); if (TextUtils.isEmpty(m.mSender)) { CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName; sb.append(bidi.unicodeWrap(replyName), - makeFontColorSpan(mBuilder.resolveContrastColor()), + makeFontColorSpan(colorize + ? builder.getPrimaryTextColor() + : mBuilder.resolveContrastColor()), 0 /* flags */); } else { sb.append(bidi.unicodeWrap(m.mSender), - makeFontColorSpan(Color.BLACK), + makeFontColorSpan(colorize + ? builder.getPrimaryTextColor() + : Color.BLACK), 0 /* flags */); } CharSequence text = m.mText == null ? "" : m.mText; @@ -5212,7 +5357,7 @@ public class Notification implements Parcelable : (m == null) ? null : m.mSender; CharSequence text = (m == null) ? null - : mConversationTitle != null ? makeMessageLine(m) : m.mText; + : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText; return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(), mBuilder.mParams.reset().hasProgress(false).title(title).text(text)); @@ -5503,6 +5648,7 @@ public class Notification implements Parcelable if (!TextUtils.isEmpty(str)) { contentView.setViewVisibility(rowIds[i], View.VISIBLE); contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str)); + mBuilder.setTextViewColorSecondary(contentView, rowIds[i]); contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0); handleInboxImageMargin(contentView, rowIds[i], first); if (first) { diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java index 087383df405d5..0fe580a239831 100644 --- a/core/java/com/android/internal/util/NotificationColorUtil.java +++ b/core/java/com/android/internal/util/NotificationColorUtil.java @@ -33,6 +33,8 @@ import android.graphics.drawable.Icon; import android.graphics.drawable.VectorDrawable; import android.text.SpannableStringBuilder; import android.text.Spanned; +import android.text.style.CharacterStyle; +import android.text.style.ForegroundColorSpan; import android.text.style.TextAppearanceSpan; import android.util.Log; import android.util.Pair; @@ -184,8 +186,24 @@ public class NotificationColorUtil { SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); for (Object span : spans) { Object resultSpan = span; - if (span instanceof TextAppearanceSpan) { - resultSpan = processTextAppearanceSpan((TextAppearanceSpan) span); + if (resultSpan instanceof CharacterStyle) { + resultSpan = ((CharacterStyle) span).getUnderlying(); + } + if (resultSpan instanceof TextAppearanceSpan) { + TextAppearanceSpan processedSpan = processTextAppearanceSpan( + (TextAppearanceSpan) span); + if (processedSpan != resultSpan) { + resultSpan = processedSpan; + } else { + // we need to still take the orgininal for wrapped spans + resultSpan = span; + } + } else if (resultSpan instanceof ForegroundColorSpan) { + ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan; + int foregroundColor = originalSpan.getForegroundColor(); + resultSpan = new ForegroundColorSpan(processColor(foregroundColor)); + } else { + resultSpan = span; } builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), ss.getSpanFlags(span)); @@ -416,6 +434,48 @@ public class NotificationColorUtil { return color; } + public static int resolvePrimaryColor(Context context, int backgroundColor) { + boolean useDark = shouldUseDark(backgroundColor); + if (useDark) { + return context.getColor( + com.android.internal.R.color.notification_primary_text_color_light); + } else { + return context.getColor( + com.android.internal.R.color.notification_primary_text_color_dark); + } + } + + public static int resolveSecondaryColor(Context context, int backgroundColor) { + boolean useDark = shouldUseDark(backgroundColor); + if (useDark) { + return context.getColor( + com.android.internal.R.color.notification_secondary_text_color_light); + } else { + return context.getColor( + com.android.internal.R.color.notification_secondary_text_color_dark); + } + } + + public static int resolveActionBarColor(int backgroundColor) { + boolean useDark = shouldUseDark(backgroundColor); + final double[] result = ColorUtilsFromCompat.getTempDouble3Array(); + ColorUtilsFromCompat.colorToLAB(backgroundColor, result); + if (useDark && result[0] < 97 || !useDark && result[0] < 4) { + result[0] = Math.min(100, result[0] + 7); + } else { + result[0] = Math.max(0, result[0] - 7); + } + return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]); + } + + private static boolean shouldUseDark(int backgroundColor) { + boolean useDark = backgroundColor == Notification.COLOR_DEFAULT; + if (!useDark) { + useDark = ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5; + } + return useDark; + } + /** * Framework copy of functions needed from android.support.v4.graphics.ColorUtils. */ diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 4164e5df76d58..b28c6f2d22e3b 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -89,7 +89,7 @@ #33b5e5 #cc222222 #fff4511e - + #7fa87f @android:color/black @@ -132,6 +132,10 @@ #0cffffff #29000000 #29ffffff + @color/primary_text_default_material_light + @color/primary_text_default_material_dark + @color/secondary_text_material_light + @color/secondary_text_material_dark #ffffffff diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 0b326e94382de..1e153485123fe 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -451,14 +451,14 @@ please see styles_device_defaults.xml. @@ -467,7 +467,7 @@ please see styles_device_defaults.xml. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a3eb180d7cb92..a7329981f6dae 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2796,6 +2796,11 @@ + + + + + diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 4a19dde445366..fc0c82cd54d2b 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -58,7 +58,6 @@ - diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java index cd6c31f370d32..1c89e32a02bc7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java @@ -23,6 +23,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Interpolator; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -298,5 +299,14 @@ public class ViewTransformationHelper implements TransformableView { TransformState otherState) { return false; } + + /** + * Get a custom interpolator for this animation + * @param interpolationType the type of the interpolation, i.e TranslationX / TranslationY + * @param isFrom true if this transformation from the other view + */ + public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) { + return null; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java new file mode 100644 index 0000000000000..de4c3128e99f3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java @@ -0,0 +1,79 @@ +/* + * 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.view.View; +import android.view.animation.Interpolator; + +import com.android.systemui.Interpolators; +import com.android.systemui.statusbar.CrossFadeHelper; +import com.android.systemui.statusbar.TransformableView; +import com.android.systemui.statusbar.ViewTransformationHelper; + +import static com.android.systemui.statusbar.TransformableView.TRANSFORMING_VIEW_TITLE; +import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y; + +/** + * A custom transformation that modifies the interpolator + */ +public abstract class CustomInterpolatorTransformation + extends ViewTransformationHelper.CustomTransformation { + + private final int mViewType; + + public CustomInterpolatorTransformation(int viewType) { + mViewType = viewType; + } + + @Override + public boolean transformTo(TransformState ownState, TransformableView notification, + float transformationAmount) { + if (!hasCustomTransformation()) { + return false; + } + TransformState otherState = notification.getCurrentState(mViewType); + if (otherState == null) { + return false; + } + View view = ownState.getTransformedView(); + CrossFadeHelper.fadeOut(view, transformationAmount); + ownState.transformViewFullyTo(otherState, this, transformationAmount); + otherState.recycle(); + return true; + } + + protected boolean hasCustomTransformation() { + return true; + } + + @Override + public boolean transformFrom(TransformState ownState, + TransformableView notification, float transformationAmount) { + if (!hasCustomTransformation()) { + return false; + } + TransformState otherState = notification.getCurrentState(mViewType); + if (otherState == null) { + return false; + } + View view = ownState.getTransformedView(); + CrossFadeHelper.fadeIn(view, transformationAmount); + ownState.transformViewFullyFrom(otherState, this, transformationAmount); + otherState.recycle(); + return true; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java index d56474141e5bd..3b18886372e20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java @@ -40,9 +40,6 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { private final ViewInvertHelper mInvertHelper; private final Paint mGreyPaint = new Paint(); - private int mBackgroundColor = 0; - private static final int CUSTOM_BACKGROUND_TAG = R.id.custom_background_color; - private boolean mShouldInvertDark; private boolean mShowingLegacyBackground; protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) { @@ -105,32 +102,6 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { mView.setAlpha(visible ? 1.0f : 0.0f); } - @Override - public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) { - super.notifyContentUpdated(notification, isLowPriority); - Drawable background = mView.getBackground(); - mBackgroundColor = 0; - if (background instanceof ColorDrawable) { - mBackgroundColor = ((ColorDrawable) background).getColor(); - mView.setBackground(null); - mView.setTag(CUSTOM_BACKGROUND_TAG, mBackgroundColor); - } else if (mView.getTag(CUSTOM_BACKGROUND_TAG) != null) { - mBackgroundColor = (int) mView.getTag(CUSTOM_BACKGROUND_TAG); - } - mShouldInvertDark = mBackgroundColor == 0 || isColorLight(mBackgroundColor); - } - - private boolean isColorLight(int backgroundColor) { - return Color.alpha(backgroundColor) == 0 - || ColorUtils.calculateLuminance(backgroundColor) > 0.5; - } - - @Override - public int getCustomBackgroundColor() { - // Parent notifications should always use the normal background color - return mRow.isSummaryWithChildren() ? 0 : mBackgroundColor; - } - @Override public void setShowingLegacyBackground(boolean showing) { super.setShowingLegacyBackground(showing); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java index e1f553c2f7dd0..8eab2e914d86a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java @@ -31,9 +31,12 @@ import android.util.ArraySet; import android.view.NotificationHeaderView; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; import android.widget.ImageView; import android.widget.TextView; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.ViewInvertHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; @@ -43,17 +46,21 @@ import com.android.systemui.statusbar.phone.NotificationPanelView; import java.util.Stack; +import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y; + /** * Wraps a notification header view. */ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { + private static final Interpolator LOW_PRIORITY_HEADER_CLOSE + = new PathInterpolator(0.4f, 0f, 0.7f, 1f); private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter( 0, PorterDuff.Mode.SRC_ATOP); private final int mIconDarkAlpha; private final int mIconDarkColor = 0xffffffff; - protected final ViewInvertHelper mInvertHelper; + protected final ViewInvertHelper mInvertHelper; protected final ViewTransformationHelper mTransformationHelper; protected int mColor; @@ -70,6 +77,32 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha); mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION); mTransformationHelper = new ViewTransformationHelper(); + + // we want to avoid that the header clashes with the other text when transforming + // low-priority + mTransformationHelper.setCustomTransformation( + new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) { + + @Override + public Interpolator getCustomInterpolator(int interpolationType, + boolean isFrom) { + boolean isLowPriority = mView instanceof NotificationHeaderView; + if (interpolationType == TRANSFORM_Y) { + if (isLowPriority && !isFrom + || !isLowPriority && isFrom) { + return Interpolators.LINEAR_OUT_SLOW_IN; + } else { + return LOW_PRIORITY_HEADER_CLOSE; + } + } + return null; + } + + @Override + protected boolean hasCustomTransformation() { + return mIsLowPriority; + } + }, TRANSFORMING_VIEW_TITLE); resolveHeaderViews(); updateInvertHelper(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java index 747798868ede4..836482a875a50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java @@ -19,12 +19,17 @@ package com.android.systemui.statusbar.notification; import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Color; import android.graphics.ColorMatrix; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.service.notification.StatusBarNotification; +import android.support.v4.graphics.ColorUtils; import android.view.NotificationHeaderView; import android.view.View; import com.android.systemui.Interpolators; +import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.TransformableView; @@ -40,6 +45,8 @@ public abstract class NotificationViewWrapper implements TransformableView { protected final View mView; protected final ExpandableNotificationRow mRow; protected boolean mDark; + private int mBackgroundColor = 0; + protected boolean mShouldInvertDark; protected boolean mDarkInitialized = false; public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) { @@ -85,7 +92,19 @@ public abstract class NotificationViewWrapper implements TransformableView { */ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) { mDarkInitialized = false; - }; + Drawable background = mView.getBackground(); + mBackgroundColor = 0; + if (background instanceof ColorDrawable) { + mBackgroundColor = ((ColorDrawable) background).getColor(); + mView.setBackground(null); + } + mShouldInvertDark = mBackgroundColor == 0 || isColorLight(mBackgroundColor); + } + + private boolean isColorLight(int backgroundColor) { + return Color.alpha(backgroundColor) == 0 + || ColorUtils.calculateLuminance(backgroundColor) > 0.5; + } protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener, @@ -156,7 +175,8 @@ public abstract class NotificationViewWrapper implements TransformableView { } public int getCustomBackgroundColor() { - return 0; + // Parent notifications should always use the normal background color + return mRow.isSummaryWithChildren() ? 0 : mBackgroundColor; } public void setShowingLegacyBackground(boolean showing) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java index 6b3d25d95c147..d15ab10e09ac5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java @@ -18,10 +18,10 @@ package com.android.systemui.statusbar.notification; import android.util.ArraySet; import android.util.Pools; -import android.view.NotificationHeaderView; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; +import android.view.animation.Interpolator; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; @@ -38,10 +38,11 @@ import com.android.systemui.statusbar.ViewTransformationHelper; */ public class TransformState { + public static final int TRANSFORM_X = 0x1; + public static final int TRANSFORM_Y = 0x10; + public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y; + private static final float UNDEFINED = -1f; - private static final int TRANSOFORM_X = 0x1; - private static final int TRANSOFORM_Y = 0x10; - private static final int TRANSOFORM_ALL = TRANSOFORM_X | TRANSOFORM_Y; private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag; private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag; private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag; @@ -80,25 +81,31 @@ public class TransformState { } public void transformViewFullyFrom(TransformState otherState, float transformationAmount) { - transformViewFrom(otherState, TRANSOFORM_ALL, null, transformationAmount); + transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount); + } + + public void transformViewFullyFrom(TransformState otherState, + ViewTransformationHelper.CustomTransformation customTransformation, + float transformationAmount) { + transformViewFrom(otherState, TRANSFORM_ALL, customTransformation, transformationAmount); } public void transformViewVerticalFrom(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount) { - transformViewFrom(otherState, TRANSOFORM_Y, customTransformation, transformationAmount); + transformViewFrom(otherState, TRANSFORM_Y, customTransformation, transformationAmount); } public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) { - transformViewFrom(otherState, TRANSOFORM_Y, null, transformationAmount); + transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount); } private void transformViewFrom(TransformState otherState, int transformationFlags, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount) { final View transformedView = mTransformedView; - boolean transformX = (transformationFlags & TRANSOFORM_X) != 0; - boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0; + boolean transformX = (transformationFlags & TRANSFORM_X) != 0; + boolean transformY = (transformationFlags & TRANSFORM_Y) != 0; boolean transformScale = transformScale(); // lets animate the positions correctly if (transformationAmount == 0.0f @@ -153,14 +160,30 @@ public class TransformState { float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( transformationAmount); if (transformX) { + float interpolation = interpolatedValue; + if (customTransformation != null) { + Interpolator customInterpolator = + customTransformation.getCustomInterpolator(TRANSFORM_X, true /* isFrom */); + if (customInterpolator != null) { + interpolation = customInterpolator.getInterpolation(transformationAmount); + } + } transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(), 0.0f, - interpolatedValue)); + interpolation)); } if (transformY) { + float interpolation = interpolatedValue; + if (customTransformation != null) { + Interpolator customInterpolator = + customTransformation.getCustomInterpolator(TRANSFORM_Y, true /* isFrom */); + if (customInterpolator != null) { + interpolation = customInterpolator.getInterpolation(transformationAmount); + } + } transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(), 0.0f, - interpolatedValue)); + interpolation)); } if (transformScale) { float transformationStartScaleX = getTransformationStartScaleX(); @@ -207,17 +230,23 @@ public class TransformState { } public void transformViewFullyTo(TransformState otherState, float transformationAmount) { - transformViewTo(otherState, TRANSOFORM_ALL, null, transformationAmount); + transformViewTo(otherState, TRANSFORM_ALL, null, transformationAmount); + } + + public void transformViewFullyTo(TransformState otherState, + ViewTransformationHelper.CustomTransformation customTransformation, + float transformationAmount) { + transformViewTo(otherState, TRANSFORM_ALL, customTransformation, transformationAmount); } public void transformViewVerticalTo(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount) { - transformViewTo(otherState, TRANSOFORM_Y, customTransformation, transformationAmount); + transformViewTo(otherState, TRANSFORM_Y, customTransformation, transformationAmount); } public void transformViewVerticalTo(TransformState otherState, float transformationAmount) { - transformViewTo(otherState, TRANSOFORM_Y, null, transformationAmount); + transformViewTo(otherState, TRANSFORM_Y, null, transformationAmount); } private void transformViewTo(TransformState otherState, int transformationFlags, @@ -226,8 +255,8 @@ public class TransformState { // lets animate the positions correctly final View transformedView = mTransformedView; - boolean transformX = (transformationFlags & TRANSOFORM_X) != 0; - boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0; + boolean transformX = (transformationFlags & TRANSFORM_X) != 0; + boolean transformY = (transformationFlags & TRANSFORM_Y) != 0; boolean transformScale = transformScale(); // lets animate the positions correctly if (transformationAmount == 0.0f) { @@ -264,23 +293,37 @@ public class TransformState { int[] ownPosition = getLaidOutLocationOnScreen(); if (transformX) { float endX = otherStablePosition[0] - ownPosition[0]; - if (customTransformation != null - && customTransformation.customTransformTarget(this, otherState)) { - endX = mTransformationEndX; + float interpolation = interpolatedValue; + if (customTransformation != null) { + if (customTransformation.customTransformTarget(this, otherState)) { + endX = mTransformationEndX; + } + Interpolator customInterpolator = + customTransformation.getCustomInterpolator(TRANSFORM_X, false /* isFrom */); + if (customInterpolator != null) { + interpolation = customInterpolator.getInterpolation(transformationAmount); + } } transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(), endX, - interpolatedValue)); + interpolation)); } if (transformY) { float endY = otherStablePosition[1] - ownPosition[1]; - if (customTransformation != null - && customTransformation.customTransformTarget(this, otherState)) { - endY = mTransformationEndY; + float interpolation = interpolatedValue; + if (customTransformation != null) { + if (customTransformation.customTransformTarget(this, otherState)) { + endY = mTransformationEndY; + } + Interpolator customInterpolator = + customTransformation.getCustomInterpolator(TRANSFORM_Y, false /* isFrom */); + if (customInterpolator != null) { + interpolation = customInterpolator.getInterpolation(transformationAmount); + } } transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(), endY, - interpolatedValue)); + interpolation)); } if (transformScale) { View otherView = otherState.getTransformedView(); diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index 1e0035d65939a..6f49df4795655 100644 --- a/services/core/java/com/android/server/notification/NotificationComparator.java +++ b/services/core/java/com/android/server/notification/NotificationComparator.java @@ -30,7 +30,6 @@ import android.provider.Settings; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.Slog; import java.util.Comparator; import java.util.Objects; @@ -57,7 +56,15 @@ public class NotificationComparator @Override public int compare(NotificationRecord left, NotificationRecord right) { - // First up: sufficiently important ongoing notifications of certain categories + // first all colorized notifications + boolean leftImportantColorized = isImportantColorized(left); + boolean rightImportantColorized = isImportantColorized(right); + + if (leftImportantColorized != rightImportantColorized) { + return -1 * Boolean.compare(leftImportantColorized, rightImportantColorized); + } + + // sufficiently important ongoing notifications of certain categories boolean leftImportantOngoing = isImportantOngoing(left); boolean rightImportantOngoing = isImportantOngoing(right); @@ -106,6 +113,13 @@ public class NotificationComparator return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs()); } + private boolean isImportantColorized(NotificationRecord record) { + if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) { + return false; + } + return record.getNotification().isColorized(); + } + private boolean isImportantOngoing(NotificationRecord record) { if (!isOngoing(record)) { return false; @@ -148,7 +162,6 @@ public class NotificationComparator return (record.getNotification().flags & ongoingFlags) != 0; } - private Class getNotificationStyle(NotificationRecord record) { String templateClass = record.getNotification().extras.getString(Notification.EXTRA_TEMPLATE);