Loading core/java/android/app/Notification.java +94 −24 Original line number Diff line number Diff line Loading @@ -17,12 +17,14 @@ package android.app; import com.android.internal.R; import com.android.internal.util.LegacyNotificationUtil; import android.annotation.IntDef; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.media.AudioManager; import android.net.Uri; import android.os.BadParcelableException; Loading Loading @@ -652,13 +654,6 @@ public class Notification implements Parcelable */ public static final String EXTRA_AS_HEADS_UP = "headsup"; /** * Extra added from {@link Notification.Builder} to indicate that the remote views were inflated * from the builder, as opposed to being created directly from the application. * @hide */ public static final String EXTRA_BUILDER_REMOTE_VIEWS = "android.builderRemoteViews"; /** * Allow certain system-generated notifications to appear before the device is provisioned. * Only available to notifications coming from the android package. Loading Loading @@ -1315,6 +1310,7 @@ public class Notification implements Parcelable private int mVisibility = VISIBILITY_PRIVATE; private Notification mPublicVersion = null; private boolean mQuantumTheme; private final LegacyNotificationUtil mLegacyNotificationUtil; /** * Constructs a new Builder with the defaults: Loading Loading @@ -1345,6 +1341,10 @@ public class Notification implements Parcelable // TODO: Decide on targetSdk from calling app whether to use quantum theme. mQuantumTheme = true; // TODO: Decide on targetSdk from calling app whether to instantiate the processor at // all. mLegacyNotificationUtil = LegacyNotificationUtil.getInstance(); } /** Loading Loading @@ -1846,42 +1846,50 @@ public class Notification implements Parcelable boolean showLine3 = false; boolean showLine2 = false; int smallIconImageViewId = R.id.icon; if (mLargeIcon != null) { contentView.setImageViewBitmap(R.id.icon, mLargeIcon); smallIconImageViewId = R.id.right_icon; } if (!mQuantumTheme && mPriority < PRIORITY_LOW) { contentView.setInt(R.id.icon, "setBackgroundResource", R.drawable.notification_template_icon_low_bg); contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource", R.drawable.notification_bg_low); } if (mLargeIcon != null) { contentView.setImageViewBitmap(R.id.icon, mLargeIcon); processLegacyLargeIcon(mLargeIcon, contentView); smallIconImageViewId = R.id.right_icon; } if (mSmallIcon != 0) { contentView.setImageViewResource(smallIconImageViewId, mSmallIcon); contentView.setViewVisibility(smallIconImageViewId, View.VISIBLE); if (mLargeIcon != null) { processLegacySmallIcon(mSmallIcon, smallIconImageViewId, contentView); } else { processLegacyLargeIcon(mSmallIcon, contentView); } } else { contentView.setViewVisibility(smallIconImageViewId, View.GONE); } if (mContentTitle != null) { contentView.setTextViewText(R.id.title, mContentTitle); contentView.setTextViewText(R.id.title, processLegacyText(mContentTitle)); } if (mContentText != null) { contentView.setTextViewText(R.id.text, mContentText); contentView.setTextViewText(R.id.text, processLegacyText(mContentText)); showLine3 = true; } if (mContentInfo != null) { contentView.setTextViewText(R.id.info, mContentInfo); contentView.setTextViewText(R.id.info, processLegacyText(mContentInfo)); contentView.setViewVisibility(R.id.info, View.VISIBLE); showLine3 = true; } else if (mNumber > 0) { final int tooBig = mContext.getResources().getInteger( R.integer.status_bar_notification_info_maxnum); if (mNumber > tooBig) { contentView.setTextViewText(R.id.info, mContext.getResources().getString( R.string.status_bar_notification_info_overflow)); contentView.setTextViewText(R.id.info, processLegacyText( mContext.getResources().getString( R.string.status_bar_notification_info_overflow))); } else { NumberFormat f = NumberFormat.getIntegerInstance(); contentView.setTextViewText(R.id.info, f.format(mNumber)); contentView.setTextViewText(R.id.info, processLegacyText(f.format(mNumber))); } contentView.setViewVisibility(R.id.info, View.VISIBLE); showLine3 = true; Loading @@ -1891,9 +1899,9 @@ public class Notification implements Parcelable // Need to show three lines? if (mSubText != null) { contentView.setTextViewText(R.id.text, mSubText); contentView.setTextViewText(R.id.text, processLegacyText(mSubText)); if (mContentText != null) { contentView.setTextViewText(R.id.text2, mContentText); contentView.setTextViewText(R.id.text2, processLegacyText(mContentText)); contentView.setViewVisibility(R.id.text2, View.VISIBLE); showLine2 = true; } else { Loading Loading @@ -2001,14 +2009,77 @@ public class Notification implements Parcelable tombstone ? getActionTombstoneLayoutResource() : getActionLayoutResource()); button.setTextViewCompoundDrawablesRelative(R.id.action0, action.icon, 0, 0, 0); button.setTextViewText(R.id.action0, action.title); button.setTextViewText(R.id.action0, processLegacyText(action.title)); if (!tombstone) { button.setOnClickPendingIntent(R.id.action0, action.actionIntent); } button.setContentDescription(R.id.action0, action.title); processLegacyAction(action, button); return button; } /** * @return Whether we are currently building a notification from a legacy (an app that * doesn't create quantum notifications by itself) app. */ private boolean isLegacy() { return mLegacyNotificationUtil != null; } private void processLegacyAction(Action action, RemoteViews button) { if (isLegacy()) { if (mLegacyNotificationUtil.isGrayscale(mContext, action.icon)) { button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0, mContext.getResources().getColor( R.color.notification_action_legacy_color_filter), PorterDuff.Mode.MULTIPLY); } } } private CharSequence processLegacyText(CharSequence charSequence) { if (isLegacy()) { return mLegacyNotificationUtil.invertCharSequenceColors(charSequence); } else { return charSequence; } } private void processLegacyLargeIcon(int largeIconId, RemoteViews contentView) { if (isLegacy()) { processLegacyLargeIcon( mLegacyNotificationUtil.isGrayscale(mContext, largeIconId), contentView); } } private void processLegacyLargeIcon(Bitmap largeIcon, RemoteViews contentView) { if (isLegacy()) { processLegacyLargeIcon( mLegacyNotificationUtil.isGrayscale(largeIcon), contentView); } } private void processLegacyLargeIcon(boolean isGrayscale, RemoteViews contentView) { if (isLegacy() && isGrayscale) { contentView.setInt(R.id.icon, "setBackgroundResource", R.drawable.notification_icon_legacy_bg_inset); } } private void processLegacySmallIcon(int smallIconDrawableId, int smallIconImageViewId, RemoteViews contentView) { if (isLegacy()) { if (mLegacyNotificationUtil.isGrayscale(mContext, smallIconDrawableId)) { contentView.setDrawableParameters(smallIconImageViewId, false, -1, mContext.getResources().getColor( R.color.notification_action_legacy_color_filter), PorterDuff.Mode.MULTIPLY, -1); } } } /** * Apply the unstyled operations and return a new {@link Notification} object. * @hide Loading Loading @@ -2075,7 +2146,6 @@ public class Notification implements Parcelable extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate); extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer); extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen); extras.putBoolean(EXTRA_BUILDER_REMOTE_VIEWS, mContentView == null); if (mLargeIcon != null) { extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); } Loading Loading @@ -2226,7 +2296,7 @@ public class Notification implements Parcelable mSummaryTextSet ? mSummaryText : mBuilder.mSubText; if (overflowText != null) { contentView.setTextViewText(R.id.text, overflowText); contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText)); contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE); contentView.setViewVisibility(R.id.line3, View.VISIBLE); } else { Loading Loading @@ -2437,7 +2507,7 @@ public class Notification implements Parcelable contentView.setViewPadding(R.id.line1, 0, 0, 0, 0); } contentView.setTextViewText(R.id.big_text, mBigText); contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText)); contentView.setViewVisibility(R.id.big_text, View.VISIBLE); contentView.setViewVisibility(R.id.text2, View.GONE); Loading Loading @@ -2542,7 +2612,7 @@ public class Notification implements Parcelable CharSequence str = mTexts.get(i); if (str != null && !str.equals("")) { contentView.setViewVisibility(rowIds[i], View.VISIBLE); contentView.setTextViewText(rowIds[i], str); contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str)); } i++; } Loading core/java/android/widget/RemoteViews.java +94 −0 Original line number Diff line number Diff line Loading @@ -1515,6 +1515,75 @@ public class RemoteViews implements Parcelable, Filter { public final static int TAG = 14; } /** * Helper action to set a color filter on a compound drawable on a TextView. Supports relative * (s/t/e/b) or cardinal (l/t/r/b) arrangement. */ private class TextViewDrawableColorFilterAction extends Action { public TextViewDrawableColorFilterAction(int viewId, boolean isRelative, int index, int color, PorterDuff.Mode mode) { this.viewId = viewId; this.isRelative = isRelative; this.index = index; this.color = color; this.mode = mode; } public TextViewDrawableColorFilterAction(Parcel parcel) { viewId = parcel.readInt(); isRelative = (parcel.readInt() != 0); index = parcel.readInt(); color = parcel.readInt(); mode = readPorterDuffMode(parcel); } private PorterDuff.Mode readPorterDuffMode(Parcel parcel) { int mode = parcel.readInt(); if (mode >= 0 && mode < PorterDuff.Mode.values().length) { return PorterDuff.Mode.values()[mode]; } else { return PorterDuff.Mode.CLEAR; } } public void writeToParcel(Parcel dest, int flags) { dest.writeInt(TAG); dest.writeInt(viewId); dest.writeInt(isRelative ? 1 : 0); dest.writeInt(index); dest.writeInt(color); dest.writeInt(mode.ordinal()); } @Override public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { final TextView target = (TextView) root.findViewById(viewId); if (target == null) return; Drawable[] drawables = isRelative ? target.getCompoundDrawablesRelative() : target.getCompoundDrawables(); if (index < 0 || index >= 4) { throw new IllegalStateException("index must be in range [0, 3]."); } Drawable d = drawables[index]; if (d != null) { d.mutate(); d.setColorFilter(color, mode); } } public String getActionName() { return "TextViewDrawableColorFilterAction"; } final boolean isRelative; final int index; final int color; final PorterDuff.Mode mode; public final static int TAG = 17; } /** * Simple class used to keep track of memory usage in a RemoteViews. * Loading Loading @@ -1686,6 +1755,9 @@ public class RemoteViews implements Parcelable, Filter { case SetRemoteViewsAdapterList.TAG: mActions.add(new SetRemoteViewsAdapterList(parcel)); break; case TextViewDrawableColorFilterAction.TAG: mActions.add(new TextViewDrawableColorFilterAction(parcel)); break; default: throw new ActionException("Tag " + tag + " not found"); } Loading Loading @@ -1920,6 +1992,28 @@ public class RemoteViews implements Parcelable, Filter { addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); } /** * Equivalent to applying a color filter on one of the drawables in * {@link android.widget.TextView#getCompoundDrawablesRelative()}. * * @param viewId The id of the view whose text should change. * @param index The index of the drawable in the array of * {@link android.widget.TextView#getCompoundDrawablesRelative()} to set the color * filter on. Must be in [0, 3]. * @param color The color of the color filter. See * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}. * @param mode The mode of the color filter. See * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}. * @hide */ public void setTextViewCompoundDrawablesRelativeColorFilter(int viewId, int index, int color, PorterDuff.Mode mode) { if (index < 0 || index >= 4) { throw new IllegalArgumentException("index must be in range [0, 3]."); } addAction(new TextViewDrawableColorFilterAction(viewId, true, index, color, mode)); } /** * Equivalent to calling ImageView.setImageResource * Loading packages/SystemUI/src/com/android/systemui/ImageUtils.java→core/java/com/android/internal/util/ImageUtils.java +4 −2 Original line number Diff line number Diff line Loading @@ -14,12 +14,14 @@ * limitations under the License */ package com.android.systemui; package com.android.internal.util; import android.graphics.Bitmap; /** * Utility class for image analysis and processing. * * @hide */ public class ImageUtils { Loading Loading @@ -65,7 +67,7 @@ public class ImageUtils { * * Note that really transparent colors are always grayscale. */ public boolean isGrayscale(int color) { public static boolean isGrayscale(int color) { int alpha = 0xFF & (color >> 24); if (alpha < ALPHA_TOLERANCE) { return true; Loading core/java/com/android/internal/util/LegacyNotificationUtil.java 0 → 100644 +193 −0 Original line number Diff line number Diff line /* * Copyright (C) 2014 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.internal.util; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.TextAppearanceSpan; import android.util.Log; import android.util.Pair; import java.util.Arrays; import java.util.WeakHashMap; /** * Helper class to process legacy (Holo) notifications to make them look like quantum notifications. * * @hide */ public class LegacyNotificationUtil { private static final String TAG = "LegacyNotificationUtil"; private static final Object sLock = new Object(); private static LegacyNotificationUtil sInstance; private final ImageUtils mImageUtils = new ImageUtils(); private final WeakHashMap<Bitmap, Pair<Boolean, Integer>> mGrayscaleBitmapCache = new WeakHashMap<Bitmap, Pair<Boolean, Integer>>(); public static LegacyNotificationUtil getInstance() { synchronized (sLock) { if (sInstance == null) { sInstance = new LegacyNotificationUtil(); } return sInstance; } } /** * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect * gray". * * @param bitmap The bitmap to test. * @return Whether the bitmap is grayscale. */ public boolean isGrayscale(Bitmap bitmap) { synchronized (sLock) { Pair<Boolean, Integer> cached = mGrayscaleBitmapCache.get(bitmap); if (cached != null) { if (cached.second == bitmap.getGenerationId()) { return cached.first; } } } boolean result; int generationId; synchronized (mImageUtils) { result = mImageUtils.isGrayscale(bitmap); // generationId and the check whether the Bitmap is grayscale can't be read atomically // here. However, since the thread is in the process of posting the notification, we can // assume that it doesn't modify the bitmap while we are checking the pixels. generationId = bitmap.getGenerationId(); } synchronized (sLock) { mGrayscaleBitmapCache.put(bitmap, Pair.create(result, generationId)); } return result; } /** * Checks whether a drawable is grayscale. Grayscale here means "very close to a perfect * gray". * * @param d The drawable to test. * @return Whether the drawable is grayscale. */ public boolean isGrayscale(Drawable d) { if (d == null) { return false; } else if (d instanceof BitmapDrawable) { BitmapDrawable bd = (BitmapDrawable) d; return bd.getBitmap() != null && isGrayscale(bd.getBitmap()); } else if (d instanceof AnimationDrawable) { AnimationDrawable ad = (AnimationDrawable) d; int count = ad.getNumberOfFrames(); return count > 0 && isGrayscale(ad.getFrame(0)); } else { return false; } } /** * Checks whether a drawable with a resoure id is grayscale. Grayscale here means "very close * to a perfect gray". * * @param context The context to load the drawable from. * @return Whether the drawable is grayscale. */ public boolean isGrayscale(Context context, int drawableResId) { if (drawableResId != 0) { try { return isGrayscale(context.getDrawable(drawableResId)); } catch (Resources.NotFoundException ex) { Log.e(TAG, "Drawable not found: " + drawableResId); return false; } } else { return false; } } /** * Inverts all the grayscale colors set by {@link android.text.style.TextAppearanceSpan}s on * the text. * * @param charSequence The text to process. * @return The color inverted text. */ public CharSequence invertCharSequenceColors(CharSequence charSequence) { if (charSequence instanceof Spanned) { Spanned ss = (Spanned) charSequence; Object[] spans = ss.getSpans(0, ss.length(), Object.class); SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); for (Object span : spans) { Object resultSpan = span; if (span instanceof TextAppearanceSpan) { resultSpan = processTextAppearanceSpan((TextAppearanceSpan) span); } builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), ss.getSpanFlags(span)); } return builder; } return charSequence; } private TextAppearanceSpan processTextAppearanceSpan(TextAppearanceSpan span) { ColorStateList colorStateList = span.getTextColor(); if (colorStateList != null) { int[] colors = colorStateList.getColors(); boolean changed = false; for (int i = 0; i < colors.length; i++) { if (ImageUtils.isGrayscale(colors[i])) { // Allocate a new array so we don't change the colors in the old color state // list. if (!changed) { colors = Arrays.copyOf(colors, colors.length); } colors[i] = processColor(colors[i]); changed = true; } } if (changed) { return new TextAppearanceSpan( span.getFamily(), span.getTextStyle(), span.getTextSize(), new ColorStateList(colorStateList.getStates(), colors), span.getLinkTextColor()); } } return span; } private int processColor(int color) { return Color.argb(Color.alpha(color), 255 - Color.red(color), 255 - Color.green(color), 255 - Color.blue(color)); } } packages/SystemUI/res/drawable/notification_icon_legacy_bg.xml→core/res/res/drawable/notification_icon_legacy_bg.xml +0 −0 File moved. View file Loading
core/java/android/app/Notification.java +94 −24 Original line number Diff line number Diff line Loading @@ -17,12 +17,14 @@ package android.app; import com.android.internal.R; import com.android.internal.util.LegacyNotificationUtil; import android.annotation.IntDef; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.media.AudioManager; import android.net.Uri; import android.os.BadParcelableException; Loading Loading @@ -652,13 +654,6 @@ public class Notification implements Parcelable */ public static final String EXTRA_AS_HEADS_UP = "headsup"; /** * Extra added from {@link Notification.Builder} to indicate that the remote views were inflated * from the builder, as opposed to being created directly from the application. * @hide */ public static final String EXTRA_BUILDER_REMOTE_VIEWS = "android.builderRemoteViews"; /** * Allow certain system-generated notifications to appear before the device is provisioned. * Only available to notifications coming from the android package. Loading Loading @@ -1315,6 +1310,7 @@ public class Notification implements Parcelable private int mVisibility = VISIBILITY_PRIVATE; private Notification mPublicVersion = null; private boolean mQuantumTheme; private final LegacyNotificationUtil mLegacyNotificationUtil; /** * Constructs a new Builder with the defaults: Loading Loading @@ -1345,6 +1341,10 @@ public class Notification implements Parcelable // TODO: Decide on targetSdk from calling app whether to use quantum theme. mQuantumTheme = true; // TODO: Decide on targetSdk from calling app whether to instantiate the processor at // all. mLegacyNotificationUtil = LegacyNotificationUtil.getInstance(); } /** Loading Loading @@ -1846,42 +1846,50 @@ public class Notification implements Parcelable boolean showLine3 = false; boolean showLine2 = false; int smallIconImageViewId = R.id.icon; if (mLargeIcon != null) { contentView.setImageViewBitmap(R.id.icon, mLargeIcon); smallIconImageViewId = R.id.right_icon; } if (!mQuantumTheme && mPriority < PRIORITY_LOW) { contentView.setInt(R.id.icon, "setBackgroundResource", R.drawable.notification_template_icon_low_bg); contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource", R.drawable.notification_bg_low); } if (mLargeIcon != null) { contentView.setImageViewBitmap(R.id.icon, mLargeIcon); processLegacyLargeIcon(mLargeIcon, contentView); smallIconImageViewId = R.id.right_icon; } if (mSmallIcon != 0) { contentView.setImageViewResource(smallIconImageViewId, mSmallIcon); contentView.setViewVisibility(smallIconImageViewId, View.VISIBLE); if (mLargeIcon != null) { processLegacySmallIcon(mSmallIcon, smallIconImageViewId, contentView); } else { processLegacyLargeIcon(mSmallIcon, contentView); } } else { contentView.setViewVisibility(smallIconImageViewId, View.GONE); } if (mContentTitle != null) { contentView.setTextViewText(R.id.title, mContentTitle); contentView.setTextViewText(R.id.title, processLegacyText(mContentTitle)); } if (mContentText != null) { contentView.setTextViewText(R.id.text, mContentText); contentView.setTextViewText(R.id.text, processLegacyText(mContentText)); showLine3 = true; } if (mContentInfo != null) { contentView.setTextViewText(R.id.info, mContentInfo); contentView.setTextViewText(R.id.info, processLegacyText(mContentInfo)); contentView.setViewVisibility(R.id.info, View.VISIBLE); showLine3 = true; } else if (mNumber > 0) { final int tooBig = mContext.getResources().getInteger( R.integer.status_bar_notification_info_maxnum); if (mNumber > tooBig) { contentView.setTextViewText(R.id.info, mContext.getResources().getString( R.string.status_bar_notification_info_overflow)); contentView.setTextViewText(R.id.info, processLegacyText( mContext.getResources().getString( R.string.status_bar_notification_info_overflow))); } else { NumberFormat f = NumberFormat.getIntegerInstance(); contentView.setTextViewText(R.id.info, f.format(mNumber)); contentView.setTextViewText(R.id.info, processLegacyText(f.format(mNumber))); } contentView.setViewVisibility(R.id.info, View.VISIBLE); showLine3 = true; Loading @@ -1891,9 +1899,9 @@ public class Notification implements Parcelable // Need to show three lines? if (mSubText != null) { contentView.setTextViewText(R.id.text, mSubText); contentView.setTextViewText(R.id.text, processLegacyText(mSubText)); if (mContentText != null) { contentView.setTextViewText(R.id.text2, mContentText); contentView.setTextViewText(R.id.text2, processLegacyText(mContentText)); contentView.setViewVisibility(R.id.text2, View.VISIBLE); showLine2 = true; } else { Loading Loading @@ -2001,14 +2009,77 @@ public class Notification implements Parcelable tombstone ? getActionTombstoneLayoutResource() : getActionLayoutResource()); button.setTextViewCompoundDrawablesRelative(R.id.action0, action.icon, 0, 0, 0); button.setTextViewText(R.id.action0, action.title); button.setTextViewText(R.id.action0, processLegacyText(action.title)); if (!tombstone) { button.setOnClickPendingIntent(R.id.action0, action.actionIntent); } button.setContentDescription(R.id.action0, action.title); processLegacyAction(action, button); return button; } /** * @return Whether we are currently building a notification from a legacy (an app that * doesn't create quantum notifications by itself) app. */ private boolean isLegacy() { return mLegacyNotificationUtil != null; } private void processLegacyAction(Action action, RemoteViews button) { if (isLegacy()) { if (mLegacyNotificationUtil.isGrayscale(mContext, action.icon)) { button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0, mContext.getResources().getColor( R.color.notification_action_legacy_color_filter), PorterDuff.Mode.MULTIPLY); } } } private CharSequence processLegacyText(CharSequence charSequence) { if (isLegacy()) { return mLegacyNotificationUtil.invertCharSequenceColors(charSequence); } else { return charSequence; } } private void processLegacyLargeIcon(int largeIconId, RemoteViews contentView) { if (isLegacy()) { processLegacyLargeIcon( mLegacyNotificationUtil.isGrayscale(mContext, largeIconId), contentView); } } private void processLegacyLargeIcon(Bitmap largeIcon, RemoteViews contentView) { if (isLegacy()) { processLegacyLargeIcon( mLegacyNotificationUtil.isGrayscale(largeIcon), contentView); } } private void processLegacyLargeIcon(boolean isGrayscale, RemoteViews contentView) { if (isLegacy() && isGrayscale) { contentView.setInt(R.id.icon, "setBackgroundResource", R.drawable.notification_icon_legacy_bg_inset); } } private void processLegacySmallIcon(int smallIconDrawableId, int smallIconImageViewId, RemoteViews contentView) { if (isLegacy()) { if (mLegacyNotificationUtil.isGrayscale(mContext, smallIconDrawableId)) { contentView.setDrawableParameters(smallIconImageViewId, false, -1, mContext.getResources().getColor( R.color.notification_action_legacy_color_filter), PorterDuff.Mode.MULTIPLY, -1); } } } /** * Apply the unstyled operations and return a new {@link Notification} object. * @hide Loading Loading @@ -2075,7 +2146,6 @@ public class Notification implements Parcelable extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate); extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer); extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen); extras.putBoolean(EXTRA_BUILDER_REMOTE_VIEWS, mContentView == null); if (mLargeIcon != null) { extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); } Loading Loading @@ -2226,7 +2296,7 @@ public class Notification implements Parcelable mSummaryTextSet ? mSummaryText : mBuilder.mSubText; if (overflowText != null) { contentView.setTextViewText(R.id.text, overflowText); contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText)); contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE); contentView.setViewVisibility(R.id.line3, View.VISIBLE); } else { Loading Loading @@ -2437,7 +2507,7 @@ public class Notification implements Parcelable contentView.setViewPadding(R.id.line1, 0, 0, 0, 0); } contentView.setTextViewText(R.id.big_text, mBigText); contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText)); contentView.setViewVisibility(R.id.big_text, View.VISIBLE); contentView.setViewVisibility(R.id.text2, View.GONE); Loading Loading @@ -2542,7 +2612,7 @@ public class Notification implements Parcelable CharSequence str = mTexts.get(i); if (str != null && !str.equals("")) { contentView.setViewVisibility(rowIds[i], View.VISIBLE); contentView.setTextViewText(rowIds[i], str); contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str)); } i++; } Loading
core/java/android/widget/RemoteViews.java +94 −0 Original line number Diff line number Diff line Loading @@ -1515,6 +1515,75 @@ public class RemoteViews implements Parcelable, Filter { public final static int TAG = 14; } /** * Helper action to set a color filter on a compound drawable on a TextView. Supports relative * (s/t/e/b) or cardinal (l/t/r/b) arrangement. */ private class TextViewDrawableColorFilterAction extends Action { public TextViewDrawableColorFilterAction(int viewId, boolean isRelative, int index, int color, PorterDuff.Mode mode) { this.viewId = viewId; this.isRelative = isRelative; this.index = index; this.color = color; this.mode = mode; } public TextViewDrawableColorFilterAction(Parcel parcel) { viewId = parcel.readInt(); isRelative = (parcel.readInt() != 0); index = parcel.readInt(); color = parcel.readInt(); mode = readPorterDuffMode(parcel); } private PorterDuff.Mode readPorterDuffMode(Parcel parcel) { int mode = parcel.readInt(); if (mode >= 0 && mode < PorterDuff.Mode.values().length) { return PorterDuff.Mode.values()[mode]; } else { return PorterDuff.Mode.CLEAR; } } public void writeToParcel(Parcel dest, int flags) { dest.writeInt(TAG); dest.writeInt(viewId); dest.writeInt(isRelative ? 1 : 0); dest.writeInt(index); dest.writeInt(color); dest.writeInt(mode.ordinal()); } @Override public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { final TextView target = (TextView) root.findViewById(viewId); if (target == null) return; Drawable[] drawables = isRelative ? target.getCompoundDrawablesRelative() : target.getCompoundDrawables(); if (index < 0 || index >= 4) { throw new IllegalStateException("index must be in range [0, 3]."); } Drawable d = drawables[index]; if (d != null) { d.mutate(); d.setColorFilter(color, mode); } } public String getActionName() { return "TextViewDrawableColorFilterAction"; } final boolean isRelative; final int index; final int color; final PorterDuff.Mode mode; public final static int TAG = 17; } /** * Simple class used to keep track of memory usage in a RemoteViews. * Loading Loading @@ -1686,6 +1755,9 @@ public class RemoteViews implements Parcelable, Filter { case SetRemoteViewsAdapterList.TAG: mActions.add(new SetRemoteViewsAdapterList(parcel)); break; case TextViewDrawableColorFilterAction.TAG: mActions.add(new TextViewDrawableColorFilterAction(parcel)); break; default: throw new ActionException("Tag " + tag + " not found"); } Loading Loading @@ -1920,6 +1992,28 @@ public class RemoteViews implements Parcelable, Filter { addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); } /** * Equivalent to applying a color filter on one of the drawables in * {@link android.widget.TextView#getCompoundDrawablesRelative()}. * * @param viewId The id of the view whose text should change. * @param index The index of the drawable in the array of * {@link android.widget.TextView#getCompoundDrawablesRelative()} to set the color * filter on. Must be in [0, 3]. * @param color The color of the color filter. See * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}. * @param mode The mode of the color filter. See * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}. * @hide */ public void setTextViewCompoundDrawablesRelativeColorFilter(int viewId, int index, int color, PorterDuff.Mode mode) { if (index < 0 || index >= 4) { throw new IllegalArgumentException("index must be in range [0, 3]."); } addAction(new TextViewDrawableColorFilterAction(viewId, true, index, color, mode)); } /** * Equivalent to calling ImageView.setImageResource * Loading
packages/SystemUI/src/com/android/systemui/ImageUtils.java→core/java/com/android/internal/util/ImageUtils.java +4 −2 Original line number Diff line number Diff line Loading @@ -14,12 +14,14 @@ * limitations under the License */ package com.android.systemui; package com.android.internal.util; import android.graphics.Bitmap; /** * Utility class for image analysis and processing. * * @hide */ public class ImageUtils { Loading Loading @@ -65,7 +67,7 @@ public class ImageUtils { * * Note that really transparent colors are always grayscale. */ public boolean isGrayscale(int color) { public static boolean isGrayscale(int color) { int alpha = 0xFF & (color >> 24); if (alpha < ALPHA_TOLERANCE) { return true; Loading
core/java/com/android/internal/util/LegacyNotificationUtil.java 0 → 100644 +193 −0 Original line number Diff line number Diff line /* * Copyright (C) 2014 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.internal.util; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.TextAppearanceSpan; import android.util.Log; import android.util.Pair; import java.util.Arrays; import java.util.WeakHashMap; /** * Helper class to process legacy (Holo) notifications to make them look like quantum notifications. * * @hide */ public class LegacyNotificationUtil { private static final String TAG = "LegacyNotificationUtil"; private static final Object sLock = new Object(); private static LegacyNotificationUtil sInstance; private final ImageUtils mImageUtils = new ImageUtils(); private final WeakHashMap<Bitmap, Pair<Boolean, Integer>> mGrayscaleBitmapCache = new WeakHashMap<Bitmap, Pair<Boolean, Integer>>(); public static LegacyNotificationUtil getInstance() { synchronized (sLock) { if (sInstance == null) { sInstance = new LegacyNotificationUtil(); } return sInstance; } } /** * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect * gray". * * @param bitmap The bitmap to test. * @return Whether the bitmap is grayscale. */ public boolean isGrayscale(Bitmap bitmap) { synchronized (sLock) { Pair<Boolean, Integer> cached = mGrayscaleBitmapCache.get(bitmap); if (cached != null) { if (cached.second == bitmap.getGenerationId()) { return cached.first; } } } boolean result; int generationId; synchronized (mImageUtils) { result = mImageUtils.isGrayscale(bitmap); // generationId and the check whether the Bitmap is grayscale can't be read atomically // here. However, since the thread is in the process of posting the notification, we can // assume that it doesn't modify the bitmap while we are checking the pixels. generationId = bitmap.getGenerationId(); } synchronized (sLock) { mGrayscaleBitmapCache.put(bitmap, Pair.create(result, generationId)); } return result; } /** * Checks whether a drawable is grayscale. Grayscale here means "very close to a perfect * gray". * * @param d The drawable to test. * @return Whether the drawable is grayscale. */ public boolean isGrayscale(Drawable d) { if (d == null) { return false; } else if (d instanceof BitmapDrawable) { BitmapDrawable bd = (BitmapDrawable) d; return bd.getBitmap() != null && isGrayscale(bd.getBitmap()); } else if (d instanceof AnimationDrawable) { AnimationDrawable ad = (AnimationDrawable) d; int count = ad.getNumberOfFrames(); return count > 0 && isGrayscale(ad.getFrame(0)); } else { return false; } } /** * Checks whether a drawable with a resoure id is grayscale. Grayscale here means "very close * to a perfect gray". * * @param context The context to load the drawable from. * @return Whether the drawable is grayscale. */ public boolean isGrayscale(Context context, int drawableResId) { if (drawableResId != 0) { try { return isGrayscale(context.getDrawable(drawableResId)); } catch (Resources.NotFoundException ex) { Log.e(TAG, "Drawable not found: " + drawableResId); return false; } } else { return false; } } /** * Inverts all the grayscale colors set by {@link android.text.style.TextAppearanceSpan}s on * the text. * * @param charSequence The text to process. * @return The color inverted text. */ public CharSequence invertCharSequenceColors(CharSequence charSequence) { if (charSequence instanceof Spanned) { Spanned ss = (Spanned) charSequence; Object[] spans = ss.getSpans(0, ss.length(), Object.class); SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); for (Object span : spans) { Object resultSpan = span; if (span instanceof TextAppearanceSpan) { resultSpan = processTextAppearanceSpan((TextAppearanceSpan) span); } builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), ss.getSpanFlags(span)); } return builder; } return charSequence; } private TextAppearanceSpan processTextAppearanceSpan(TextAppearanceSpan span) { ColorStateList colorStateList = span.getTextColor(); if (colorStateList != null) { int[] colors = colorStateList.getColors(); boolean changed = false; for (int i = 0; i < colors.length; i++) { if (ImageUtils.isGrayscale(colors[i])) { // Allocate a new array so we don't change the colors in the old color state // list. if (!changed) { colors = Arrays.copyOf(colors, colors.length); } colors[i] = processColor(colors[i]); changed = true; } } if (changed) { return new TextAppearanceSpan( span.getFamily(), span.getTextStyle(), span.getTextSize(), new ColorStateList(colorStateList.getStates(), colors), span.getLinkTextColor()); } } return span; } private int processColor(int color) { return Color.argb(Color.alpha(color), 255 - Color.red(color), 255 - Color.green(color), 255 - Color.blue(color)); } }
packages/SystemUI/res/drawable/notification_icon_legacy_bg.xml→core/res/res/drawable/notification_icon_legacy_bg.xml +0 −0 File moved. View file