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

Commit 4db015d0 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge changes from topic "notif_call_style_sc" into sc-dev

* changes:
  Stop using ?attr/notificationHeaderTextAppearance
  Remove unused 'app_ops' from layout
  Implement Notification.CallStyle
parents f26c5a51 11a0b653
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -5555,15 +5555,19 @@ package android.app {
    field public static final int DEFAULT_LIGHTS = 4; // 0x4
    field public static final int DEFAULT_SOUND = 1; // 0x1
    field public static final int DEFAULT_VIBRATE = 2; // 0x2
    field public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
    field public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
    field public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
    field public static final String EXTRA_BIG_TEXT = "android.bigText";
    field public static final String EXTRA_CALL_PERSON = "android.callPerson";
    field public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
    field public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
    field public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
    field public static final String EXTRA_COLORIZED = "android.colorized";
    field public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
    field public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
    field public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
    field public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
    field public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
    field public static final String EXTRA_INFO_TEXT = "android.infoText";
    field public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
@@ -5595,6 +5599,8 @@ package android.app {
    field public static final String EXTRA_TEXT_LINES = "android.textLines";
    field public static final String EXTRA_TITLE = "android.title";
    field public static final String EXTRA_TITLE_BIG = "android.title.big";
    field public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
    field public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
    field public static final int FLAG_AUTO_CANCEL = 16; // 0x10
    field public static final int FLAG_BUBBLE = 4096; // 0x1000
    field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40
@@ -5843,6 +5849,14 @@ package android.app {
    method @NonNull public android.app.Notification.Builder setWhen(long);
  }
  public static class Notification.CallStyle extends android.app.Notification.Style {
    method @NonNull public static android.app.Notification.CallStyle forIncomingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent);
    method @NonNull public static android.app.Notification.CallStyle forOngoingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent);
    method @NonNull public static android.app.Notification.CallStyle forScreeningCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent);
    method @NonNull public android.app.Notification.CallStyle setVerificationIcon(@Nullable android.graphics.drawable.Icon);
    method @NonNull public android.app.Notification.CallStyle setVerificationText(@Nullable CharSequence);
  }
  public static final class Notification.CarExtender implements android.app.Notification.Extender {
    ctor public Notification.CarExtender();
    ctor public Notification.CarExtender(android.app.Notification);
+524 −19

File changed.

Preview size limit exceeded, changes collapsed.

+137 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.widget;

import android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
import android.app.Notification;
import android.app.Person;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
import android.widget.FrameLayout;
import android.widget.RemoteViews;
import android.widget.TextView;

import com.android.internal.R;

/**
 * A custom-built layout for the Notification.CallStyle.
 */
@RemoteViews.RemoteView
public class CallLayout extends FrameLayout {
    private final PeopleHelper mPeopleHelper = new PeopleHelper();

    private int mLayoutColor;
    private Icon mLargeIcon;
    private Person mUser;

    private CachingIconView mConversationIconView;
    private CachingIconView mIcon;
    private CachingIconView mConversationIconBadgeBg;
    private TextView mConversationText;

    public CallLayout(@NonNull Context context) {
        super(context);
    }

    public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs,
            @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs,
            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mPeopleHelper.init(getContext());
        mConversationText = findViewById(R.id.conversation_text);
        mConversationIconView = findViewById(R.id.conversation_icon);
        mIcon = findViewById(R.id.icon);
        mConversationIconBadgeBg = findViewById(R.id.conversation_icon_badge_bg);

        // When the small icon is gone, hide the rest of the badge
        mIcon.setOnForceHiddenChangedListener((forceHidden) -> {
            mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
        });
    }

    private void updateCallLayout() {
        CharSequence callerName = "";
        String symbol = "";
        Icon icon = null;
        if (mUser != null) {
            icon = mUser.getIcon();
            callerName = mUser.getName();
            symbol = mPeopleHelper.findNamePrefix(callerName, "");
        }
        if (icon == null) {
            icon = mLargeIcon;
        }
        if (icon == null) {
            icon = mPeopleHelper.createAvatarSymbol(callerName, symbol, mLayoutColor);
        }
        // TODO(b/179178086): crop/clip the icon to a circle?
        mConversationIconView.setImageIcon(icon);
        mConversationText.setText(callerName);
    }

    @RemotableViewMethod
    public void setLayoutColor(int color) {
        mLayoutColor = color;
    }

    /**
     * @param color the color of the notification background
     */
    @RemotableViewMethod
    public void setNotificationBackgroundColor(int color) {
        mConversationIconBadgeBg.setImageTintList(ColorStateList.valueOf(color));
    }

    @RemotableViewMethod
    public void setLargeIcon(Icon largeIcon) {
        mLargeIcon = largeIcon;
    }

    /**
     * Set the notification extras so that this layout has access
     */
    @RemotableViewMethod
    public void setData(Bundle extras) {
        setUser(extras.getParcelable(Notification.EXTRA_CALL_PERSON));
        updateCallLayout();
    }

    private void setUser(Person user) {
        mUser = user;
    }

}
+27 −110
Original line number Diff line number Diff line
@@ -18,8 +18,6 @@ package com.android.internal.widget;

import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL;
import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_INLINE;
import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN;
import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -34,10 +32,7 @@ import android.app.Person;
import android.app.RemoteInputHistoryItem;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
@@ -68,14 +63,12 @@ import android.widget.TextView;

import com.android.internal.R;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ContrastColorUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.regex.Pattern;

/**
 * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
@@ -85,16 +78,6 @@ import java.util.regex.Pattern;
public class ConversationLayout extends FrameLayout
        implements ImageMessageConsumer, IMessagingLayout {

    private static final float COLOR_SHIFT_AMOUNT = 60;
    /**
     *  Pattern for filter some ignorable characters.
     *  p{Z} for any kind of whitespace or invisible separator.
     *  p{C} for any kind of punctuation character.
     */
    private static final Pattern IGNORABLE_CHAR_PATTERN
            = Pattern.compile("[\\p{C}\\p{Z}]");
    private static final Pattern SPECIAL_CHAR_PATTERN
            = Pattern.compile ("[!@#$%&*()_+=|<>?{}\\[\\]~-]");
    private static final Consumer<MessagingMessage> REMOVE_MESSAGE
            = MessagingMessage::removeMessage;
    public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
@@ -106,6 +89,7 @@ public class ConversationLayout extends FrameLayout
    public static final int IMPORTANCE_ANIM_GROW_DURATION = 250;
    public static final int IMPORTANCE_ANIM_SHRINK_DURATION = 200;
    public static final int IMPORTANCE_ANIM_SHRINK_DELAY = 25;
    private final PeopleHelper mPeopleHelper = new PeopleHelper();
    private List<MessagingMessage> mMessages = new ArrayList<>();
    private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
    private MessagingLinearLayout mMessagingLinearLayout;
@@ -114,9 +98,6 @@ public class ConversationLayout extends FrameLayout
    private int mLayoutColor;
    private int mSenderTextColor;
    private int mMessageTextColor;
    private int mAvatarSize;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Paint mTextPaint = new Paint();
    private Icon mAvatarReplacement;
    private boolean mIsOneToOne;
    private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
@@ -196,6 +177,7 @@ public class ConversationLayout extends FrameLayout
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mPeopleHelper.init(getContext());
        mMessagingLinearLayout = findViewById(R.id.notification_messaging);
        mActions = findViewById(R.id.actions);
        mImageMessageContainer = findViewById(R.id.conversation_image_message_container);
@@ -205,9 +187,6 @@ public class ConversationLayout extends FrameLayout
        int size = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels);
        mMessagingClipRect = new Rect(0, 0, size, size);
        setMessagingClippingDisabled(false);
        mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setAntiAlias(true);
        mConversationIconView = findViewById(R.id.conversation_icon);
        mConversationIconContainer = findViewById(R.id.conversation_icon_container);
        mIcon = findViewById(R.id.icon);
@@ -250,15 +229,15 @@ public class ConversationLayout extends FrameLayout
        });
        // When the small icon is gone, hide the rest of the badge
        mIcon.setOnForceHiddenChangedListener((forceHidden) -> {
            animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
            animateViewForceHidden(mImportanceRingView, forceHidden);
            mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
            mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
        });

        // When the conversation icon is gone, hide the whole badge
        mConversationIconView.setOnForceHiddenChangedListener((forceHidden) -> {
            animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
            animateViewForceHidden(mImportanceRingView, forceHidden);
            animateViewForceHidden(mIcon, forceHidden);
            mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
            mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
            mPeopleHelper.animateViewForceHidden(mIcon, forceHidden);
        });
        mConversationText = findViewById(R.id.conversation_text);
        mExpandButtonContainer = findViewById(R.id.expand_button_container);
@@ -317,28 +296,6 @@ public class ConversationLayout extends FrameLayout
                R.dimen.notification_header_separating_margin);
    }

    private void animateViewForceHidden(CachingIconView view, boolean forceHidden) {
        boolean nowForceHidden = view.willBeForceHidden() || view.isForceHidden();
        if (forceHidden == nowForceHidden) {
            // We are either already forceHidden or will be
            return;
        }
        view.animate().cancel();
        view.setWillBeForceHidden(forceHidden);
        view.animate()
                .scaleX(forceHidden ? 0.5f : 1.0f)
                .scaleY(forceHidden ? 0.5f : 1.0f)
                .alpha(forceHidden ? 0.0f : 1.0f)
                .setInterpolator(forceHidden ? ALPHA_OUT : ALPHA_IN)
                .setDuration(160);
        if (view.getVisibility() != VISIBLE) {
            view.setForceHidden(forceHidden);
        } else {
            view.animate().withEndAction(() -> view.setForceHidden(forceHidden));
        }
        view.animate().start();
    }

    @RemotableViewMethod
    public void setAvatarReplacement(Icon icon) {
        mAvatarReplacement = icon;
@@ -561,7 +518,8 @@ public class ConversationLayout extends FrameLayout
                    if (mConversationIcon == null) {
                        Icon avatarIcon = messagingGroup.getAvatarIcon();
                        if (avatarIcon == null) {
                            avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor);
                            avatarIcon = mPeopleHelper.createAvatarSymbol(conversationText, "",
                                    mLayoutColor);
                        }
                        mConversationIcon = avatarIcon;
                    }
@@ -674,11 +632,11 @@ public class ConversationLayout extends FrameLayout
            }
        }
        if (lastIcon == null) {
            lastIcon = createAvatarSymbol(" ", "", mLayoutColor);
            lastIcon = mPeopleHelper.createAvatarSymbol(" ", "", mLayoutColor);
        }
        bottomView.setImageIcon(lastIcon);
        if (secondLastIcon == null) {
            secondLastIcon = createAvatarSymbol("", "", mLayoutColor);
            secondLastIcon = mPeopleHelper.createAvatarSymbol("", "", mLayoutColor);
        }
        topView.setImageIcon(secondLastIcon);
    }
@@ -838,8 +796,10 @@ public class ConversationLayout extends FrameLayout
    }

    private void updateTitleAndNamesDisplay() {
        // Map of unique names to their prefix
        ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
        ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
        // Map of single-character string prefix to the only name which uses it, or null if multiple
        ArrayMap<String, CharSequence> uniqueCharacters = new ArrayMap<>();
        for (int i = 0; i < mGroups.size(); i++) {
            MessagingGroup group = mGroups.get(i);
            CharSequence senderName = group.getSenderName();
@@ -847,22 +807,22 @@ public class ConversationLayout extends FrameLayout
                continue;
            }
            if (!uniqueNames.containsKey(senderName)) {
                // Only use visible characters to get uniqueNames
                String pureSenderName = IGNORABLE_CHAR_PATTERN
                        .matcher(senderName).replaceAll("" /* replacement */);
                char c = pureSenderName.charAt(0);
                if (uniqueCharacters.containsKey(c)) {
                String charPrefix = mPeopleHelper.findNamePrefix(senderName, null);
                if (charPrefix == null) {
                    continue;
                }
                if (uniqueCharacters.containsKey(charPrefix)) {
                    // this character was already used, lets make it more unique. We first need to
                    // resolve the existing character if it exists
                    CharSequence existingName = uniqueCharacters.get(c);
                    CharSequence existingName = uniqueCharacters.get(charPrefix);
                    if (existingName != null) {
                        uniqueNames.put(existingName, findNameSplit((String) existingName));
                        uniqueCharacters.put(c, null);
                        uniqueNames.put(existingName, mPeopleHelper.findNameSplit(existingName));
                        uniqueCharacters.put(charPrefix, null);
                    }
                    uniqueNames.put(senderName, findNameSplit((String) senderName));
                    uniqueNames.put(senderName, mPeopleHelper.findNameSplit(senderName));
                } else {
                    uniqueNames.put(senderName, Character.toString(c));
                    uniqueCharacters.put(c, pureSenderName);
                    uniqueNames.put(senderName, charPrefix);
                    uniqueCharacters.put(charPrefix, senderName);
                }
            }
        }
@@ -898,8 +858,8 @@ public class ConversationLayout extends FrameLayout
            } else {
                Icon cachedIcon = cachedAvatars.get(senderName);
                if (cachedIcon == null) {
                    cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
                            mLayoutColor);
                    cachedIcon = mPeopleHelper.createAvatarSymbol(senderName,
                            uniqueNames.get(senderName), mLayoutColor);
                    cachedAvatars.put(senderName, cachedIcon);
                }
                group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
@@ -908,49 +868,6 @@ public class ConversationLayout extends FrameLayout
        }
    }

    private Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
        if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol) ||
                SPECIAL_CHAR_PATTERN.matcher(symbol).find()) {
            Icon avatarIcon = Icon.createWithResource(getContext(),
                    R.drawable.messaging_user);
            avatarIcon.setTint(findColor(senderName, layoutColor));
            return avatarIcon;
        } else {
            Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            float radius = mAvatarSize / 2.0f;
            int color = findColor(senderName, layoutColor);
            mPaint.setColor(color);
            canvas.drawCircle(radius, radius, radius, mPaint);
            boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
            mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
            mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f);
            int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
            canvas.drawText(symbol, radius, yPos, mTextPaint);
            return Icon.createWithBitmap(bitmap);
        }
    }

    private int findColor(CharSequence senderName, int layoutColor) {
        double luminance = ContrastColorUtil.calculateLuminance(layoutColor);
        float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;

        // we need to offset the range if the luminance is too close to the borders
        shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
        shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
        return ContrastColorUtil.getShiftedColor(layoutColor,
                (int) (shift * COLOR_SHIFT_AMOUNT));
    }

    private String findNameSplit(String existingName) {
        String[] split = existingName.split(" ");
        if (split.length > 1) {
            return Character.toString(split[0].charAt(0))
                    + Character.toString(split[1].charAt(0));
        }
        return existingName.substring(0, 1);
    }

    @RemotableViewMethod
    public void setLayoutColor(int color) {
        mLayoutColor = color;
+62 −1
Original line number Diff line number Diff line
@@ -16,17 +16,24 @@

package com.android.internal.widget;

import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.BlendMode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableWrapper;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RemoteViews;

import com.android.internal.R;

/**
 * A button implementation for the emphasized notification style.
 *
@@ -37,6 +44,7 @@ public class EmphasizedNotificationButton extends Button {
    private final RippleDrawable mRipple;
    private final int mStrokeWidth;
    private final int mStrokeColor;
    private boolean mPriority;

    public EmphasizedNotificationButton(Context context) {
        this(context, null);
@@ -80,4 +88,57 @@ public class EmphasizedNotificationButton extends Button {
        inner.setStroke(hasStroke ? mStrokeWidth : 0, mStrokeColor);
        invalidate();
    }

    /**
     * Sets an image icon which will have its size constrained and will be set to the same color as
     * the text. Must be called after {@link #setTextColor(int)} for the latter to work.
     */
    @RemotableViewMethod(asyncImpl = "setImageIconAsync")
    public void setImageIcon(@Nullable Icon icon) {
        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
        setImageDrawable(drawable);
    }

    /**
     * @hide
     */
    @RemotableViewMethod
    public Runnable setImageIconAsync(@Nullable Icon icon) {
        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
        return () -> setImageDrawable(drawable);
    }

    private void setImageDrawable(Drawable drawable) {
        if (drawable != null) {
            drawable.mutate();
            drawable.setTintList(getTextColors());
            drawable.setTintBlendMode(BlendMode.SRC_IN);
            int iconSize = mContext.getResources().getDimensionPixelSize(
                    R.dimen.notification_actions_icon_drawable_size);
            drawable.setBounds(0, 0, iconSize, iconSize);
        }
        setCompoundDrawablesRelative(drawable, null, null, null);
    }

    /**
     * Changes the LayoutParams.width to WRAP_CONTENT, with the argument representing if this view
     * is a priority over its peers (which affects weight).
     */
    @RemotableViewMethod
    public void setWrapModePriority(boolean priority) {
        mPriority = priority;
        ViewGroup.LayoutParams layoutParams = getLayoutParams();
        layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
        if (layoutParams instanceof LinearLayout.LayoutParams) {
            ((LinearLayout.LayoutParams) layoutParams).weight = 0;
        }
        setLayoutParams(layoutParams);
    }

    /**
     * Sizing this button is a priority compared with its peers.
     */
    public boolean isPriority() {
        return mPriority;
    }
}
Loading