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

Commit be24db38 authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Allow summarizations to be two lines of text long" into main

parents e26794d9 6f491d5c
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -399,7 +399,9 @@ public class ConversationLayout extends FrameLayout
    @RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
    public void setIsCollapsed(boolean isCollapsed) {
        mIsCollapsed = isCollapsed;
        mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed ? 1 : Integer.MAX_VALUE);
        mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed
                ? TextUtils.isEmpty(mSummarizedContent) ? 1 : 2
                : Integer.MAX_VALUE);
        updateExpandButton();
        updateContentEndPaddings();
    }
@@ -448,7 +450,7 @@ public class ConversationLayout extends FrameLayout

        List<MessagingMessage> newMessagingMessages;
        mSummarizedContent = extras.getCharSequence(Notification.EXTRA_SUMMARIZED_CONTENT);
        if (mSummarizedContent != null && mIsCollapsed) {
        if (!TextUtils.isEmpty(mSummarizedContent) && mIsCollapsed) {
            Notification.MessagingStyle.Message summary =
                    new Notification.MessagingStyle.Message(mSummarizedContent,  0, "");
            newMessagingMessages = createMessages(List.of(summary), false, usePrecomputedText);
@@ -1162,7 +1164,7 @@ public class ConversationLayout extends FrameLayout
                nameOverride = mNameReplacement;
            }
            newGroup.setShowingAvatar(!mIsOneToOne && !mIsCollapsed);
            newGroup.setSingleLine(mIsCollapsed);
            newGroup.setSingleLine(mIsCollapsed && TextUtils.isEmpty(mSummarizedContent));
            newGroup.setSender(sender, nameOverride);
            newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner);
            mGroups.add(newGroup);
@@ -1462,7 +1464,6 @@ public class ConversationLayout extends FrameLayout
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            }

            maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
            if (maxHeight != getMeasuredHeight()) {
                setMeasuredDimension(getMeasuredWidth(), maxHeight);
+3 −2
Original line number Diff line number Diff line
@@ -198,7 +198,8 @@ public class MessagingLayout extends FrameLayout
                /* isHistoric= */true, usePrecomputedText);
        List<MessagingMessage> newMessagingMessages;
        mSummarizedContent = extras.getCharSequence(Notification.EXTRA_SUMMARIZED_CONTENT);
        if (mSummarizedContent != null && mIsCollapsed) {
        if (!TextUtils.isEmpty(mSummarizedContent) && mIsCollapsed) {
            mMessagingLinearLayout.setMaxDisplayedLines(2);
            Notification.MessagingStyle.Message summary =
                    new Notification.MessagingStyle.Message(mSummarizedContent,  0, "");
            newMessagingMessages = createMessages(List.of(summary), false, usePrecomputedText);
@@ -488,7 +489,7 @@ public class MessagingLayout extends FrameLayout
            if (sender != mUser && mNameReplacement != null) {
                nameOverride = mNameReplacement;
            }
            newGroup.setSingleLine(mIsCollapsed);
            newGroup.setSingleLine(mIsCollapsed && TextUtils.isEmpty(mSummarizedContent));
            newGroup.setShowingAvatar(!mIsCollapsed);
            newGroup.setSender(sender, nameOverride);
            newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner);
+6 −3
Original line number Diff line number Diff line
@@ -431,15 +431,18 @@
    <!-- The minimum height of the notification content (even when there's only one line of text) -->
    <dimen name="notification_2025_content_min_height">40dp</dimen>

    <!-- Height of a headerless notification with one or two lines -->
    <!-- 16 * 2 (margins) + 40 (min content height) = 72 (notification) -->
    <!-- Max height of a collapsed (headerless) notification with a summarization -->
    <dimen name="notification_collapsed_height_with_summarization">156dp</dimen>

    <!-- Max height of a collapsed (headerless) notification with one or two lines -->
    <!-- 16 * 2 (margins) + 48 (min content height) = 72 (notification) -->
    <dimen name="notification_2025_min_height">72dp</dimen>

    <!-- Height of a headerless notification with one line -->
    <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) -->
    <dimen name="notification_headerless_min_height">56dp</dimen>

    <!-- Height of a small two-line notification -->
    <!-- Max height of a collapsed two-line notification -->
    <!-- 20 * 2 (margins) + 24 * 2 (2 lines) = 88 (notification) -->
    <dimen name="notification_min_height">88dp</dimen>

+1 −0
Original line number Diff line number Diff line
@@ -5899,6 +5899,7 @@

  <java-symbol type="array" name="config_notificationDefaultUnsupportedAdjustments" />
  <java-symbol type="drawable" name="ic_notification_summarization" />
  <java-symbol type="dimen" name="notification_collapsed_height_with_summarization" />

  <!-- Advanced Protection Service USB feature -->
  <java-symbol type="string" name="usb_apm_usb_plugged_in_when_locked_notification_title" />
+161 −0
Original line number Diff line number Diff line
/*
 * 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.
 * 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.systemui.statusbar.notification

import android.app.Flags
import android.app.Notification
import android.app.Notification.EXTRA_SUMMARIZED_CONTENT
import android.app.Person
import android.content.pm.LauncherApps
import android.content.pm.launcherApps
import android.graphics.drawable.Icon
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import android.text.SpannableStringBuilder
import android.text.style.ImageSpan
import android.text.style.StyleSpan
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinderLogger
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.row.notificationRowContentBinderLogger
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
class ConversationNotificationProcessorTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private lateinit var conversationNotificationProcessor: ConversationNotificationProcessor
    private lateinit var testHelper: NotificationTestHelper
    private lateinit var launcherApps: LauncherApps
    private lateinit var logger: NotificationRowContentBinderLogger
    private lateinit var conversationNotificationManager: ConversationNotificationManager

    @Before
    fun setup() {
        launcherApps = kosmos.launcherApps
        conversationNotificationManager = kosmos.conversationNotificationManager
        logger = kosmos.notificationRowContentBinderLogger
        testHelper = NotificationTestHelper(mContext, mDependency)

        conversationNotificationProcessor =
            ConversationNotificationProcessor(
                context,
                launcherApps,
                conversationNotificationManager,
            )
    }

    @Test
    fun processNotification_notMessagingStyle() {
        val nb = Notification.Builder(mContext).setSmallIcon(R.drawable.ic_person)
        val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())

        assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
            .isNull()
    }

    @Test
    @DisableFlags(Flags.FLAG_NM_SUMMARIZATION, Flags.FLAG_NM_SUMMARIZATION_UI)
    fun processNotification_messagingStyleWithSummarization_flagOff() {
        val summarization = "hello"
        val nb = getMessagingNotification()
        val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
        newRow.entry.setRanking(
            RankingBuilder(newRow.entry.ranking).setSummarization(summarization).build()
        )

        assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
            .isNotNull()
        assertThat(nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)).isNull()
    }

    @Test
    @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
    fun processNotification_messagingStyleWithSummarization() {
        val summarization = "hello"
        val nb = getMessagingNotification()
        val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
        newRow.entry.setRanking(
            RankingBuilder(newRow.entry.ranking).setSummarization(summarization).build()
        )

        assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
            .isNotNull()

        val processedSummary = nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)
        assertThat(processedSummary.toString()).isEqualTo("x$summarization")

        val checkSpans = SpannableStringBuilder(processedSummary)
        assertThat(
                checkSpans.getSpans(
                    /* queryStart = */ 0,
                    /* queryEnd = */ 1,
                    /* kind = */ ImageSpan::class.java,
                )
            )
            .isNotNull()

        assertThat(
                processedSummary?.let {
                    checkSpans.getSpans(
                        /* queryStart = */ 0,
                        /* queryEnd = */ it.length,
                        /* kind = */ StyleSpan::class.java,
                    )
                }
            )
            .isNotNull()
    }

    @Test
    @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
    fun processNotification_messagingStyleWithoutSummarization() {
        val nb = getMessagingNotification()
        val newRow: ExpandableNotificationRow = testHelper.createRow(nb.build())
        assertThat(conversationNotificationProcessor.processNotification(newRow.entry, nb, logger))
            .isNotNull()
        assertThat(nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)).isNull()
    }

    private fun getMessagingNotification(): Notification.Builder {
        val displayName = "Display Name"
        val messageText = "Message Text"
        val personIcon = Icon.createWithResource(mContext, R.drawable.ic_person)
        val testPerson = Person.Builder().setName(displayName).setIcon(personIcon).build()
        val messagingStyle = Notification.MessagingStyle(testPerson)
        messagingStyle.addMessage(
            Notification.MessagingStyle.Message(messageText, System.currentTimeMillis(), testPerson)
        )
        return Notification.Builder(mContext)
            .setSmallIcon(R.drawable.ic_person)
            .setStyle(messagingStyle)
    }
}
Loading