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

Commit eab16f80 authored by Ioana Alexandru's avatar Ioana Alexandru
Browse files

[Notif redesign] Update conversation layout

We're now using two separate layouts for the collapsed and expanded
version of conversations, like we do for all other styles. The structure
is also more consistent with the other templates.

CallLayout temporarily uses the old conversation header so that it
doesn't crash, it will be updated as well in a follow-up CL.

Files notification_2025_template_collapsed_conversation.xml and
notification_2025_template_expanded_conversation.xml are based on
notification_2025_template_collapsed_messaging.xml and
notification_2025_template_expanded_messaging.xml respectively.

The new notification_2025_conversation_header.xml is based on
notification_2025_header.xml.

Bug: 378660052
Test: tested manually, screenshot tests to come later
Flag: android.app.notifications_redesign_templates

Change-Id: Ib21bae19aee26a77f7d7f4fd4812df64d804bdde
parent 7cbacbdc
Loading
Loading
Loading
Loading
+71 −18
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.app;
import static android.annotation.Dimension.DP;
import static android.app.Flags.evenlyDividedCallStyleActionLayout;
import static android.app.Flags.notificationsRedesignTemplates;
import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -818,7 +819,8 @@ public class Notification implements Parcelable
                     R.layout.notification_2025_template_expanded_base,
                     R.layout.notification_2025_template_heads_up_base,
                     R.layout.notification_2025_template_header,
                     R.layout.notification_2025_template_conversation,
                     R.layout.notification_2025_template_collapsed_conversation,
                     R.layout.notification_2025_template_expanded_conversation,
                     R.layout.notification_2025_template_collapsed_call,
                     R.layout.notification_2025_template_expanded_call,
                     R.layout.notification_2025_template_collapsed_messaging,
@@ -5963,7 +5965,8 @@ public class Notification implements Parcelable
                    || resId == getCompactHeadsUpBaseLayoutResource()
                    || resId == getMessagingCompactHeadsUpLayoutResource()
                    || resId == getCollapsedMessagingLayoutResource()
                    || resId == getCollapsedMediaLayoutResource());
                    || resId == getCollapsedMediaLayoutResource()
                    || resId == getCollapsedConversationLayoutResource());
            RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
            resetStandardTemplate(contentView);
@@ -7673,12 +7676,18 @@ public class Notification implements Parcelable
            }
        }
        // Note: In the 2025 redesign, we use two separate layouts for the collapsed and expanded
        //  version of conversations. See below.
        private int getConversationLayoutResource() {
            if (Flags.notificationsRedesignTemplates()) {
                return R.layout.notification_2025_template_conversation;
            } else {
            return R.layout.notification_template_material_conversation;
        }
        private int getCollapsedConversationLayoutResource() {
            return R.layout.notification_2025_template_collapsed_conversation;
        }
        private int getExpandedConversationLayoutResource() {
            return R.layout.notification_2025_template_expanded_conversation;
        }
        private int getCollapsedCallLayoutResource() {
@@ -9483,7 +9492,8 @@ public class Notification implements Parcelable
            } else {
                isOneToOne = !isGroupConversation();
            }
            if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
            if ((isHeaderless || notificationsRedesignTemplates())
                    && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
                conversationTitle = getOtherPersonName();
            }
@@ -9493,22 +9503,27 @@ public class Notification implements Parcelable
                    .viewType(viewType)
                    .highlightExpander(isConversationLayout)
                    .hideProgress(true)
                    .title(isHeaderless ? conversationTitle : null)
                    .text(null)
                    .hideLeftIcon(isOneToOne)
                    .hideRightIcon(hideRightIcons || isOneToOne)
                    .headerTextSecondary(isHeaderless ? null : conversationTitle)
                    .skipTopLineAlignment(true);
                    .hideRightIcon(hideRightIcons || isOneToOne);
            if (notificationsRedesignTemplates()) {
                p.title((isConversationLayout || isCollapsed) ? conversationTitle : null)
                        .headerTextSecondary(
                                (isConversationLayout || isCollapsed) ? null : conversationTitle)
                        .hideAppName(isCollapsed)
                        .skipTopLineAlignment(!isConversationLayout && !isCollapsed);
            } else {
                p.title(isHeaderless ? conversationTitle : null)
                        .headerTextSecondary(isHeaderless ? null : conversationTitle);
            }
            RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
                    isConversationLayout
                            ? mBuilder.getConversationLayoutResource()
                            : isCollapsed
                                    ? mBuilder.getCollapsedMessagingLayoutResource()
                                    : mBuilder.getExpandedMessagingLayoutResource(),
                    getMessagingLayoutResource(isConversationLayout, isCollapsed),
                    p,
                    bindResult);
            if (isConversationLayout) {
            if (isConversationLayout && !notificationsRedesignTemplates()) {
                // Redesign note: This view is replaced by the `title`, which is handled normally.
                mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
                // Redesign note: This special divider is no longer needed.
                mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
            }
@@ -9537,6 +9552,17 @@ public class Notification implements Parcelable
                        "setShortcutIcon", mShortcutIcon);
                contentView.setBoolean(R.id.status_bar_latest_event_content,
                        "setIsImportantConversation", isImportantConversation);
                if (notificationsRedesignTemplates() && !isCollapsed) {
                    // Align the title to the app/small icon in the expanded form. In other layouts,
                    // this margin is added directly to the notification_main_column parent, but for
                    // messages we don't want the margin to be applied to the actual messaging
                    // content since it can contain icons that are displayed below the app icon.
                    Resources res = mBuilder.mContext.getResources();
                    int marginStart = res.getDimensionPixelSize(
                            R.dimen.notification_2025_content_margin_start);
                    contentView.setViewLayoutMargin(R.id.title,
                            RemoteViews.MARGIN_START, marginStart, TypedValue.COMPLEX_UNIT_PX);
                }
            }
            if (isHeaderless) {
                // Collapsed legacy messaging style has a 1-line limit.
@@ -9549,6 +9575,33 @@ public class Notification implements Parcelable
            return contentView;
        }
        private int getMessagingLayoutResource(boolean isConversationLayout, boolean isCollapsed) {
            if (notificationsRedesignTemplates()) {
                // Note: We eventually would like to use the same layouts for both conversations and
                //  regular messaging notifications.
                if (isConversationLayout) {
                    if (isCollapsed) {
                        return mBuilder.getCollapsedConversationLayoutResource();
                    } else {
                        return mBuilder.getExpandedConversationLayoutResource();
                    }
                } else {
                    if (isCollapsed) {
                        return mBuilder.getCollapsedMessagingLayoutResource();
                    } else {
                        return mBuilder.getExpandedMessagingLayoutResource();
                    }
                }
            } else {
                return isConversationLayout
                        ? mBuilder.getConversationLayoutResource()
                        : isCollapsed
                                ? mBuilder.getCollapsedMessagingLayoutResource()
                                : mBuilder.getExpandedMessagingLayoutResource();
            }
        }
        private CharSequence getKey(Person person) {
            return person == null ? null
                    : person.getKey() == null ? person.getName() : person.getKey();
+43 −11
Original line number Diff line number Diff line
@@ -250,7 +250,8 @@ public class ConversationLayout extends FrameLayout
            mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
            mPeopleHelper.animateViewForceHidden(mIcon, forceHidden);
        });
        mConversationText = findViewById(R.id.conversation_text);
        mConversationText = findViewById(notificationsRedesignTemplates()
                ? R.id.title : R.id.conversation_text);
        mExpandButtonContainer = findViewById(R.id.expand_button_container);
        mExpandButtonContainerA11yContainer =
                findViewById(R.id.expand_button_a11y_container);
@@ -716,17 +717,10 @@ public class ConversationLayout extends FrameLayout
    }

    private void updateImageMessages() {
        View newMessage = null;
        if (mIsCollapsed && !mGroups.isEmpty()) {

            // When collapsed, we're displaying the image message in a dedicated container
            // on the right of the layout instead of inline. Let's add the isolated image there
            MessagingGroup messagingGroup = mGroups.getLast();
            MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
            if (isolatedMessage != null) {
                newMessage = isolatedMessage.getView();
            }
        if (mImageMessageContainer == null) {
            return;
        }
        View newMessage = getNewImageMessage();
        // Remove all messages that don't belong into the image layout
        View previousMessage = mImageMessageContainer.getChildAt(0);
        if (previousMessage != newMessage) {
@@ -738,6 +732,20 @@ public class ConversationLayout extends FrameLayout
        mImageMessageContainer.setVisibility(newMessage != null ? VISIBLE : GONE);
    }

    @Nullable
    private View getNewImageMessage() {
        if (mIsCollapsed && !mGroups.isEmpty()) {
            // When collapsed, we're displaying the image message in a dedicated container
            // on the right of the layout instead of inline. Let's add the isolated image there
            MessagingGroup messagingGroup = mGroups.getLast();
            MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
            if (isolatedMessage != null) {
                return isolatedMessage.getView();
            }
        }
        return null;
    }

    public void bindFacePile(ImageView bottomBackground, ImageView bottomView, ImageView topView) {
        applyNotificationBackgroundColor(bottomBackground);
        // Let's find the two last conversations:
@@ -841,6 +849,10 @@ public class ConversationLayout extends FrameLayout
    }

    private void updateAppName() {
        if (notificationsRedesignTemplates()) {
            return;
        }

        mAppName.setVisibility(mIsCollapsed ? GONE : VISIBLE);
    }

@@ -1533,6 +1545,10 @@ public class ConversationLayout extends FrameLayout
    }

    private void updateExpandButton() {
        if (notificationsRedesignTemplates()) {
            return;
        }

        int buttonGravity;
        ViewGroup newContainer;
        if (mIsCollapsed) {
@@ -1565,6 +1581,10 @@ public class ConversationLayout extends FrameLayout
    }

    private void updateContentEndPaddings() {
        if (notificationsRedesignTemplates()) {
            return;
        }

        // Let's make sure the conversation header can't run into the expand button when we're
        // collapsed and update the paddings of the content
        int headerPaddingEnd;
@@ -1593,6 +1613,10 @@ public class ConversationLayout extends FrameLayout
    }

    private void onAppNameVisibilityChanged() {
        if (notificationsRedesignTemplates()) {
            return;
        }

        boolean appNameGone = mAppName.getVisibility() == GONE;
        if (appNameGone != mAppNameGone) {
            mAppNameGone = appNameGone;
@@ -1601,10 +1625,18 @@ public class ConversationLayout extends FrameLayout
    }

    private void updateAppNameDividerVisibility() {
        if (notificationsRedesignTemplates()) {
            return;
        }

        mAppNameDivider.setVisibility(mAppNameGone ? GONE : VISIBLE);
    }

    public void updateExpandability(boolean expandable, @Nullable OnClickListener onClickListener) {
        if (notificationsRedesignTemplates()) {
            return;
        }

        mExpandable = expandable;
        if (expandable) {
            mExpandButtonContainer.setVisibility(VISIBLE);
+2 −6
Original line number Diff line number Diff line
@@ -449,12 +449,8 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements
    }

    private void updateIconVisibility() {
        if (Flags.notificationsRedesignTemplates() && !mIsInConversation) {
            // We don't show any icon (other than the app icon) in the collapsed form. For
            // conversations, keeping this container helps with aligning the message to the icon
            // when collapsed, but the old messaging style already has this alignment built into
            // the template like all other layouts. Conversations are special because we use the
            // same base layout for both the collapsed and expanded views.
        if (Flags.notificationsRedesignTemplates()) {
            // We don't show any icon (other than the app or person icon) in the collapsed form.
            mMessagingIconContainer.setVisibility(mSingleLine ? GONE : VISIBLE);
        }
    }
+15 −10
Original line number Diff line number Diff line
@@ -314,19 +314,10 @@ public class MessagingLayout extends FrameLayout
    }

    private void updateImageMessages() {
        View newMessage = null;
        if (mImageMessageContainer == null) {
            return;
        }
        if (mIsCollapsed && !mGroups.isEmpty()) {
            // When collapsed, we're displaying the image message in a dedicated container
            // on the right of the layout instead of inline. Let's add the isolated image there
            MessagingGroup messagingGroup = mGroups.getLast();
            MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
            if (isolatedMessage != null) {
                newMessage = isolatedMessage.getView();
            }
        }
        View newMessage = getNewImageMessage();
        // Remove all messages that don't belong into the image layout
        View previousMessage = mImageMessageContainer.getChildAt(0);
        if (previousMessage != newMessage) {
@@ -345,6 +336,20 @@ public class MessagingLayout extends FrameLayout
        }
    }

    @Nullable
    private View getNewImageMessage() {
        if (mIsCollapsed && !mGroups.isEmpty()) {
            // When collapsed, we're displaying the image message in a dedicated container
            // on the right of the layout instead of inline. Let's add the isolated image there
            MessagingGroup messagingGroup = mGroups.getLast();
            MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
            if (isolatedMessage != null) {
                return isolatedMessage.getView();
            }
        }
        return null;
    }

    private void removeGroups(ArrayList<MessagingGroup> oldGroups) {
        int size = oldGroups.size();
        for (int i = 0; i < size; i++) {
+53 −136
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2024 The Android Open Source Project
  ~ Copyright (C) 2025 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.
@@ -15,157 +15,74 @@
  ~ limitations under the License
  -->

<com.android.internal.widget.ConversationHeaderLinearLayout
<!-- extends RelativeLayout -->
<NotificationHeaderView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/conversation_header"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/notification_header"
    android:layout_width="match_parent"
    android:layout_height="@dimen/notification_2025_header_height"
    android:clipChildren="false"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:paddingTop="@dimen/notification_2025_margin"
    android:theme="@style/Theme.DeviceDefault.Notification"
    android:importantForAccessibility="no"
    >

    <TextView
        android:id="@+id/conversation_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
        android:textSize="16sp"
        android:singleLine="true"
        android:layout_weight="1"
        />

    <TextView
        android:id="@+id/app_name_divider"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
        android:text="@string/notification_header_divider_symbol"
        android:singleLine="true"
    <ImageView
        android:id="@+id/left_icon"
        android:layout_width="@dimen/notification_2025_left_icon_size"
        android:layout_height="@dimen/notification_2025_left_icon_size"
        android:layout_alignParentStart="true"
        android:layout_margin="@dimen/notification_2025_margin"
        android:background="@drawable/notification_large_icon_outline"
        android:clipToOutline="true"
        android:importantForAccessibility="no"
        android:scaleType="centerCrop"
        android:visibility="gone"
        />

    <!-- App Name -->
    <com.android.internal.widget.ObservableTextView
        android:id="@+id/app_name_text"
        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
        android:singleLine="true"
        android:visibility="gone"
        />
    <include layout="@layout/notification_2025_conversation_icon_container" />

    <TextView
        android:id="@+id/time_divider"
    <!-- extends ViewGroup -->
    <NotificationTopLineView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/notification_top_line"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
        android:text="@string/notification_header_divider_symbol"
        android:singleLine="true"
        android:visibility="gone"
        />
        android:layout_alignParentStart="true"
        android:layout_toStartOf="@id/expand_button"
        android:layout_alignWithParentIfMissing="true"
        android:layout_marginVertical="@dimen/notification_2025_margin"
        android:clipChildren="false"
        android:gravity="center_vertical"
        android:paddingStart="@dimen/notification_2025_content_margin_start"
        android:theme="@style/Theme.DeviceDefault.Notification"
        >

    <DateTimeView
        android:id="@+id/time"
        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
        android:showRelative="true"
        android:singleLine="true"
        android:visibility="gone"
        />
        <include layout="@layout/notification_2025_top_line_views" />

    <ViewStub
        android:id="@+id/chronometer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
        android:layout="@layout/notification_template_part_chronometer"
        android:visibility="gone"
        />

    <TextView
        android:id="@+id/verification_divider"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
        android:text="@string/notification_header_divider_symbol"
        android:singleLine="true"
        android:visibility="gone"
        />
    </NotificationTopLineView>

    <ImageView
        android:id="@+id/verification_icon"
        android:layout_width="@dimen/notification_verification_icon_size"
        android:layout_height="@dimen/notification_verification_icon_size"
        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
        android:baseline="10dp"
        android:scaleType="fitCenter"
        android:src="@drawable/ic_notifications_alerted"
        android:visibility="gone"
    <FrameLayout
        android:id="@+id/alternate_expand_target"
        android:layout_width="@dimen/notification_2025_content_margin_start"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        android:importantForAccessibility="no"
        android:focusable="false"
        />

    <TextView
        android:id="@+id/verification_text"
        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
    <include layout="@layout/notification_2025_expand_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
        android:layout_weight="100"
        android:showRelative="true"
        android:singleLine="true"
        android:visibility="gone"
        />
        android:layout_gravity="top|end"
        android:layout_alignParentEnd="true" />

    <ImageButton
        android:id="@+id/feedback"
        android:layout_width="@dimen/notification_feedback_size"
        android:layout_height="@dimen/notification_feedback_size"
        android:layout_marginStart="@dimen/notification_header_separating_margin"
        android:background="?android:selectableItemBackgroundBorderless"
        android:contentDescription="@string/notification_feedback_indicator"
        android:baseline="13dp"
        android:scaleType="fitCenter"
        android:src="@drawable/ic_feedback_indicator"
        android:visibility="gone"
        />
    <include layout="@layout/notification_close_button"
        android:id="@+id/close_button"
        android:layout_width="@dimen/notification_close_button_size"
        android:layout_height="@dimen/notification_close_button_size"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true" />

    <ImageView
        android:id="@+id/phishing_alert"
        android:layout_width="@dimen/notification_2025_badge_size"
        android:layout_height="@dimen/notification_2025_badge_size"
        android:layout_marginStart="@dimen/notification_2025_badge_margin"
        android:baseline="@dimen/notification_2025_badge_baseline"
        android:scaleType="fitCenter"
        android:src="@drawable/ic_dialog_alert_material"
        android:visibility="gone"
        android:contentDescription="@string/notification_phishing_alert_content_description"
        />

    <ImageView
        android:id="@+id/profile_badge"
        android:layout_width="@dimen/notification_2025_badge_size"
        android:layout_height="@dimen/notification_2025_badge_size"
        android:layout_marginStart="@dimen/notification_2025_badge_margin"
        android:baseline="@dimen/notification_2025_badge_baseline"
        android:scaleType="fitCenter"
        android:visibility="gone"
        android:contentDescription="@string/notification_work_profile_content_description"
        />

    <ImageView
        android:id="@+id/alerted_icon"
        android:layout_width="@dimen/notification_2025_badge_size"
        android:layout_height="@dimen/notification_2025_badge_size"
        android:layout_marginStart="@dimen/notification_2025_badge_margin"
        android:baseline="@dimen/notification_2025_badge_baseline"
        android:contentDescription="@string/notification_alerted_content_description"
        android:scaleType="fitCenter"
        android:src="@drawable/ic_notifications_alerted"
        android:visibility="gone"
        />
</com.android.internal.widget.ConversationHeaderLinearLayout>
</NotificationHeaderView>
Loading