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

Commit f82dcec6 authored by Jeff DeCew's avatar Jeff DeCew
Browse files

Clean up MessagingLayout notifications

* Collapsed state now appears with the 'headerless' style used for all other collapsed notification templates (except conversations / calls which have their own style).
* Expanded state contnues to use the 'header' treatment of all our big states, but messaging faces will now appear in the left icon gutter, and text will always align with other styles.
* Image messages show up in the 'right icon' location when collapsed, but will not be moved to the 'left icon' when grouped.
* 1-1 conversations will highlight the other user's name as the title, in the collapsed state.

Fixes: 173204301
Test: post and inspect lots of messaging notifications
Change-Id: I5a56d11ea98be1a22caca0d85040ab3fc375216f
parent 33b9b855
Loading
Loading
Loading
Loading
+53 −30
Original line number Diff line number Diff line
@@ -5110,6 +5110,7 @@ public class Notification implements Parcelable
                TemplateBindResult result) {
            p.headerless(resId == getBaseLayoutResource()
                    || resId == getHeadsUpBaseLayoutResource()
                    || resId == getMessagingLayoutResource()
                    || resId == R.layout.notification_template_material_media);
            RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);

@@ -6635,6 +6636,10 @@ public class Notification implements Parcelable
            return R.layout.notification_template_material_messaging;
        }

        private int getBigMessagingLayoutResource() {
            return R.layout.notification_template_material_big_messaging;
        }

        private int getConversationLayoutResource() {
            return R.layout.notification_template_material_conversation;
        }
@@ -8145,12 +8150,14 @@ public class Notification implements Parcelable
         */
        @Override
        public RemoteViews makeContentView(boolean increasedHeight) {
            // All messaging templates contain the actions
            ArrayList<Action> originalActions = mBuilder.mActions;
            try {
                mBuilder.mActions = new ArrayList<>();
            RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */,
                    false /* hideLargeIcon */);
                return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL);
            } finally {
                mBuilder.mActions = originalActions;
            return remoteViews;
            }
        }

        /**
@@ -8236,18 +8243,24 @@ public class Notification implements Parcelable
         */
        @Override
        public RemoteViews makeBigContentView() {
            return makeMessagingView(false /* isCollapsed */, true /* hideLargeIcon */);
            return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG);
        }

        /**
         * Create a messaging layout.
         *
         * @param isCollapsed Should this use the collapsed layout
         * @param hideRightIcons Should the reply affordance be shown at the end of the notification
         * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG,
         *                VIEW_TYPE_HEADS_UP
         * @return the created remoteView.
         */
        @NonNull
        private RemoteViews makeMessagingView(boolean isCollapsed, boolean hideRightIcons) {
        private RemoteViews makeMessagingView(int viewType) {
            boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG;
            boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
            boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
            boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
            boolean isHeaderless = !isConversationLayout && isCollapsed;

            CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
                    ? super.mBigContentTitle
                    : mConversationTitle;
@@ -8265,23 +8278,26 @@ public class Notification implements Parcelable
            } else {
                isOneToOne = !isGroupConversation();
            }
            boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
            boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
            if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
                conversationTitle = getOtherPersonName();
            }

            Icon largeIcon = mBuilder.mN.mLargeIcon;
            TemplateBindResult bindResult = new TemplateBindResult();
            StandardTemplateParams p = mBuilder.mParams.reset()
                    .viewType(isCollapsed ? StandardTemplateParams.VIEW_TYPE_NORMAL
                            : StandardTemplateParams.VIEW_TYPE_BIG)
                    .viewType(viewType)
                    .highlightExpander(isConversationLayout)
                    .hideProgress(true)
                    .title(conversationTitle)
                    .title(isHeaderless ? conversationTitle : null)
                    .text(null)
                    .hideLargeIcon(hideRightIcons || isOneToOne)
                    .headerTextSecondary(conversationTitle);
                    .headerTextSecondary(isHeaderless ? null : conversationTitle);
            RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
                    isConversationLayout
                            ? mBuilder.getConversationLayoutResource()
                            : mBuilder.getMessagingLayoutResource(),
                            : isCollapsed
                                    ? mBuilder.getMessagingLayoutResource()
                                    : mBuilder.getBigMessagingLayoutResource(),
                    p,
                    bindResult);
            if (isConversationLayout) {
@@ -8290,14 +8306,6 @@ public class Notification implements Parcelable
            }

            addExtras(mBuilder.mN.extras);
            if (!isConversationLayout) {
                // also update the end margin if there is an image
                // NOTE: This template doesn't support moving this icon to the left, so we don't
                // need to fully apply the MarginSet
                contentView.setViewLayoutMargin(R.id.notification_messaging, RemoteViews.MARGIN_END,
                        bindResult.mHeadingExtraMarginSet.getDpValue(),
                        TypedValue.COMPLEX_UNIT_DIP);
            }
            contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
                    mBuilder.getSmallIconColor(p));
            contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor",
@@ -8323,6 +8331,10 @@ public class Notification implements Parcelable
                contentView.setBoolean(R.id.status_bar_latest_event_content,
                        "setIsImportantConversation", isImportantConversation);
            }
            if (isHeaderless) {
                // Collapsed legacy messaging style has a 1-line limit.
                contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
            }
            contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
                    largeIcon);
            contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
@@ -8330,6 +8342,22 @@ public class Notification implements Parcelable
            return contentView;
        }

        private CharSequence getKey(Person person) {
            return person == null ? null
                    : person.getKey() == null ? person.getName() : person.getKey();
        }

        private CharSequence getOtherPersonName() {
            CharSequence userKey = getKey(mUser);
            for (int i = mMessages.size() - 1; i >= 0; i--) {
                Person sender = mMessages.get(i).getSenderPerson();
                if (sender != null && !TextUtils.equals(userKey, getKey(sender))) {
                    return sender.getName();
                }
            }
            return null;
        }

        private boolean hasOnlyWhiteSpaceSenders() {
            for (int i = 0; i < mMessages.size(); i++) {
                Message m = mMessages.get(i);
@@ -8364,12 +8392,7 @@ public class Notification implements Parcelable
         */
        @Override
        public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
            RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */,
                    true /* hideLargeIcon */);
            if (mConversationType == CONVERSATION_TYPE_LEGACY) {
                remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
            }
            return remoteViews;
            return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
        }

        public static final class Message {
+3 −36
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ import com.android.internal.R;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

@@ -530,13 +531,7 @@ public class ConversationLayout extends FrameLayout
        mConversationText.setText(conversationText);
        // Update if the groups can hide the sender if they are first (applies to 1:1 conversations)
        // This needs to happen after all of the above o update all of the groups
        for (int i = mGroups.size() - 1; i >= 0; i--) {
            MessagingGroup messagingGroup = mGroups.get(i);
            CharSequence messageSender = messagingGroup.getSenderName();
            boolean canHide = mIsOneToOne
                    && TextUtils.equals(conversationText, messageSender);
            messagingGroup.setCanHideSenderIfFirst(canHide);
        }
        mPeopleHelper.maybeHideFirstSenderName(mGroups, mIsOneToOne, conversationText);
        updateAppName();
        updateIconPositionAndSize();
        updateImageMessages();
@@ -779,35 +774,7 @@ public class ConversationLayout extends FrameLayout

    private void updateTitleAndNamesDisplay() {
        // Map of unique names to their prefix
        ArrayMap<CharSequence, String> uniqueNames = 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();
            if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)) {
                continue;
            }
            if (!uniqueNames.containsKey(senderName)) {
                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(charPrefix);
                    if (existingName != null) {
                        uniqueNames.put(existingName, mPeopleHelper.findNameSplit(existingName));
                        uniqueCharacters.put(charPrefix, null);
                    }
                    uniqueNames.put(senderName, mPeopleHelper.findNameSplit(senderName));
                } else {
                    uniqueNames.put(senderName, charPrefix);
                    uniqueCharacters.put(charPrefix, senderName);
                }
            }
        }
        Map<CharSequence, String> uniqueNames = mPeopleHelper.mapUniqueNamesToPrefix(mGroups);

        // Now that we have the correct symbols, let's look what we have cached
        ArrayMap<CharSequence, Icon> cachedAvatars = new ArrayMap<>();
+28 −11
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.annotation.StyleRes;
import android.app.Person;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
@@ -109,7 +110,10 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
    private boolean mIsInConversation = true;
    private ViewGroup mMessagingIconContainer;
    private int mConversationContentStart;
    private int mNonConversationMarginEnd;
    private int mNonConversationContentStart;
    private int mNonConversationPaddingStart;
    private int mConversationAvatarSize;
    private int mNonConversationAvatarSize;
    private int mNotificationTextMarginTop;

    public MessagingGroup(@NonNull Context context) {
@@ -141,16 +145,21 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
        mMessagingIconContainer = findViewById(R.id.message_icon_container);
        mContentContainer = findViewById(R.id.messaging_group_content_container);
        mSendingSpinnerContainer = findViewById(R.id.messaging_group_sending_progress_container);
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        Resources res = getResources();
        DisplayMetrics displayMetrics = res.getDisplayMetrics();
        mDisplaySize.x = displayMetrics.widthPixels;
        mDisplaySize.y = displayMetrics.heightPixels;
        mSenderTextPaddingSingleLine = getResources().getDimensionPixelSize(
        mSenderTextPaddingSingleLine = res.getDimensionPixelSize(
                R.dimen.messaging_group_singleline_sender_padding_end);
        mConversationContentStart = getResources().getDimensionPixelSize(
                R.dimen.conversation_content_start);
        mNonConversationMarginEnd = getResources().getDimensionPixelSize(
                R.dimen.messaging_layout_margin_end);
        mNotificationTextMarginTop = getResources().getDimensionPixelSize(
        mConversationContentStart = res.getDimensionPixelSize(R.dimen.conversation_content_start);
        mNonConversationContentStart = res.getDimensionPixelSize(
                R.dimen.notification_content_margin_start);
        mNonConversationPaddingStart = res.getDimensionPixelSize(
                R.dimen.messaging_layout_icon_padding_start);
        mConversationAvatarSize = res.getDimensionPixelSize(R.dimen.messaging_avatar_size);
        mNonConversationAvatarSize = res.getDimensionPixelSize(
                R.dimen.notification_icon_circle_size);
        mNotificationTextMarginTop = res.getDimensionPixelSize(
                R.dimen.notification_text_margin_top);
    }

@@ -696,10 +705,18 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
            mIsInConversation = isInConversation;
            MarginLayoutParams layoutParams =
                    (MarginLayoutParams) mMessagingIconContainer.getLayoutParams();
            layoutParams.width = mIsInConversation ? mConversationContentStart
                    : ViewPager.LayoutParams.WRAP_CONTENT;
            layoutParams.setMarginEnd(mIsInConversation ? 0 : mNonConversationMarginEnd);
            layoutParams.width = mIsInConversation
                    ? mConversationContentStart
                    : mNonConversationContentStart;
            mMessagingIconContainer.setLayoutParams(layoutParams);
            int imagePaddingStart = isInConversation ? 0 : mNonConversationPaddingStart;
            mMessagingIconContainer.setPaddingRelative(imagePaddingStart, 0, 0, 0);

            ViewGroup.LayoutParams avatarLayoutParams = mAvatarView.getLayoutParams();
            int size = mIsInConversation ? mConversationAvatarSize : mNonConversationAvatarSize;
            avatarLayoutParams.height = size;
            avatarLayoutParams.width = size;
            mAvatarView.setLayoutParams(avatarLayoutParams);
        }
    }

+65 −87

File changed.

Preview size limit exceeded, changes collapsed.

+58 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;

import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -28,12 +29,15 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.view.View;

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

import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
@@ -176,4 +180,58 @@ public class PeopleHelper {
        }
        return findNamePrefix(name, "");
    }

    /**
     * Creates a mapping of the unique sender names in the groups to the string 1- or 2-character
     * prefix strings for the names, which are extracted as the initials, and should be used for
     * generating the avatar.  Senders not requiring a generated avatar, or with an empty name are
     * omitted.
     */
    public Map<CharSequence, String> mapUniqueNamesToPrefix(List<MessagingGroup> groups) {
        // Map of unique names to their prefix
        ArrayMap<CharSequence, String> uniqueNames = 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 < groups.size(); i++) {
            MessagingGroup group = groups.get(i);
            CharSequence senderName = group.getSenderName();
            if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)) {
                continue;
            }
            if (!uniqueNames.containsKey(senderName)) {
                String charPrefix = 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(charPrefix);
                    if (existingName != null) {
                        uniqueNames.put(existingName, findNameSplit(existingName));
                        uniqueCharacters.put(charPrefix, null);
                    }
                    uniqueNames.put(senderName, findNameSplit(senderName));
                } else {
                    uniqueNames.put(senderName, charPrefix);
                    uniqueCharacters.put(charPrefix, senderName);
                }
            }
        }
        return uniqueNames;
    }

    /**
     * Update whether the groups can hide the sender if they are first
     * (happens only for 1:1 conversations where the given title matches the sender's name)
     */
    public void maybeHideFirstSenderName(@NonNull List<MessagingGroup> groups,
            boolean isOneToOne, @Nullable CharSequence conversationTitle) {
        for (int i = groups.size() - 1; i >= 0; i--) {
            MessagingGroup messagingGroup = groups.get(i);
            CharSequence messageSender = messagingGroup.getSenderName();
            boolean canHide = isOneToOne && TextUtils.equals(conversationTitle, messageSender);
            messagingGroup.setCanHideSenderIfFirst(canHide);
        }
    }
}
Loading