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

Commit 1a5c8337 authored by Ibrahim Yilmaz's avatar Ibrahim Yilmaz
Browse files

Set LayoutInflatorFactory to Notification RemoteViews

This CL set LayoutInflatorFactory for Notification RemoteViews.

Bug: 289250881
Test: atest NotifLayoutInflaterFactoryTest && Manual by checking Notifications.
Change-Id: I7b1051d6632d4090cc905c09090c77d61aebb3c7
parent e6ac1838
Loading
Loading
Loading
Loading
+96 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 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.row

import android.content.Context
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.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 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
constructor(
    dumpManager: DumpManager,
    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
    private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
) : LayoutInflater.Factory2, Dumpable {
    init {
        dumpManager.registerNormalDumpable(TAG, this)
    }

    override fun onCreateView(
        parent: View?,
        name: String,
        context: Context,
        attrs: AttributeSet
    ): View? {
        var view: View? = null
        var handledFactory: NotifRemoteViewsFactory? = null
        for (layoutFactory in remoteViewsFactories) {
            view = layoutFactory.instantiate(parent, name, context, attrs)
            if (view != null) {
                check(handledFactory == null) {
                    "${layoutFactory.javaClass.name} tries to produce view. However, " +
                        "${handledFactory?.javaClass?.name} produced view for $name before."
                }
                handledFactory = layoutFactory
            }
        }
        logOnCreateView(name, view, handledFactory)
        return view
    }

    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")
        }
    }

    private companion object {
        private const val TAG = "NotifLayoutInflaterFac"
        private val SPEW = Log.isLoggable(TAG, Log.VERBOSE)
    }
}
+28 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 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.row

import android.content.Context
import android.util.AttributeSet
import android.view.View

/** 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?
}
+36 −11
Original line number Original line Diff line number Diff line
@@ -79,6 +79,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
    private final ConversationNotificationProcessor mConversationProcessor;
    private final ConversationNotificationProcessor mConversationProcessor;
    private final Executor mBgExecutor;
    private final Executor mBgExecutor;
    private final SmartReplyStateInflater mSmartReplyStateInflater;
    private final SmartReplyStateInflater mSmartReplyStateInflater;
    private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;


    @Inject
    @Inject
    NotificationContentInflater(
    NotificationContentInflater(
@@ -87,13 +88,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder
            ConversationNotificationProcessor conversationProcessor,
            ConversationNotificationProcessor conversationProcessor,
            MediaFeatureFlag mediaFeatureFlag,
            MediaFeatureFlag mediaFeatureFlag,
            @Background Executor bgExecutor,
            @Background Executor bgExecutor,
            SmartReplyStateInflater smartRepliesInflater) {
            SmartReplyStateInflater smartRepliesInflater,
            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
        mRemoteViewCache = remoteViewCache;
        mRemoteViewCache = remoteViewCache;
        mRemoteInputManager = remoteInputManager;
        mRemoteInputManager = remoteInputManager;
        mConversationProcessor = conversationProcessor;
        mConversationProcessor = conversationProcessor;
        mIsMediaInQS = mediaFeatureFlag.getEnabled();
        mIsMediaInQS = mediaFeatureFlag.getEnabled();
        mBgExecutor = bgExecutor;
        mBgExecutor = bgExecutor;
        mSmartReplyStateInflater = smartRepliesInflater;
        mSmartReplyStateInflater = smartRepliesInflater;
        mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
    }
    }


    @Override
    @Override
@@ -137,7 +140,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
                callback,
                callback,
                mRemoteInputManager.getRemoteViewsOnClickHandler(),
                mRemoteInputManager.getRemoteViewsOnClickHandler(),
                mIsMediaInQS,
                mIsMediaInQS,
                mSmartReplyStateInflater);
                mSmartReplyStateInflater,
                mNotifLayoutInflaterFactory);
        if (mInflateSynchronously) {
        if (mInflateSynchronously) {
            task.onPostExecute(task.doInBackground());
            task.onPostExecute(task.doInBackground());
        } else {
        } else {
@@ -160,7 +164,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
                bindParams.isLowPriority,
                bindParams.isLowPriority,
                bindParams.usesIncreasedHeight,
                bindParams.usesIncreasedHeight,
                bindParams.usesIncreasedHeadsUpHeight,
                bindParams.usesIncreasedHeadsUpHeight,
                packageContext);
                packageContext,
                mNotifLayoutInflaterFactory);


        result = inflateSmartReplyViews(result, reInflateFlags, entry,
        result = inflateSmartReplyViews(result, reInflateFlags, entry,
                row.getContext(), packageContext,
                row.getContext(), packageContext,
@@ -298,7 +303,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder


    private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags,
    private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags,
            Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight,
            Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight,
            boolean usesIncreasedHeadsUpHeight, Context packageContext) {
            boolean usesIncreasedHeadsUpHeight, Context packageContext,
            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
        InflationProgress result = new InflationProgress();
        InflationProgress result = new InflationProgress();


        if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
        if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
@@ -316,7 +322,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
        if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
        if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
            result.newPublicView = builder.makePublicContentView(isLowPriority);
            result.newPublicView = builder.makePublicContentView(isLowPriority);
        }
        }

        setNotifsViewsInflaterFactory(result, notifLayoutInflaterFactory);
        result.packageContext = packageContext;
        result.packageContext = packageContext;
        result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
        result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
        result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
        result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
@@ -324,6 +330,22 @@ public class NotificationContentInflater implements NotificationRowContentBinder
        return result;
        return result;
    }
    }


    private static void setNotifsViewsInflaterFactory(InflationProgress result,
            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
        setRemoteViewsInflaterFactory(result.newContentView, notifLayoutInflaterFactory);
        setRemoteViewsInflaterFactory(result.newExpandedView,
                notifLayoutInflaterFactory);
        setRemoteViewsInflaterFactory(result.newHeadsUpView, notifLayoutInflaterFactory);
        setRemoteViewsInflaterFactory(result.newPublicView, notifLayoutInflaterFactory);
    }

    private static void setRemoteViewsInflaterFactory(RemoteViews remoteViews,
            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
        if (remoteViews != null) {
            remoteViews.setLayoutInflaterFactory(notifLayoutInflaterFactory);
        }
    }

    private static CancellationSignal apply(
    private static CancellationSignal apply(
            Executor bgExecutor,
            Executor bgExecutor,
            boolean inflateSynchronously,
            boolean inflateSynchronously,
@@ -348,7 +370,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
                public void setResultView(View v) {
                public void setResultView(View v) {
                    result.inflatedContentView = v;
                    result.inflatedContentView = v;
                }
                }

                @Override
                @Override
                public RemoteViews getRemoteView() {
                public RemoteViews getRemoteView() {
                    return result.newContentView;
                    return result.newContentView;
@@ -800,6 +821,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
        private final ConversationNotificationProcessor mConversationProcessor;
        private final ConversationNotificationProcessor mConversationProcessor;
        private final boolean mIsMediaInQS;
        private final boolean mIsMediaInQS;
        private final SmartReplyStateInflater mSmartRepliesInflater;
        private final SmartReplyStateInflater mSmartRepliesInflater;
        private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;


        private AsyncInflationTask(
        private AsyncInflationTask(
                Executor bgExecutor,
                Executor bgExecutor,
@@ -815,7 +837,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
                InflationCallback callback,
                InflationCallback callback,
                RemoteViews.InteractionHandler remoteViewClickHandler,
                RemoteViews.InteractionHandler remoteViewClickHandler,
                boolean isMediaFlagEnabled,
                boolean isMediaFlagEnabled,
                SmartReplyStateInflater smartRepliesInflater) {
                SmartReplyStateInflater smartRepliesInflater,
                NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
            mEntry = entry;
            mEntry = entry;
            mRow = row;
            mRow = row;
            mBgExecutor = bgExecutor;
            mBgExecutor = bgExecutor;
@@ -831,6 +854,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
            mCallback = callback;
            mCallback = callback;
            mConversationProcessor = conversationProcessor;
            mConversationProcessor = conversationProcessor;
            mIsMediaInQS = isMediaFlagEnabled;
            mIsMediaInQS = isMediaFlagEnabled;
            mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
            entry.setInflationTask(this);
            entry.setInflationTask(this);
        }
        }


@@ -874,7 +898,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
                }
                }
                InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                        recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
                        recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
                        mUsesIncreasedHeadsUpHeight, packageContext);
                        mUsesIncreasedHeadsUpHeight, packageContext,
                        mNotifLayoutInflaterFactory);
                InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
                InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
                InflationProgress result = inflateSmartReplyViews(
                InflationProgress result = inflateSmartReplyViews(
                        inflationProgress,
                        inflationProgress,
+22 −0
Original line number Original line Diff line number Diff line
@@ -17,15 +17,26 @@
package com.android.systemui.statusbar.notification.row;
package com.android.systemui.statusbar.notification.row;


import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;


import dagger.Binds;
import dagger.Binds;
import dagger.Module;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;

import java.util.HashSet;
import java.util.Set;

import javax.inject.Named;


/**
/**
 * Dagger Module containing notification row and view inflation implementations.
 * Dagger Module containing notification row and view inflation implementations.
 */
 */
@Module
@Module
public abstract class NotificationRowModule {
public abstract class NotificationRowModule {
    public static final String NOTIF_REMOTEVIEWS_FACTORIES =
            "notif_remoteviews_factories";

    /**
    /**
     * Provides notification row content binder instance.
     * Provides notification row content binder instance.
     */
     */
@@ -41,4 +52,15 @@ public abstract class NotificationRowModule {
    @SysUISingleton
    @SysUISingleton
    public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
    public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
            NotifRemoteViewCacheImpl cacheImpl);
            NotifRemoteViewCacheImpl cacheImpl);

    /** Provides view factories to be inflated in notification content. */
    @Provides
    @ElementsIntoSet
    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
    static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
            FeatureFlags featureFlags
    ) {
        final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
        return replacementFactories;
    }
}
}
+129 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 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.row

import android.content.Context
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.util.AttributeSet
import android.view.View
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 org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations

/** Tests for [NotifLayoutInflaterFactory] */
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class NotifLayoutInflaterFactoryTest : SysuiTestCase() {

    @Mock private lateinit var attrs: AttributeSet

    @Before
    fun before() {
        MockitoAnnotations.initMocks(this)
    }

    @Test
    fun onCreateView_notMatchingViews_returnNull() {
        // GIVEN
        val layoutInflaterFactory =
            createNotifLayoutInflaterFactoryImpl(
                setOf(
                    createReplacementViewFactory("TextView") { context, attrs ->
                        FrameLayout(context)
                    }
                )
            )

        // WHEN
        val createView = layoutInflaterFactory.onCreateView("ImageView", mContext, attrs)

        // THEN
        assertNull(createView)
    }

    @Test
    fun onCreateView_matchingViews_returnReplacementView() {
        // GIVEN
        val layoutInflaterFactory =
            createNotifLayoutInflaterFactoryImpl(
                setOf(
                    createReplacementViewFactory("TextView") { context, attrs ->
                        FrameLayout(context)
                    }
                )
            )

        // WHEN
        val createView = layoutInflaterFactory.onCreateView("TextView", mContext, attrs)

        // THEN
        assertNotNull(createView)
        assertEquals(requireNotNull(createView)::class.java, FrameLayout::class.java)
    }

    @Test(expected = IllegalStateException::class)
    fun onCreateView_multipleFactory_throwIllegalStateException() {
        // GIVEN
        val layoutInflaterFactory =
            createNotifLayoutInflaterFactoryImpl(
                setOf(
                    createReplacementViewFactory("TextView") { context, attrs ->
                        FrameLayout(context)
                    },
                    createReplacementViewFactory("TextView") { context, attrs ->
                        LinearLayout(context)
                    }
                )
            )

        // WHEN
        layoutInflaterFactory.onCreateView("TextView", mContext, attrs)
    }

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

    private fun createReplacementViewFactory(
        replacementName: String,
        createView: (context: Context, attrs: AttributeSet) -> View
    ) =
        object : NotifRemoteViewsFactory {
            override fun instantiate(
                parent: View?,
                name: String,
                context: Context,
                attrs: AttributeSet
            ): View? =
                if (replacementName == name) {
                    createView(context, attrs)
                } else {
                    null
                }
        }
}
Loading