Merge "Set LayoutInflatorFactory to Notification RemoteViews" into udc-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
f9c01162b8
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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?
|
||||
}
|
||||
@@ -79,6 +79,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
private final ConversationNotificationProcessor mConversationProcessor;
|
||||
private final Executor mBgExecutor;
|
||||
private final SmartReplyStateInflater mSmartReplyStateInflater;
|
||||
private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
|
||||
|
||||
@Inject
|
||||
NotificationContentInflater(
|
||||
@@ -87,13 +88,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
ConversationNotificationProcessor conversationProcessor,
|
||||
MediaFeatureFlag mediaFeatureFlag,
|
||||
@Background Executor bgExecutor,
|
||||
SmartReplyStateInflater smartRepliesInflater) {
|
||||
SmartReplyStateInflater smartRepliesInflater,
|
||||
NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
|
||||
mRemoteViewCache = remoteViewCache;
|
||||
mRemoteInputManager = remoteInputManager;
|
||||
mConversationProcessor = conversationProcessor;
|
||||
mIsMediaInQS = mediaFeatureFlag.getEnabled();
|
||||
mBgExecutor = bgExecutor;
|
||||
mSmartReplyStateInflater = smartRepliesInflater;
|
||||
mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,7 +140,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
callback,
|
||||
mRemoteInputManager.getRemoteViewsOnClickHandler(),
|
||||
mIsMediaInQS,
|
||||
mSmartReplyStateInflater);
|
||||
mSmartReplyStateInflater,
|
||||
mNotifLayoutInflaterFactory);
|
||||
if (mInflateSynchronously) {
|
||||
task.onPostExecute(task.doInBackground());
|
||||
} else {
|
||||
@@ -160,7 +164,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
bindParams.isLowPriority,
|
||||
bindParams.usesIncreasedHeight,
|
||||
bindParams.usesIncreasedHeadsUpHeight,
|
||||
packageContext);
|
||||
packageContext,
|
||||
mNotifLayoutInflaterFactory);
|
||||
|
||||
result = inflateSmartReplyViews(result, reInflateFlags, entry,
|
||||
row.getContext(), packageContext,
|
||||
@@ -298,7 +303,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
|
||||
private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags,
|
||||
Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight,
|
||||
boolean usesIncreasedHeadsUpHeight, Context packageContext) {
|
||||
boolean usesIncreasedHeadsUpHeight, Context packageContext,
|
||||
NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
|
||||
InflationProgress result = new InflationProgress();
|
||||
|
||||
if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
|
||||
@@ -316,7 +322,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
|
||||
result.newPublicView = builder.makePublicContentView(isLowPriority);
|
||||
}
|
||||
|
||||
setNotifsViewsInflaterFactory(result, notifLayoutInflaterFactory);
|
||||
result.packageContext = packageContext;
|
||||
result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
|
||||
result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
|
||||
@@ -324,6 +330,22 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
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(
|
||||
Executor bgExecutor,
|
||||
boolean inflateSynchronously,
|
||||
@@ -348,7 +370,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
public void setResultView(View v) {
|
||||
result.inflatedContentView = v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteViews getRemoteView() {
|
||||
return result.newContentView;
|
||||
@@ -356,7 +377,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
};
|
||||
applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
|
||||
remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
|
||||
privateLayout, privateLayout.getContractedChild(),
|
||||
privateLayout, privateLayout.getContractedChild(),
|
||||
privateLayout.getVisibleWrapper(
|
||||
NotificationContentView.VISIBLE_TYPE_CONTRACTED),
|
||||
runningInflations, applyCallback);
|
||||
@@ -758,8 +779,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
* @param oldView The old view that was applied to the existing view before
|
||||
* @return {@code true} if the RemoteViews are the same and the view can be reused to reapply.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static boolean canReapplyRemoteView(final RemoteViews newView,
|
||||
@VisibleForTesting
|
||||
static boolean canReapplyRemoteView(final RemoteViews newView,
|
||||
final RemoteViews oldView) {
|
||||
return (newView == null && oldView == null) ||
|
||||
(newView != null && oldView != null
|
||||
@@ -800,6 +821,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
private final ConversationNotificationProcessor mConversationProcessor;
|
||||
private final boolean mIsMediaInQS;
|
||||
private final SmartReplyStateInflater mSmartRepliesInflater;
|
||||
private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
|
||||
|
||||
private AsyncInflationTask(
|
||||
Executor bgExecutor,
|
||||
@@ -815,7 +837,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
InflationCallback callback,
|
||||
RemoteViews.InteractionHandler remoteViewClickHandler,
|
||||
boolean isMediaFlagEnabled,
|
||||
SmartReplyStateInflater smartRepliesInflater) {
|
||||
SmartReplyStateInflater smartRepliesInflater,
|
||||
NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
|
||||
mEntry = entry;
|
||||
mRow = row;
|
||||
mBgExecutor = bgExecutor;
|
||||
@@ -831,6 +854,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
mCallback = callback;
|
||||
mConversationProcessor = conversationProcessor;
|
||||
mIsMediaInQS = isMediaFlagEnabled;
|
||||
mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
|
||||
entry.setInflationTask(this);
|
||||
}
|
||||
|
||||
@@ -874,7 +898,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
|
||||
}
|
||||
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
|
||||
recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
|
||||
mUsesIncreasedHeadsUpHeight, packageContext);
|
||||
mUsesIncreasedHeadsUpHeight, packageContext,
|
||||
mNotifLayoutInflaterFactory);
|
||||
InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
|
||||
InflationProgress result = inflateSmartReplyViews(
|
||||
inflationProgress,
|
||||
|
||||
@@ -17,15 +17,26 @@
|
||||
package com.android.systemui.statusbar.notification.row;
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
|
||||
import dagger.Binds;
|
||||
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.
|
||||
*/
|
||||
@Module
|
||||
public abstract class NotificationRowModule {
|
||||
public static final String NOTIF_REMOTEVIEWS_FACTORIES =
|
||||
"notif_remoteviews_factories";
|
||||
|
||||
/**
|
||||
* Provides notification row content binder instance.
|
||||
*/
|
||||
@@ -41,4 +52,15 @@ public abstract class NotificationRowModule {
|
||||
@SysUISingleton
|
||||
public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
|
||||
@Mock private ConversationNotificationProcessor mConversationNotificationProcessor;
|
||||
@Mock private InflatedSmartReplyState mInflatedSmartReplyState;
|
||||
@Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies;
|
||||
@Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
|
||||
|
||||
private final SmartReplyStateInflater mSmartReplyStateInflater =
|
||||
new SmartReplyStateInflater() {
|
||||
@@ -130,7 +131,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
|
||||
mConversationNotificationProcessor,
|
||||
mock(MediaFeatureFlag.class),
|
||||
mock(Executor.class),
|
||||
mSmartReplyStateInflater);
|
||||
mSmartReplyStateInflater,
|
||||
mNotifLayoutInflaterFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -158,7 +158,8 @@ public class NotificationTestHelper {
|
||||
mock(ConversationNotificationProcessor.class),
|
||||
mock(MediaFeatureFlag.class),
|
||||
mock(Executor.class),
|
||||
new MockSmartReplyInflater());
|
||||
new MockSmartReplyInflater(),
|
||||
mock(NotifLayoutInflaterFactory.class));
|
||||
contentBinder.setInflateSynchronously(true);
|
||||
mBindStage = new RowContentBindStage(contentBinder,
|
||||
mock(NotifInflationErrorManager.class),
|
||||
|
||||
Reference in New Issue
Block a user