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

Commit c2ef64da authored by András Kurucz's avatar András Kurucz Committed by Android (Google) Code Review
Browse files

Merge "Pass the row to the NotificationLayoutInflaters" into main

parents 8398d9de 231f0011
Loading
Loading
Loading
Loading
+25 −30
Original line number Diff line number Diff line
@@ -21,29 +21,25 @@ import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import com.android.systemui.Dumpable
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES
import com.android.systemui.util.asIndenting
import com.android.systemui.util.withIncreasedIndent
import java.io.PrintWriter
import javax.inject.Inject
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import javax.inject.Named

/**
 * Implementation of [NotifLayoutInflaterFactory]. This class uses a set of
 * [NotifRemoteViewsFactory] objects to create replacement views for Notification RemoteViews.
 */
open class NotifLayoutInflaterFactory
@Inject
class NotifLayoutInflaterFactory
@AssistedInject
constructor(
    dumpManager: DumpManager,
    @Assisted private val row: ExpandableNotificationRow,
    @Assisted @InflationFlag val layoutType: Int,
    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
    private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
) : LayoutInflater.Factory2, Dumpable {
    init {
        dumpManager.registerNormalDumpable(TAG, this)
    }
) : LayoutInflater.Factory2 {

    override fun onCreateView(
        parent: View?,
@@ -51,41 +47,32 @@ constructor(
        context: Context,
        attrs: AttributeSet
    ): View? {
        var view: View? = null
        var handledFactory: NotifRemoteViewsFactory? = null
        var result: View? = null
        for (layoutFactory in remoteViewsFactories) {
            view = layoutFactory.instantiate(parent, name, context, attrs)
            if (view != null) {
            layoutFactory.instantiate(row, layoutType, parent, name, context, attrs)?.run {
                check(handledFactory == null) {
                    "${layoutFactory.javaClass.name} tries to produce view. However, " +
                        "${handledFactory?.javaClass?.name} produced view for $name before."
                    "$layoutFactory tries to produce name:$name with type:$layoutType. " +
                        "However, $handledFactory produced view for $name before."
                }
                handledFactory = layoutFactory
                result = this
            }
        }
        logOnCreateView(name, view, handledFactory)
        return view
        logOnCreateView(name, result, handledFactory)
        return result
    }

    override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? =
        onCreateView(null, name, context, attrs)

    override fun dump(pw: PrintWriter, args: Array<out String>) {
        val indentingPW = pw.asIndenting()

        indentingPW.appendLine("$TAG ReplacementFactories:")
        indentingPW.withIncreasedIndent {
            remoteViewsFactories.forEach { indentingPW.appendLine(it.javaClass.simpleName) }
        }
    }

    private fun logOnCreateView(
        name: String,
        replacedView: View?,
        factory: NotifRemoteViewsFactory?
    ) {
        if (SPEW && replacedView != null && factory != null) {
            Log.d(TAG, "$factory produced view for $name: $replacedView")
            Log.d(TAG, "$factory produced $replacedView for name:$name with type:$layoutType")
        }
    }

@@ -93,4 +80,12 @@ constructor(
        private const val TAG = "NotifLayoutInflaterFac"
        private val SPEW = Log.isLoggable(TAG, Log.VERBOSE)
    }

    @AssistedFactory
    interface Provider {
        fun provide(
            row: ExpandableNotificationRow,
            @InflationFlag layoutType: Int
        ): NotifLayoutInflaterFactory
    }
}
+9 −1
Original line number Diff line number Diff line
@@ -19,10 +19,18 @@ package com.android.systemui.statusbar.notification.row
import android.content.Context
import android.util.AttributeSet
import android.view.View
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag

/** Interface used to create replacement view instances in Notification RemoteViews. */
interface NotifRemoteViewsFactory {

    /** return the replacement view instance for the given view name */
    fun instantiate(parent: View?, name: String, context: Context, attrs: AttributeSet): View?
    fun instantiate(
        row: ExpandableNotificationRow,
        @InflationFlag layoutType: Int,
        parent: View?,
        name: String,
        context: Context,
        attrs: AttributeSet
    ): View?
}
+23 −17
Original line number Diff line number Diff line
@@ -79,7 +79,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
    private final ConversationNotificationProcessor mConversationProcessor;
    private final Executor mBgExecutor;
    private final SmartReplyStateInflater mSmartReplyStateInflater;
    private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
    private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;

    @Inject
    NotificationContentInflater(
@@ -89,14 +89,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder
            MediaFeatureFlag mediaFeatureFlag,
            @Background Executor bgExecutor,
            SmartReplyStateInflater smartRepliesInflater,
            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
            NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) {
        mRemoteViewCache = remoteViewCache;
        mRemoteInputManager = remoteInputManager;
        mConversationProcessor = conversationProcessor;
        mIsMediaInQS = mediaFeatureFlag.getEnabled();
        mBgExecutor = bgExecutor;
        mSmartReplyStateInflater = smartRepliesInflater;
        mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
        mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider;
    }

    @Override
@@ -141,7 +141,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
                mRemoteInputManager.getRemoteViewsOnClickHandler(),
                mIsMediaInQS,
                mSmartReplyStateInflater,
                mNotifLayoutInflaterFactory);
                mNotifLayoutInflaterFactoryProvider);
        if (mInflateSynchronously) {
            task.onPostExecute(task.doInBackground());
        } else {
@@ -165,7 +165,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
                bindParams.usesIncreasedHeight,
                bindParams.usesIncreasedHeadsUpHeight,
                packageContext,
                mNotifLayoutInflaterFactory);
                row,
                mNotifLayoutInflaterFactoryProvider);

        result = inflateSmartReplyViews(result, reInflateFlags, entry,
                row.getContext(), packageContext,
@@ -304,7 +305,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
    private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags,
            Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight,
            boolean usesIncreasedHeadsUpHeight, Context packageContext,
            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
            ExpandableNotificationRow row,
            NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) {
        InflationProgress result = new InflationProgress();

        if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
@@ -322,7 +324,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
        if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
            result.newPublicView = builder.makePublicContentView(isLowPriority);
        }
        setNotifsViewsInflaterFactory(result, notifLayoutInflaterFactory);
        setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
        result.packageContext = packageContext;
        result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
        result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
@@ -331,12 +333,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder
    }

    private static void setNotifsViewsInflaterFactory(InflationProgress result,
            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
        setRemoteViewsInflaterFactory(result.newContentView, notifLayoutInflaterFactory);
            ExpandableNotificationRow row,
            NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) {
        setRemoteViewsInflaterFactory(result.newContentView,
                notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_CONTRACTED));
        setRemoteViewsInflaterFactory(result.newExpandedView,
                notifLayoutInflaterFactory);
        setRemoteViewsInflaterFactory(result.newHeadsUpView, notifLayoutInflaterFactory);
        setRemoteViewsInflaterFactory(result.newPublicView, notifLayoutInflaterFactory);
                notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_EXPANDED));
        setRemoteViewsInflaterFactory(result.newHeadsUpView,
                notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_HEADS_UP));
        setRemoteViewsInflaterFactory(result.newPublicView,
                notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_PUBLIC));
    }

    private static void setRemoteViewsInflaterFactory(RemoteViews remoteViews,
@@ -821,7 +827,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
        private final ConversationNotificationProcessor mConversationProcessor;
        private final boolean mIsMediaInQS;
        private final SmartReplyStateInflater mSmartRepliesInflater;
        private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
        private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;

        private AsyncInflationTask(
                Executor bgExecutor,
@@ -838,7 +844,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
                RemoteViews.InteractionHandler remoteViewClickHandler,
                boolean isMediaFlagEnabled,
                SmartReplyStateInflater smartRepliesInflater,
                NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
                NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) {
            mEntry = entry;
            mRow = row;
            mBgExecutor = bgExecutor;
@@ -854,7 +860,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
            mCallback = callback;
            mConversationProcessor = conversationProcessor;
            mIsMediaInQS = isMediaFlagEnabled;
            mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
            mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider;
            entry.setInflationTask(this);
        }

@@ -898,8 +904,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
                }
                InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                        recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
                        mUsesIncreasedHeadsUpHeight, packageContext,
                        mNotifLayoutInflaterFactory);
                        mUsesIncreasedHeadsUpHeight, packageContext, mRow,
                        mNotifLayoutInflaterFactoryProvider);
                InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
                InflationProgress result = inflateSmartReplyViews(
                        inflationProgress,
+3 −0
Original line number Diff line number Diff line
@@ -23,10 +23,13 @@ import android.widget.TextView
import com.android.internal.widget.ConversationLayout
import com.android.internal.widget.ImageFloatingTextView
import com.android.internal.widget.MessagingLayout
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
import javax.inject.Inject

class PrecomputedTextViewFactory @Inject constructor() : NotifRemoteViewsFactory {
    override fun instantiate(
        row: ExpandableNotificationRow,
        @InflationFlag layoutType: Int,
        parent: View?,
        name: String,
        context: Context,
+72 −51
Original line number Diff line number Diff line
@@ -20,19 +20,21 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.util.AttributeSet
import android.view.View
import android.widget.Button
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
import org.junit.Before
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify

/** Tests for [NotifLayoutInflaterFactory] */
@SmallTest
@@ -40,87 +42,106 @@ import org.mockito.MockitoAnnotations
@RunWithLooper
class NotifLayoutInflaterFactoryTest : SysuiTestCase() {

    @Mock private lateinit var attrs: AttributeSet
    private lateinit var inflaterFactory: NotifLayoutInflaterFactory

    @Before
    fun before() {
        MockitoAnnotations.initMocks(this)
    private val attrs: AttributeSet = mock()
    private val row: ExpandableNotificationRow = mock()
    private val textViewExpandedFactory =
        createReplacementViewFactory("TextView", FLAG_CONTENT_VIEW_EXPANDED) { context, _ ->
            Button(context)
        }
    private val textViewCollapsedFactory =
        createReplacementViewFactory("TextView", FLAG_CONTENT_VIEW_CONTRACTED) { context, _ ->
            Button(context)
        }
    private val textViewExpandedFactorySpy = spy(textViewExpandedFactory)
    private val textViewCollapsedFactorySpy = spy(textViewCollapsedFactory)
    private val viewFactorySpies = setOf(textViewExpandedFactorySpy, textViewCollapsedFactorySpy)

    @Test
    fun onCreateView_notMatchingViews_returnNull() {
        // GIVEN
        val layoutInflaterFactory =
            createNotifLayoutInflaterFactoryImpl(
                setOf(
                    createReplacementViewFactory("TextView") { context, attrs ->
                        FrameLayout(context)
    fun onCreateView_noMatchingViewForName_returnNull() {
        // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
        val layoutType = FLAG_CONTENT_VIEW_EXPANDED
        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)

        // WHEN we try to inflate an ImageView for the expanded layout
        val createdView = inflaterFactory.onCreateView("ImageView", context, attrs)

        // THEN the inflater factory returns null
        viewFactorySpies.forEach { viewFactory ->
            verify(viewFactory).instantiate(row, layoutType, null, "ImageView", context, attrs)
        }
        assertThat(createdView).isNull()
    }
                )
            )

        // WHEN
        val createView = layoutInflaterFactory.onCreateView("ImageView", mContext, attrs)
    @Test
    fun onCreateView_noMatchingViewForLayoutType_returnNull() {
        // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
        val layoutType = FLAG_CONTENT_VIEW_HEADS_UP
        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)

        // THEN
        assertNull(createView)
        // WHEN we try to inflate a TextView for the heads-up layout
        val createdView = inflaterFactory.onCreateView("TextView", context, attrs)

        // THEN the inflater factory returns null
        viewFactorySpies.forEach { viewFactory ->
            verify(viewFactory).instantiate(row, layoutType, null, "TextView", context, attrs)
        }
        assertThat(createdView).isNull()
    }

    @Test
    fun onCreateView_matchingViews_returnReplacementView() {
        // GIVEN
        val layoutInflaterFactory =
            createNotifLayoutInflaterFactoryImpl(
                setOf(
                    createReplacementViewFactory("TextView") { context, attrs ->
                        FrameLayout(context)
                    }
                )
            )
        // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
        val layoutType = FLAG_CONTENT_VIEW_EXPANDED
        inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)

        // WHEN
        val createView = layoutInflaterFactory.onCreateView("TextView", mContext, attrs)
        // WHEN we try to inflate a TextView for the expanded layout
        val createdView = inflaterFactory.onCreateView("TextView", context, attrs)

        // THEN
        assertNotNull(createView)
        assertEquals(requireNotNull(createView)::class.java, FrameLayout::class.java)
        // THEN the expanded viewFactory returns the replaced view
        verify(textViewCollapsedFactorySpy)
            .instantiate(row, layoutType, null, "TextView", context, attrs)
        assertThat(createdView).isInstanceOf(Button::class.java)
    }

    @Test(expected = IllegalStateException::class)
    fun onCreateView_multipleFactory_throwIllegalStateException() {
        // GIVEN
        val layoutInflaterFactory =
            createNotifLayoutInflaterFactoryImpl(
        // GIVEN we have two factories that replaces TextViews in expanded layouts
        val layoutType = FLAG_CONTENT_VIEW_EXPANDED
        inflaterFactory =
            NotifLayoutInflaterFactory(
                row,
                layoutType,
                setOf(
                    createReplacementViewFactory("TextView") { context, attrs ->
                    createReplacementViewFactory("TextView", layoutType) { context, _ ->
                        FrameLayout(context)
                    },
                    createReplacementViewFactory("TextView") { context, attrs ->
                    createReplacementViewFactory("TextView", layoutType) { context, _ ->
                        LinearLayout(context)
                    }
                )
            )

        // WHEN
        layoutInflaterFactory.onCreateView("TextView", mContext, attrs)
        // WHEN we try to inflate a TextView for the expanded layout
        inflaterFactory.onCreateView("TextView", mContext, attrs)
    }

    private fun createNotifLayoutInflaterFactoryImpl(
        replacementViewFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
    ) = NotifLayoutInflaterFactory(DumpManager(), replacementViewFactories)

    private fun createReplacementViewFactory(
        replacementName: String,
        @InflationFlag replacementLayoutType: Int,
        createView: (context: Context, attrs: AttributeSet) -> View
    ) =
        object : NotifRemoteViewsFactory {
            override fun instantiate(
                row: ExpandableNotificationRow,
                @InflationFlag layoutType: Int,
                parent: View?,
                name: String,
                context: Context,
                attrs: AttributeSet
            ): View? =
                if (replacementName == name) {
                if (replacementName == name && replacementLayoutType == layoutType) {
                    createView(context, attrs)
                } else {
                    null
Loading