Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 73581eff authored by Jorim Jaggi's avatar Jorim Jaggi Committed by Android (Google) Code Review
Browse files

Merge "Move legacy notification processing to Notification.Builder"

parents afd8b17f 5c2d8467
Loading
Loading
Loading
Loading
+94 −24
Original line number Diff line number Diff line
@@ -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;
@@ -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.
@@ -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,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
@@ -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++;
            }
+94 −0
Original line number Diff line number Diff line
@@ -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.
     *
@@ -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");
                    }
@@ -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
     *
+4 −2
Original line number Diff line number Diff line
@@ -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;
+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));
    }
}
Loading