diff options
17 files changed, 419 insertions, 259 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 13e74da..36d2635 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -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; @@ -653,13 +655,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. * @hide @@ -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: @@ -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(); } /** @@ -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; @@ -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 { @@ -2001,15 +2009,78 @@ 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 */ @@ -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); } @@ -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 { @@ -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); @@ -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++; } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 0d3df51..f7d20b53 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1516,6 +1516,75 @@ public class RemoteViews implements Parcelable, Filter { } /** + * 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. * */ @@ -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"); } @@ -1921,6 +1993,28 @@ public class RemoteViews implements Parcelable, Filter { } /** + * 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 * * @param viewId The id of the view whose drawable should change diff --git a/packages/SystemUI/src/com/android/systemui/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java index 540ba20..a5ce6e0 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageUtils.java +++ b/core/java/com/android/internal/util/ImageUtils.java @@ -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 { @@ -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; diff --git a/core/java/com/android/internal/util/LegacyNotificationUtil.java b/core/java/com/android/internal/util/LegacyNotificationUtil.java new file mode 100644 index 0000000..0394bbc --- /dev/null +++ b/core/java/com/android/internal/util/LegacyNotificationUtil.java @@ -0,0 +1,193 @@ +/* + * 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)); + } +} diff --git a/packages/SystemUI/res/drawable/notification_icon_legacy_bg.xml b/core/res/res/drawable/notification_icon_legacy_bg.xml index 4ac67c3..4ac67c3 100644 --- a/packages/SystemUI/res/drawable/notification_icon_legacy_bg.xml +++ b/core/res/res/drawable/notification_icon_legacy_bg.xml diff --git a/packages/SystemUI/res/drawable/notification_icon_legacy_bg_inset.xml b/core/res/res/drawable/notification_icon_legacy_bg_inset.xml index 96c5573..96c5573 100644 --- a/packages/SystemUI/res/drawable/notification_icon_legacy_bg_inset.xml +++ b/core/res/res/drawable/notification_icon_legacy_bg_inset.xml diff --git a/core/res/res/layout/notification_quantum_action.xml b/core/res/res/layout/notification_quantum_action.xml index 775182f..0986343 100644 --- a/core/res/res/layout/notification_quantum_action.xml +++ b/core/res/res/layout/notification_quantum_action.xml @@ -16,7 +16,7 @@ --> <Button xmlns:android="http://schemas.android.com/apk/res/android" - style="?android:attr/borderlessButtonStyle" + style="@android:style/Widget.Quantum.Light.Button.Borderless.Small" android:id="@+id/action0" android:layout_width="0dp" android:layout_height="48dp" diff --git a/core/res/res/layout/notification_quantum_action_list.xml b/core/res/res/layout/notification_quantum_action_list.xml index a8aef97..d723dcd 100644 --- a/core/res/res/layout/notification_quantum_action_list.xml +++ b/core/res/res/layout/notification_quantum_action_list.xml @@ -23,7 +23,7 @@ android:visibility="gone" android:layout_marginBottom="8dp" android:showDividers="middle" - android:divider="?android:attr/listDivider" + android:divider="@drawable/list_divider_quantum_light" android:dividerPadding="12dp" > <!-- actions will be added here --> diff --git a/core/res/res/layout/notification_quantum_action_tombstone.xml b/core/res/res/layout/notification_quantum_action_tombstone.xml index 9104991..51e4205 100644 --- a/core/res/res/layout/notification_quantum_action_tombstone.xml +++ b/core/res/res/layout/notification_quantum_action_tombstone.xml @@ -16,7 +16,7 @@ --> <Button xmlns:android="http://schemas.android.com/apk/res/android" - style="?android:attr/borderlessButtonStyle" + style="@android:style/Widget.Quantum.Light.Button.Borderless.Small" android:id="@+id/action0" android:layout_width="0dp" android:layout_height="48dp" diff --git a/core/res/res/layout/notification_template_quantum_base.xml b/core/res/res/layout/notification_template_quantum_base.xml index 3e97b2a..8863cfa 100644 --- a/core/res/res/layout/notification_template_quantum_base.xml +++ b/core/res/res/layout/notification_template_quantum_base.xml @@ -92,7 +92,7 @@ android:layout_height="12dp" android:layout_marginStart="8dp" android:visibility="gone" - style="?android:attr/progressBarStyleHorizontal" + style="@style/Widget.Quantum.Light.ProgressBar.Horizontal" /> <LinearLayout android:id="@+id/line3" diff --git a/core/res/res/layout/notification_template_quantum_big_base.xml b/core/res/res/layout/notification_template_quantum_big_base.xml index d860045..483ea07 100644 --- a/core/res/res/layout/notification_template_quantum_big_base.xml +++ b/core/res/res/layout/notification_template_quantum_big_base.xml @@ -148,7 +148,7 @@ android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:visibility="gone" - style="?android:attr/progressBarStyleHorizontal" + style="@style/Widget.Quantum.Light.ProgressBar.Horizontal" /> </LinearLayout> <ImageView @@ -156,7 +156,7 @@ android:layout_height="1dp" android:id="@+id/action_divider" android:visibility="gone" - android:background="?android:attr/dividerHorizontal" /> + android:background="@drawable/list_divider_quantum_light" /> <include layout="@layout/notification_quantum_action_list" android:layout_width="match_parent" diff --git a/core/res/res/layout/notification_template_quantum_big_text.xml b/core/res/res/layout/notification_template_quantum_big_text.xml index 585be80..4fe91c0 100644 --- a/core/res/res/layout/notification_template_quantum_big_text.xml +++ b/core/res/res/layout/notification_template_quantum_big_text.xml @@ -101,7 +101,7 @@ android:layout_marginEnd="8dp" android:visibility="gone" android:layout_weight="0" - style="?android:attr/progressBarStyleHorizontal" + style="@style/Widget.Quantum.Light.ProgressBar.Horizontal" /> <TextView android:id="@+id/big_text" android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent" @@ -121,7 +121,7 @@ android:layout_height="1dip" android:id="@+id/action_divider" android:visibility="gone" - android:background="?android:attr/dividerHorizontal" /> + android:background="@drawable/list_divider_quantum_light" /> <include layout="@layout/notification_quantum_action_list" android:layout_width="match_parent" @@ -135,7 +135,7 @@ android:id="@+id/overflow_divider" android:layout_marginBottom="8dp" android:visibility="visible" - android:background="?android:attr/dividerHorizontal" /> + android:background="@drawable/list_divider_quantum_light" /> <LinearLayout android:id="@+id/line3" android:layout_width="match_parent" diff --git a/core/res/res/layout/notification_template_quantum_inbox.xml b/core/res/res/layout/notification_template_quantum_inbox.xml index 31ed508..fd9089b 100644 --- a/core/res/res/layout/notification_template_quantum_inbox.xml +++ b/core/res/res/layout/notification_template_quantum_inbox.xml @@ -103,7 +103,7 @@ android:layout_marginEnd="8dp" android:visibility="gone" android:layout_weight="0" - style="?android:attr/progressBarStyleHorizontal" + style="@style/Widget.Quantum.Light.ProgressBar.Horizontal" /> <TextView android:id="@+id/inbox_text0" android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent" @@ -206,7 +206,7 @@ android:layout_height="1dip" android:id="@+id/action_divider" android:visibility="gone" - android:background="?android:attr/dividerHorizontal" /> + android:background="@drawable/list_divider_quantum_light" /> <include layout="@layout/notification_quantum_action_list" android:layout_width="match_parent" @@ -218,7 +218,7 @@ android:layout_height="1dip" android:id="@+id/overflow_divider" android:visibility="visible" - android:background="?android:attr/dividerHorizontal" /> + android:background="@drawable/list_divider_quantum_light" /> <LinearLayout android:id="@+id/line3" android:layout_width="match_parent" diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 1947c50..d0c455b 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -193,6 +193,9 @@ <drawable name="notification_template_icon_bg">#3333B5E5</drawable> <drawable name="notification_template_icon_low_bg">#0cffffff</drawable> + <color name="notification_icon_legacy_bg_color">#ff4285F4</color> + <color name="notification_action_legacy_color_filter">#ff555555</color> + <!-- Keyguard colors --> <color name="keyguard_avatar_frame_color">#ffffffff</color> <color name="keyguard_avatar_frame_shadow_color">#80000000</color> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index efa873d..db5f66e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1637,6 +1637,8 @@ <java-symbol type="layout" name="notification_template_quantum_big_picture" /> <java-symbol type="layout" name="notification_template_quantum_big_text" /> <java-symbol type="layout" name="notification_template_quantum_inbox" /> + <java-symbol type="color" name="notification_action_legacy_color_filter" /> + <java-symbol type="drawable" name="notification_icon_legacy_bg_inset" /> <!-- From SystemUI --> <java-symbol type="anim" name="push_down_in" /> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index a59dc75..5cf0453 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -39,8 +39,6 @@ <color name="status_bar_clock_color">#FFFFFFFF</color> <drawable name="notification_item_background_color">#ff111111</drawable> <drawable name="notification_item_background_color_pressed">#ff454545</drawable> - <color name="notification_icon_legacy_bg_color">#ff4285F4</color> - <color name="notification_action_legacy_color_filter">#ff555555</color> <!-- Tint color for inactive Quick Settings icons. --> <color name="ic_qs_off">#ff404040</color> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 68c8364..59eaf1a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -29,14 +29,9 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; -import android.content.res.ColorStateList; import android.content.res.Configuration; import android.database.ContentObserver; -import android.graphics.Color; -import android.graphics.PorterDuff; import android.graphics.Rect; -import android.graphics.drawable.AnimationDrawable; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; @@ -52,14 +47,10 @@ import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; -import android.text.SpannableStringBuilder; -import android.text.Spanned; import android.text.TextUtils; -import android.text.style.TextAppearanceSpan; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; -import android.view.ContextThemeWrapper; import android.view.Display; import android.view.IWindowManager; import android.view.LayoutInflater; @@ -70,7 +61,6 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupMenu; @@ -80,8 +70,8 @@ import android.widget.TextView; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; +import com.android.internal.util.LegacyNotificationUtil; import com.android.internal.widget.SizeAdaptiveLayout; -import com.android.systemui.ImageUtils; import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SearchPanelView; @@ -91,7 +81,6 @@ import com.android.systemui.statusbar.policy.NotificationRowLayout; import java.util.ArrayList; import java.util.Locale; -import java.util.Stack; public abstract class BaseStatusBar extends SystemUI implements CommandQueue.Callbacks { @@ -157,8 +146,7 @@ public abstract class BaseStatusBar extends SystemUI implements // public mode, private notifications, etc private boolean mLockscreenPublicMode = false; private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); - private Context mLightThemeContext; - private ImageUtils mImageUtils = new ImageUtils(); + private LegacyNotificationUtil mLegacyNotificationUtil = LegacyNotificationUtil.getInstance(); private UserManager mUserManager; @@ -296,8 +284,6 @@ public abstract class BaseStatusBar extends SystemUI implements true, mLockscreenSettingsObserver, UserHandle.USER_ALL); - mLightThemeContext = new RemoteViewsThemeContextWrapper(mContext, - android.R.style.Theme_Holo_Light); mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); @@ -458,158 +444,6 @@ public abstract class BaseStatusBar extends SystemUI implements } } - private void processLegacyHoloNotification(StatusBarNotification sbn, View content) { - - // TODO: Also skip processing if it is a holo-style notification. - // If the notification is custom, we can't process it. - if (!sbn.getNotification().extras.getBoolean(Notification.EXTRA_BUILDER_REMOTE_VIEWS)) { - return; - } - - processLegacyHoloLargeIcon(content); - processLegacyHoloActions(content); - processLegacyNotificationIcon(content); - processLegacyTextViews(content); - } - - /** - * @return the context to be used for the inflation of the specified {@code sbn}; this is - * dependent whether the notification is quantum-style or holo-style - */ - private Context getInflationContext(StatusBarNotification sbn) { - - // TODO: Adjust this logic when we change the theme of the status bar windows. - if (sbn.getNotification().extras.getBoolean(Notification.EXTRA_BUILDER_REMOTE_VIEWS)) { - return mLightThemeContext; - } else { - return mContext; - } - } - - private void processLegacyNotificationIcon(View content) { - View v = content.findViewById(com.android.internal.R.id.right_icon); - if (v != null & v instanceof ImageView) { - ImageView iv = (ImageView) v; - Drawable d = iv.getDrawable(); - if (isMonochrome(d)) { - d.mutate(); - d.setColorFilter(mLightThemeContext.getResources().getColor( - R.color.notification_action_legacy_color_filter), PorterDuff.Mode.MULTIPLY); - } - } - } - - private void processLegacyHoloLargeIcon(View content) { - View v = content.findViewById(com.android.internal.R.id.icon); - if (v != null & v instanceof ImageView) { - ImageView iv = (ImageView) v; - if (isMonochrome(iv.getDrawable())) { - iv.setBackground(mLightThemeContext.getResources().getDrawable( - R.drawable.notification_icon_legacy_bg_inset)); - } - } - } - - private boolean isMonochrome(Drawable d) { - if (d == null) { - return false; - } else if (d instanceof BitmapDrawable) { - BitmapDrawable bd = (BitmapDrawable) d; - return bd.getBitmap() != null && mImageUtils.isGrayscale(bd.getBitmap()); - } else if (d instanceof AnimationDrawable) { - AnimationDrawable ad = (AnimationDrawable) d; - int count = ad.getNumberOfFrames(); - return count > 0 && isMonochrome(ad.getFrame(0)); - } else { - return false; - } - } - - private void processLegacyHoloActions(View content) { - View v = content.findViewById(com.android.internal.R.id.actions); - if (v != null & v instanceof ViewGroup) { - ViewGroup vg = (ViewGroup) v; - int childCount = vg.getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = vg.getChildAt(i); - if (child instanceof Button) { - Button button = (Button) child; - Drawable[] compoundDrawables = button.getCompoundDrawablesRelative(); - if (isMonochrome(compoundDrawables[0])) { - Drawable d = compoundDrawables[0]; - d.mutate(); - d.setColorFilter(mLightThemeContext.getResources().getColor( - R.color.notification_action_legacy_color_filter), - PorterDuff.Mode.MULTIPLY); - } - } - } - } - } - - private void processLegacyTextViews(View content) { - Stack<View> viewStack = new Stack<View>(); - viewStack.push(content); - while(!viewStack.isEmpty()) { - View current = viewStack.pop(); - if(current instanceof ViewGroup){ - ViewGroup currentGroup = (ViewGroup) current; - int numChildren = currentGroup.getChildCount(); - for(int i=0;i<numChildren;i++){ - viewStack.push(currentGroup.getChildAt(i)); - } - } - if (current instanceof TextView) { - processLegacyTextView((TextView) current); - } - } - } - - private void processLegacyTextView(TextView textView) { - if (textView.getText() instanceof Spanned) { - Spanned ss = (Spanned) textView.getText(); - 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)); - } - textView.setText(builder); - } - } - - 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 (mImageUtils.isGrayscale(colors[i])) { - 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)); - } - private void startApplicationDetailsActivity(String packageName) { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", packageName, null)); @@ -947,10 +781,10 @@ public abstract class BaseStatusBar extends SystemUI implements View contentViewLocal = null; View bigContentViewLocal = null; try { - contentViewLocal = contentView.apply(getInflationContext(sbn), expanded, + contentViewLocal = contentView.apply(mContext, expanded, mOnClickHandler); if (bigContentView != null) { - bigContentViewLocal = bigContentView.apply(getInflationContext(sbn), expanded, + bigContentViewLocal = bigContentView.apply(mContext, expanded, mOnClickHandler); } } @@ -983,8 +817,8 @@ public abstract class BaseStatusBar extends SystemUI implements View publicViewLocal = null; if (publicNotification != null) { try { - publicViewLocal = publicNotification.contentView.apply(getInflationContext(sbn), - expandedPublic, mOnClickHandler); + publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic, + mOnClickHandler); if (publicViewLocal != null) { publicViewLocal.setIsRootNamespace(true); @@ -1005,7 +839,8 @@ public abstract class BaseStatusBar extends SystemUI implements if (publicViewLocal == null) { // Add a basic notification template publicViewLocal = LayoutInflater.from(mContext).inflate( - com.android.internal.R.layout.notification_template_base, expandedPublic, true); + com.android.internal.R.layout.notification_template_quantum_base, + expandedPublic, true); final TextView title = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.title); try { @@ -1024,7 +859,12 @@ public abstract class BaseStatusBar extends SystemUI implements entry.notification.getNotification().number, entry.notification.getNotification().tickerText); - icon.setImageDrawable(StatusBarIconView.getIcon(mContext, ic)); + Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic); + icon.setImageDrawable(iconDrawable); + if (mLegacyNotificationUtil.isGrayscale(iconDrawable)) { + icon.setBackgroundResource( + com.android.internal.R.drawable.notification_icon_legacy_bg_inset); + } final TextView text = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.text); text.setText("Unlock your device to see this notification."); @@ -1035,13 +875,6 @@ public abstract class BaseStatusBar extends SystemUI implements row.setDrawingCacheEnabled(true); applyLegacyRowBackground(sbn, content); - processLegacyHoloNotification(sbn, contentViewLocal); - if (bigContentViewLocal != null) { - processLegacyHoloNotification(sbn, bigContentViewLocal); - } - if (publicViewLocal != null) { - processLegacyHoloNotification(sbn, publicViewLocal); - } if (MULTIUSER_DEBUG) { TextView debug = (TextView) row.findViewById(R.id.debug_info); @@ -1459,17 +1292,13 @@ public abstract class BaseStatusBar extends SystemUI implements : null; // Reapply the RemoteViews - contentView.reapply(getInflationContext(notification), entry.expanded, mOnClickHandler); - processLegacyHoloNotification(notification, entry.expanded); + contentView.reapply(mContext, entry.expanded, mOnClickHandler); if (bigContentView != null && entry.getBigContentView() != null) { - bigContentView.reapply(getInflationContext(notification), entry.getBigContentView(), + bigContentView.reapply(mContext, entry.getBigContentView(), mOnClickHandler); - processLegacyHoloNotification(notification, entry.getBigContentView()); } if (publicContentView != null && entry.getPublicContentView() != null) { - publicContentView.reapply(getInflationContext(notification), - entry.getPublicContentView(), mOnClickHandler); - processLegacyHoloNotification(notification, entry.getPublicContentView()); + publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler); } // update the contentIntent final PendingIntent contentIntent = notification.getNotification().contentIntent; @@ -1540,35 +1369,4 @@ public abstract class BaseStatusBar extends SystemUI implements } mContext.unregisterReceiver(mBroadcastReceiver); } - - /** - * A custom context theme wrapper that applies a platform theme to a created package context. - * This is useful if you want to inflate {@link RemoteViews} with a custom theme (normally, the - * theme used there is the default platform theme). - */ - private static class RemoteViewsThemeContextWrapper extends ContextThemeWrapper { - - private int mThemeRes; - - private RemoteViewsThemeContextWrapper(Context base, int themeres) { - super(base, themeres); - mThemeRes = themeres; - } - - @Override - public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) - throws NameNotFoundException { - Context c = super.createPackageContextAsUser(packageName, flags, user); - c.setTheme(mThemeRes); - return c; - } - - @Override - public Context createPackageContext(String packageName, int flags) - throws NameNotFoundException { - Context c = super.createPackageContext(packageName, flags); - c.setTheme(mThemeRes); - return c; - } - } } |