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

Commit 88188f2d authored by Selim Cinek's avatar Selim Cinek
Browse files

Redesigned the messaging style

The layout now looks much more recognizable
as a messaging template and enables us to
prepare for more useful functionality.

Test: Send messages and observe display
Bug: 63708826
Change-Id: I896b3692a1e84976e8fd37cf37611ddb1d358fb9
parent c0dada07
Loading
Loading
Loading
Loading
+108 −108
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -3900,7 +3901,7 @@ public class Notification implements Parcelable
            final Bundle ex = mN.extras;
            updateBackgroundColor(contentView);
            bindNotificationHeader(contentView, p.ambient);
            bindLargeIcon(contentView);
            bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
            boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
            if (p.title != null) {
                contentView.setViewVisibility(R.id.title, View.VISIBLE);
@@ -4110,11 +4111,13 @@ public class Notification implements Parcelable
            }
        }

        private void bindLargeIcon(RemoteViews contentView) {
        private void bindLargeIcon(RemoteViews contentView, boolean hideLargeIcon,
                boolean alwaysShowReply) {
            if (mN.mLargeIcon == null && mN.largeIcon != null) {
                mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
            }
            if (mN.mLargeIcon != null) {
            boolean showLargeIcon = mN.mLargeIcon != null && !hideLargeIcon;
            if (showLargeIcon) {
                contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
                contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
                processLargeLegacyIcon(mN.mLargeIcon, contentView);
@@ -4122,32 +4125,45 @@ public class Notification implements Parcelable
                contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin);
                contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin);
                contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin);
            }
            // Bind the reply action
            Action action = findReplyAction();
                contentView.setViewVisibility(R.id.reply_icon_action, action != null
                        ? View.VISIBLE
                        : View.GONE);

                if (action != null) {
            boolean actionVisible = action != null && (showLargeIcon || alwaysShowReply);
            int replyId = showLargeIcon ? R.id.reply_icon_action : R.id.right_icon;
            if (actionVisible) {
                // We're only showing the icon as big if we're hiding the large icon
                int contrastColor = resolveContrastColor();
                int iconColor;
                if (showLargeIcon) {
                    contentView.setDrawableTint(R.id.reply_icon_action,
                            true /* targetBackground */,
                            contrastColor, PorterDuff.Mode.SRC_ATOP);
                    int iconColor = NotificationColorUtil.isColorLight(contrastColor)
                            ? Color.BLACK : Color.WHITE;
                    contentView.setDrawableTint(R.id.reply_icon_action,
                            false /* targetBackground */,
                            iconColor, PorterDuff.Mode.SRC_ATOP);
                    contentView.setOnClickPendingIntent(R.id.right_icon,
                            action.actionIntent);
                    contentView.setOnClickPendingIntent(R.id.reply_icon_action,
                            action.actionIntent);
                    contentView.setRemoteInputs(R.id.right_icon, action.mRemoteInputs);
                    contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);

                    iconColor = NotificationColorUtil.isColorLight(contrastColor)
                            ? Color.BLACK : Color.WHITE;
                } else {
                    contentView.setImageViewResource(R.id.right_icon,
                            R.drawable.ic_reply_notification_large);
                    contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
                    iconColor = contrastColor;
                }
                contentView.setDrawableTint(replyId,
                        false /* targetBackground */,
                        iconColor,
                        PorterDuff.Mode.SRC_ATOP);
                contentView.setOnClickPendingIntent(replyId,
                        action.actionIntent);
                contentView.setRemoteInputs(replyId, action.mRemoteInputs);
            } else {
                contentView.setRemoteInputs(R.id.right_icon, null);
            }
            contentView.setViewVisibility(R.id.right_icon_container, mN.mLargeIcon != null
            contentView.setViewVisibility(R.id.reply_icon_action, actionVisible && showLargeIcon
                    ? View.VISIBLE
                    : View.GONE);
            contentView.setViewVisibility(R.id.right_icon_container, actionVisible || showLargeIcon
                    ? View.VISIBLE
                    : View.GONE);
        }
@@ -6055,19 +6071,13 @@ public class Notification implements Parcelable
        protected void restoreFromExtras(Bundle extras) {
            super.restoreFromExtras(extras);

            mMessages.clear();
            mHistoricMessages.clear();
            mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
            mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
            Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
            if (messages != null && messages instanceof Parcelable[]) {
            mMessages = Message.getMessagesFromBundleArray(messages);
            }
            Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
            if (histMessages != null && histMessages instanceof Parcelable[]) {
            mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
        }
        }

        /**
         * @hide
@@ -6096,16 +6106,25 @@ public class Notification implements Parcelable
        }

        private Message findLatestIncomingMessage() {
            for (int i = mMessages.size() - 1; i >= 0; i--) {
                Message m = mMessages.get(i);
            return findLatestIncomingMessage(mMessages);
        }

        /**
         * @hide
         */
        @Nullable
        public static Message findLatestIncomingMessage(
                List<Message> messages) {
            for (int i = messages.size() - 1; i >= 0; i--) {
                Message m = messages.get(i);
                // Incoming messages have a non-empty sender.
                if (!TextUtils.isEmpty(m.mSender)) {
                    return m;
                }
            }
            if (!mMessages.isEmpty()) {
            if (!messages.isEmpty()) {
                // No incoming messages, fall back to outgoing message
                return mMessages.get(mMessages.size() - 1);
                return messages.get(messages.size() - 1);
            }
            return null;
        }
@@ -6115,93 +6134,55 @@ public class Notification implements Parcelable
         */
        @Override
        public RemoteViews makeBigContentView() {
            CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle)
            CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
                    ? super.mBigContentTitle
                    : mConversationTitle;
            boolean hasTitle = !TextUtils.isEmpty(title);

            if (mMessages.size() == 1) {
                // Special case for a single message: Use the big text style
                // so the collapsed and expanded versions match nicely.
                CharSequence bigTitle;
                CharSequence text;
                if (hasTitle) {
                    bigTitle = title;
                    text = makeMessageLine(mMessages.get(0), mBuilder);
                } else {
                    bigTitle = mMessages.get(0).mSender;
                    text = mMessages.get(0).mText;
                }
                RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
                        mBuilder.getBigTextLayoutResource(),
                        mBuilder.mParams.reset().hasProgress(false).title(bigTitle).text(null));
                BigTextStyle.applyBigTextContentView(mBuilder, contentView, text);
                return contentView;
            boolean isOneToOne = TextUtils.isEmpty(conversationTitle);
            if (isOneToOne) {
                // Let's add the conversationTitle in case we didn't have one before and all
                // messages are from the same sender
                conversationTitle = createConversationTitleFromMessages();
            }

            boolean hasTitle = !TextUtils.isEmpty(conversationTitle);
            RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
                    mBuilder.getMessagingLayoutResource(),
                    mBuilder.mParams.reset().hasProgress(false).title(title).text(null));

            int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
                    R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};

            // Make sure all rows are gone in case we reuse a view.
            for (int rowId : rowIds) {
                contentView.setViewVisibility(rowId, View.GONE);
            }

            int i=0;
                    mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
                            .hideLargeIcon(isOneToOne).alwaysShowReply(true));
            contentView.setViewLayoutMarginBottomDimen(R.id.line1,
                    hasTitle ? R.dimen.notification_messaging_spacing : 0);
            contentView.setInt(R.id.notification_messaging, "setNumIndentLines",
                    !mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2));

            int contractedChildId = View.NO_ID;
            Message contractedMessage = findLatestIncomingMessage();
            int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
                    - (rowIds.length - mMessages.size()));
            while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
                Message m = mHistoricMessages.get(firstHistoricMessage + i);
                int rowId = rowIds[i];

                contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));

                if (contractedMessage == m) {
                    contractedChildId = rowId;
            addExtras(mBuilder.mN.extras);
            contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
                    mBuilder.resolveContrastColor());
            contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
                    mBuilder.mN.mLargeIcon);
            contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
                    isOneToOne);
            contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
                    mBuilder.mN.extras);
            return contentView;
        }

                i++;
        private CharSequence createConversationTitleFromMessages() {
            ArraySet<CharSequence> names = new ArraySet<>();
            for (int i = 0; i < mMessages.size(); i++) {
                Message m = mMessages.get(i);
                CharSequence sender = m.getSender();
                if (sender != null) {
                    names.add(sender);
                }

            int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
            while (firstMessage + i < mMessages.size() && i < rowIds.length) {
                Message m = mMessages.get(firstMessage + i);
                int rowId = rowIds[i];

                contentView.setViewVisibility(rowId, View.VISIBLE);
                contentView.setTextViewText(rowId, mBuilder.processTextSpans(
                        makeMessageLine(m, mBuilder)));
                mBuilder.setTextViewColorSecondary(contentView, rowId);

                if (contractedMessage == m) {
                    contractedChildId = rowId;
            }

                i++;
            SpannableStringBuilder title = new SpannableStringBuilder();
            int size = names.size();
            for (int i = 0; i < size; i++) {
                CharSequence name = names.valueAt(i);
                if (!TextUtils.isEmpty(title)) {
                    title.append(", ");
                }
            // Clear the remaining views for reapply. Ensures that historic message views can
            // reliably be identified as being GONE and having non-null text.
            while (i < rowIds.length) {
                int rowId = rowIds[i];
                contentView.setTextViewText(rowId, null);
                i++;
                title.append(BidiFormatter.getInstance().unicodeWrap(name));
            }

            // Record this here to allow transformation between the contracted and expanded views.
            contentView.setInt(R.id.notification_messaging, "setContractedChildId",
                    contractedChildId);
            return contentView;
            return title;
        }

        private CharSequence makeMessageLine(Message m, Builder builder) {
@@ -6394,7 +6375,15 @@ public class Notification implements Parcelable
                return bundles;
            }

            static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
            /**
             * @return A list of messages read from the bundles.
             *
             * @hide
             */
            public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
                if (bundles == null) {
                    return new ArrayList<>();
                }
                List<Message> messages = new ArrayList<>(bundles.length);
                for (int i = 0; i < bundles.length; i++) {
                    if (bundles[i] instanceof Bundle) {
@@ -8487,6 +8476,8 @@ public class Notification implements Parcelable
        boolean ambient = false;
        CharSequence title;
        CharSequence text;
        boolean hideLargeIcon;
        public boolean alwaysShowReply;

        final StandardTemplateParams reset() {
            hasProgress = true;
@@ -8511,6 +8502,16 @@ public class Notification implements Parcelable
            return this;
        }

        final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) {
            this.alwaysShowReply = alwaysShowReply;
            return this;
        }

        final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
            this.hideLargeIcon = hideLargeIcon;
            return this;
        }

        final StandardTemplateParams ambient(boolean ambient) {
            Preconditions.checkState(title == null && text == null, "must set ambient before text");
            this.ambient = ambient;
@@ -8527,7 +8528,6 @@ public class Notification implements Parcelable
                text = extras.getCharSequence(EXTRA_TEXT);
            }
            this.text = b.processLegacyText(text, ambient);

            return this;
        }
    }
+0 −4
Original line number Diff line number Diff line
@@ -176,8 +176,4 @@ public class ImageFloatingTextView extends TextView {
        }
        return false;
    }

    public int getLayoutHeight() {
        return getLayout().getHeight();
    }
}
+241 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.content.Context;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.Pools;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RemoteViews;

import com.android.internal.R;
import com.android.internal.util.NotificationColorUtil;

import java.util.List;

/**
 * A message of a {@link MessagingLayout}.
 */
@RemoteViews.RemoteView
public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {

    private static Pools.SimplePool<MessagingGroup> sInstancePool
            = new Pools.SynchronizedPool<>(10);
    private MessagingLinearLayout mMessageContainer;
    private ImageFloatingTextView mSenderName;
    private ImageView mAvatarView;
    private String mAvatarSymbol = "";
    private int mLayoutColor;
    private CharSequence mAvatarName = "";
    private Icon mAvatarIcon;
    private ColorFilter mMessageBackgroundFilter;
    private int mTextColor;

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

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

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

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMessageContainer = findViewById(R.id.group_message_container);
        mSenderName = findViewById(R.id.message_name);
        mAvatarView = findViewById(R.id.message_icon);
    }

    public void setSender(CharSequence sender) {
        if (sender == null) {
            mAvatarView.setVisibility(GONE);
            mSenderName.setVisibility(GONE);
            setGravity(Gravity.END);
            mMessageBackgroundFilter = new PorterDuffColorFilter(mLayoutColor,
                    PorterDuff.Mode.SRC_ATOP);
            mTextColor = NotificationColorUtil.isColorLight(mLayoutColor) ? getNormalTextColor()
                    : Color.WHITE;
        } else {
            mSenderName.setText(sender);
            mAvatarView.setVisibility(VISIBLE);
            mSenderName.setVisibility(VISIBLE);
            setGravity(Gravity.START);
            mMessageBackgroundFilter = null;
            mTextColor = getNormalTextColor();
        }
    }

    private int getNormalTextColor() {
        return mContext.getColor(R.color.notification_primary_text_color_light);
    }

    public void setAvatar(Icon icon) {
        mAvatarIcon = icon;
        mAvatarView.setImageIcon(icon);
        mAvatarSymbol = "";
        mLayoutColor = 0;
        mAvatarName = "";
    }

    static MessagingGroup createGroup(MessagingLinearLayout layout) {;
        MessagingGroup createdGroup = sInstancePool.acquire();
        if (createdGroup == null) {
            createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
                    R.layout.notification_template_messaging_group, layout,
                    false);
        }
        layout.addView(createdGroup);
        return createdGroup;
    }

    public void removeMessage(MessagingMessage messagingMessage) {
        // TODO: add removal animation
        mMessageContainer.removeView(messagingMessage);
        if (mMessageContainer.getChildCount() == 0) {
            ViewParent parent = getParent();
            if (parent instanceof ViewGroup) {
                ((ViewGroup) parent).removeView(this);
            }
            setAvatar(null);
            sInstancePool.release(this);
        }
    }

    public CharSequence getSenderName() {
        return mSenderName.getText();
    }

    public void setSenderVisible(boolean visible) {
        mSenderName.setVisibility(visible ? VISIBLE : GONE);
    }

    public static void dropCache() {
        sInstancePool = new Pools.SynchronizedPool<>(10);
    }

    @Override
    public int getMeasuredType() {
        boolean hasNormal = false;
        for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
            View child = mMessageContainer.getChildAt(i);
            if (child instanceof MessagingLinearLayout.MessagingChild) {
                int type = ((MessagingLinearLayout.MessagingChild) child).getMeasuredType();
                if (type == MEASURED_TOO_SMALL) {
                    if (hasNormal) {
                        return MEASURED_SHORTENED;
                    } else {
                        return MEASURED_TOO_SMALL;
                    }
                } else if (type == MEASURED_SHORTENED) {
                    return MEASURED_SHORTENED;
                } else {
                    hasNormal = true;
                }
            }
        }
        return MEASURED_NORMAL;
    }

    @Override
    public int getConsumedLines() {
        int result = 0;
        for (int i = 0; i < mMessageContainer.getChildCount(); i++) {
            View child = mMessageContainer.getChildAt(i);
            if (child instanceof MessagingLinearLayout.MessagingChild) {
                result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
            }
        }
        return result;
    }

    public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol,
            int layoutColor) {
        if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol)
                && layoutColor == mLayoutColor) {
            return mAvatarIcon;
        }
        return null;
    }

    public void setCreatedAvatar(Icon cachedIcon, CharSequence avatarName, String avatarSymbol,
            int layoutColor) {
        if (!mAvatarName.equals(avatarName) || !mAvatarSymbol.equals(avatarSymbol)
                || layoutColor != mLayoutColor) {
            setAvatar(cachedIcon);
            mAvatarSymbol = avatarSymbol;
            mLayoutColor = layoutColor;
            mAvatarName = avatarName;
        }
    }

    public void setLayoutColor(int layoutColor) {
        mLayoutColor = layoutColor;
    }

    public void setMessages(List<MessagingMessage> group) {
        // Let's now make sure all children are added and in the correct order
        for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
            MessagingMessage message = group.get(messageIndex);
            if (message.getGroup() != this) {
                message.setMessagingGroup(this);
                ViewParent parent = mMessageContainer.getParent();
                if (parent instanceof ViewGroup) {
                    ((ViewGroup) parent).removeView(message);
                }
                mMessageContainer.addView(message, messageIndex);
            }
            if (messageIndex != mMessageContainer.indexOfChild(message)) {
                mMessageContainer.removeView(message);
                mMessageContainer.addView(message, messageIndex);
            }
            // Let's make sure the message color is correct
            Drawable targetDrawable = message.getBackground();

            if (targetDrawable != null) {
                targetDrawable.mutate().setColorFilter(mMessageBackgroundFilter);
            }
            message.setTextColor(mTextColor);
        }
    }
}
+397 −0

File added.

Preview size limit exceeded, changes collapsed.

+26 −76

File changed.

Preview size limit exceeded, changes collapsed.

Loading