New Pipeline: Extend the NotifPipeline to include a render stage with callbacks
* RenderStageManager implements the pipeline behavior for handoff and listeners. * ViewRenderer interface (implemented by ShadeViewManager) defines view update API. * Implement a StackCoordinator (and similar logic in NotificationViewHierarchyManager) which sets notification stats on the NotifStackController rather than letting this be calculated by the view. This is an example conversion to unidirectional data flow. Bug: 204674942 Test: atest NotifPipelineTest PreparationCoordinatorTest NodeSpecBuilderTest RenderStageManagerTest StackCoordinatorTest GroupCountCoordinatorTest Change-Id: I7b2296504202a9cbe87d5d3ead9bd3afffa00aab
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.systemui.statusbar;
|
||||
|
||||
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Handler;
|
||||
@@ -39,6 +41,8 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
|
||||
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
|
||||
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
|
||||
@@ -97,6 +101,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
|
||||
private final Context mContext;
|
||||
|
||||
private NotificationPresenter mPresenter;
|
||||
private NotifStackController mStackController;
|
||||
private NotificationListContainer mListContainer;
|
||||
|
||||
// Used to help track down re-entrant calls to our update methods, which will cause bugs.
|
||||
@@ -147,8 +152,10 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
|
||||
}
|
||||
|
||||
public void setUpWithPresenter(NotificationPresenter presenter,
|
||||
NotifStackController stackController,
|
||||
NotificationListContainer listContainer) {
|
||||
mPresenter = presenter;
|
||||
mStackController = stackController;
|
||||
mListContainer = listContainer;
|
||||
if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
|
||||
mDynamicPrivacyController.addListener(this);
|
||||
@@ -328,12 +335,62 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
|
||||
mTmpChildOrderMap.clear();
|
||||
|
||||
updateRowStatesInternal();
|
||||
updateNotifStats();
|
||||
|
||||
mListContainer.onNotificationViewUpdateFinished();
|
||||
|
||||
endUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* In the spirit of unidirectional data flow, calculate this information when the notification
|
||||
* views are updated, and set it once, speeding up lookups later.
|
||||
* This is analogous to logic in the
|
||||
* {@link com.android.systemui.statusbar.notification.collection.coordinator.StackCoordinator}
|
||||
*/
|
||||
private void updateNotifStats() {
|
||||
boolean hasNonClearableAlertingNotifs = false;
|
||||
boolean hasClearableAlertingNotifs = false;
|
||||
boolean hasNonClearableSilentNotifs = false;
|
||||
boolean hasClearableSilentNotifs = false;
|
||||
final int childCount = mListContainer.getContainerChildCount();
|
||||
int visibleTopLevelEntries = 0;
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = mListContainer.getContainerChildAt(i);
|
||||
if (child == null || child.getVisibility() == View.GONE) {
|
||||
continue;
|
||||
}
|
||||
if (!(child instanceof ExpandableNotificationRow)) {
|
||||
continue;
|
||||
}
|
||||
final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
|
||||
boolean isSilent = row.getEntry().getBucket() == BUCKET_SILENT;
|
||||
// NOTE: NotificationEntry.isClearable() will internally check group children to ensure
|
||||
// the group itself definitively clearable.
|
||||
boolean isClearable = row.getEntry().isClearable();
|
||||
if (isSilent) {
|
||||
if (isClearable) {
|
||||
hasClearableSilentNotifs = true;
|
||||
} else { // !isClearable
|
||||
hasNonClearableSilentNotifs = true;
|
||||
}
|
||||
} else { // !isSilent
|
||||
if (isClearable) {
|
||||
hasClearableAlertingNotifs = true;
|
||||
} else { // !isClearable
|
||||
hasNonClearableAlertingNotifs = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
mStackController.setNotifStats(new NotifStats(
|
||||
visibleTopLevelEntries /* numActiveNotifs */,
|
||||
hasNonClearableAlertingNotifs /* hasNonClearableAlertingNotifs */,
|
||||
hasClearableAlertingNotifs /* hasClearableAlertingNotifs */,
|
||||
hasNonClearableSilentNotifs /* hasNonClearableSilentNotifs */,
|
||||
hasClearableSilentNotifs /* hasClearableSilentNotifs */
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Should a notification entry from the active list be suppressed and not show?
|
||||
*/
|
||||
@@ -528,9 +585,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
|
||||
|
||||
@Override
|
||||
public void onDynamicPrivacyChanged() {
|
||||
if (mNotifPipelineFlags.isNewPipelineEnabled()) {
|
||||
throw new IllegalStateException("Old pipeline code running w/ new pipeline enabled");
|
||||
}
|
||||
mNotifPipelineFlags.assertLegacyPipelineEnabled();
|
||||
if (mPerformingUpdate) {
|
||||
Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call");
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ import com.android.systemui.flags.Flags
|
||||
import javax.inject.Inject
|
||||
|
||||
class NotifPipelineFlags @Inject constructor(
|
||||
val context: Context,
|
||||
val featureFlags: FeatureFlags
|
||||
val context: Context,
|
||||
val featureFlags: FeatureFlags
|
||||
) {
|
||||
fun checkLegacyPipelineEnabled(): Boolean {
|
||||
if (!featureFlags.isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING)) {
|
||||
if (!isNewPipelineEnabled()) {
|
||||
return true
|
||||
}
|
||||
Log.d("NotifPipeline", "Old pipeline code running w/ new pipeline enabled", Exception())
|
||||
@@ -36,10 +36,13 @@ class NotifPipelineFlags @Inject constructor(
|
||||
return false
|
||||
}
|
||||
|
||||
fun isNewPipelineEnabled(): Boolean = featureFlags.isEnabled(
|
||||
Flags.NEW_NOTIFICATION_PIPELINE_RENDERING)
|
||||
fun assertLegacyPipelineEnabled(): Nothing =
|
||||
error("Old pipeline code running w/ new pipeline enabled")
|
||||
|
||||
fun isNewPipelineEnabled(): Boolean =
|
||||
featureFlags.isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING)
|
||||
|
||||
fun isSmartspaceDedupingEnabled(): Boolean =
|
||||
featureFlags.isEnabled(Flags.SMARTSPACE)
|
||||
&& featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
|
||||
featureFlags.isEnabled(Flags.SMARTSPACE) &&
|
||||
featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
|
||||
}
|
||||
@@ -19,8 +19,6 @@ package com.android.systemui.statusbar.notification.collection;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -61,24 +59,6 @@ public class GroupEntry extends ListEntry {
|
||||
mSummary = summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getUntruncatedChildCount()
|
||||
*/
|
||||
public void setUntruncatedChildCount(int childCount) {
|
||||
mUntruncatedChildCount = childCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the untruncated number of children from the data model, including those that will not
|
||||
* have views bound. This includes children that {@link PreparationCoordinator} will filter out
|
||||
* entirely when they are beyond the last visible child.
|
||||
*
|
||||
* TODO: This should move to some shared class between the model and view hierarchy
|
||||
*/
|
||||
public int getUntruncatedChildCount() {
|
||||
return mUntruncatedChildCount;
|
||||
}
|
||||
|
||||
void clearChildren() {
|
||||
mChildren.clear();
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public class NotifInflaterImpl implements NotifInflater {
|
||||
public void onAsyncInflationFinished(NotificationEntry entry) {
|
||||
mNotifErrorManager.clearInflationError(entry);
|
||||
if (callback != null) {
|
||||
callback.onInflationFinished(entry);
|
||||
callback.onInflationFinished(entry, entry.getRowController());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
package com.android.systemui.statusbar.notification.collection
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener
|
||||
@@ -31,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.In
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
|
||||
import com.android.systemui.statusbar.notification.collection.render.RenderStageManager
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@@ -65,11 +69,15 @@ import javax.inject.Inject
|
||||
* 9. Finalize filters are fired on each notification ([.addFinalizeFilter])
|
||||
* 10. OnBeforeRenderListListeners are fired ([.addOnBeforeRenderListListener])
|
||||
* 11. The list is handed off to the view layer to be rendered
|
||||
* 12. OnAfterRenderListListeners are fired ([.addOnAfterRenderListListener])
|
||||
* 13. OnAfterRenderGroupListeners are fired ([.addOnAfterRenderGroupListener])
|
||||
* 13. OnAfterRenderEntryListeners are fired ([.addOnAfterRenderEntryListener])
|
||||
*/
|
||||
@SysUISingleton
|
||||
class NotifPipeline @Inject constructor(
|
||||
private val mNotifCollection: NotifCollection,
|
||||
private val mShadeListBuilder: ShadeListBuilder
|
||||
private val mShadeListBuilder: ShadeListBuilder,
|
||||
private val mRenderStageManager: RenderStageManager
|
||||
) : CommonNotifCollection {
|
||||
/**
|
||||
* Returns the list of all known notifications, i.e. the notifications that are currently posted
|
||||
@@ -205,6 +213,28 @@ class NotifPipeline @Inject constructor(
|
||||
mShadeListBuilder.addPreRenderInvalidator(invalidator)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end of the pipeline after the notif list has been handed off to the view layer.
|
||||
*/
|
||||
fun addOnAfterRenderListListener(listener: OnAfterRenderListListener) {
|
||||
mRenderStageManager.addOnAfterRenderListListener(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end of the pipeline after a group has been handed off to the view layer.
|
||||
*/
|
||||
fun addOnAfterRenderGroupListener(listener: OnAfterRenderGroupListener) {
|
||||
mRenderStageManager.addOnAfterRenderGroupListener(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end of the pipeline after an entry has been handed off to the view layer.
|
||||
* This will be called for every top level entry, every group summary, and every group child.
|
||||
*/
|
||||
fun addOnAfterRenderEntryListener(listener: OnAfterRenderEntryListener) {
|
||||
mRenderStageManager.addOnAfterRenderEntryListener(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object which can be used to update a notification (internally to the pipeline)
|
||||
* in response to a user action.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.android.systemui.statusbar.notification.collection.coordinator
|
||||
|
||||
import android.util.ArrayMap
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntry
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
|
||||
import javax.inject.Inject
|
||||
|
||||
/** A small coordinator which calculates, stores, and applies the untruncated child count. */
|
||||
@CoordinatorScope
|
||||
class GroupCountCoordinator @Inject constructor() : Coordinator {
|
||||
private val untruncatedChildCounts = ArrayMap<GroupEntry, Int>()
|
||||
|
||||
override fun attach(pipeline: NotifPipeline) {
|
||||
pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter)
|
||||
pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroup)
|
||||
}
|
||||
|
||||
private fun onBeforeFinalizeFilter(entries: List<ListEntry>) {
|
||||
// save untruncated child counts to our internal map
|
||||
untruncatedChildCounts.clear()
|
||||
entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
|
||||
untruncatedChildCounts[groupEntry] = groupEntry.children.size
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAfterRenderGroup(group: GroupEntry, controller: NotifGroupController) {
|
||||
// find the untruncated child count for a group and apply it to the controller
|
||||
val count = untruncatedChildCounts[group]
|
||||
checkNotNull(count) { "No untruncated child count for group: ${group.key}" }
|
||||
controller.setUntruncatedChildCount(count)
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,10 @@ class NotifCoordinatorsImpl @Inject constructor(
|
||||
gutsCoordinator: GutsCoordinator,
|
||||
conversationCoordinator: ConversationCoordinator,
|
||||
preparationCoordinator: PreparationCoordinator,
|
||||
groupCountCoordinator: GroupCountCoordinator,
|
||||
mediaCoordinator: MediaCoordinator,
|
||||
remoteInputCoordinator: RemoteInputCoordinator,
|
||||
stackCoordinator: StackCoordinator,
|
||||
shadeEventCoordinator: ShadeEventCoordinator,
|
||||
smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
|
||||
viewConfigCoordinator: ViewConfigCoordinator,
|
||||
@@ -72,8 +74,10 @@ class NotifCoordinatorsImpl @Inject constructor(
|
||||
mCoordinators.add(deviceProvisionedCoordinator)
|
||||
mCoordinators.add(bubbleCoordinator)
|
||||
mCoordinators.add(conversationCoordinator)
|
||||
mCoordinators.add(groupCountCoordinator)
|
||||
mCoordinators.add(mediaCoordinator)
|
||||
mCoordinators.add(remoteInputCoordinator)
|
||||
mCoordinators.add(stackCoordinator)
|
||||
mCoordinators.add(shadeEventCoordinator)
|
||||
mCoordinators.add(viewConfigCoordinator)
|
||||
mCoordinators.add(visualStabilityCoordinator)
|
||||
@@ -89,7 +93,7 @@ class NotifCoordinatorsImpl @Inject constructor(
|
||||
}
|
||||
|
||||
// Manually add Ordered Sections
|
||||
// HeadsUp > FGS > People > Alerting > Silent > Unknown/Default
|
||||
// HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default
|
||||
if (notifPipelineFlags.isNewPipelineEnabled()) {
|
||||
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
|
||||
}
|
||||
|
||||
@@ -31,19 +31,19 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
|
||||
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
|
||||
import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
|
||||
import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustment;
|
||||
import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
|
||||
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
|
||||
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener;
|
||||
|
||||
@@ -61,8 +61,7 @@ import javax.inject.Inject;
|
||||
* If a notification was uninflated, this coordinator will filter the notification out from the
|
||||
* {@link ShadeListBuilder} until it is inflated.
|
||||
*/
|
||||
// TODO(b/204468557): Move to @CoordinatorScope
|
||||
@SysUISingleton
|
||||
@CoordinatorScope
|
||||
public class PreparationCoordinator implements Coordinator {
|
||||
private static final String TAG = "PreparationCoordinator";
|
||||
|
||||
@@ -145,7 +144,7 @@ public class PreparationCoordinator implements Coordinator {
|
||||
|
||||
pipeline.addCollectionListener(mNotifCollectionListener);
|
||||
// Inflate after grouping/sorting since that affects what views to inflate.
|
||||
pipeline.addOnBeforeFinalizeFilterListener(mOnBeforeFinalizeFilterListener);
|
||||
pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllRequiredViews);
|
||||
pipeline.addFinalizeFilter(mNotifInflationErrorFilter);
|
||||
pipeline.addFinalizeFilter(mNotifInflatingFilter);
|
||||
}
|
||||
@@ -182,9 +181,6 @@ public class PreparationCoordinator implements Coordinator {
|
||||
}
|
||||
};
|
||||
|
||||
private final OnBeforeFinalizeFilterListener mOnBeforeFinalizeFilterListener =
|
||||
entries -> inflateAllRequiredViews(entries);
|
||||
|
||||
private final NotifFilter mNotifInflationErrorFilter = new NotifFilter(
|
||||
TAG + "InflationError") {
|
||||
/**
|
||||
@@ -256,7 +252,6 @@ public class PreparationCoordinator implements Coordinator {
|
||||
ListEntry entry = entries.get(i);
|
||||
if (entry instanceof GroupEntry) {
|
||||
GroupEntry groupEntry = (GroupEntry) entry;
|
||||
groupEntry.setUntruncatedChildCount(groupEntry.getChildren().size());
|
||||
inflateRequiredGroupViews(groupEntry);
|
||||
} else {
|
||||
NotificationEntry notifEntry = (NotificationEntry) entry;
|
||||
@@ -363,10 +358,10 @@ public class PreparationCoordinator implements Coordinator {
|
||||
mInflatingNotifs.remove(entry);
|
||||
}
|
||||
|
||||
private void onInflationFinished(NotificationEntry entry) {
|
||||
private void onInflationFinished(NotificationEntry entry, NotifViewController controller) {
|
||||
mLogger.logNotifInflated(entry.getKey());
|
||||
mInflatingNotifs.remove(entry);
|
||||
mViewBarn.registerViewForEntry(entry, entry.getRowController());
|
||||
mViewBarn.registerViewForEntry(entry, controller);
|
||||
mInflationStates.put(entry, STATE_INFLATED);
|
||||
mNotifInflatingFilter.invalidateList();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.service.notification.NotificationListenerService.REASON_CLICK
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.statusbar.NotificationRemoteInputManager
|
||||
@@ -32,6 +31,7 @@ import com.android.systemui.statusbar.RemoteInputNotificationRebuilder
|
||||
import com.android.systemui.statusbar.SmartReplyController
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.SelfTrackingLifetimeExtender
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
|
||||
@@ -61,7 +61,7 @@ private const val REMOTE_INPUT_EXTENDER_RELEASE_DELAY: Long = 200
|
||||
/** Whether this class should print spammy debug logs */
|
||||
private val DEBUG: Boolean by lazy { Log.isLoggable(TAG, Log.DEBUG) }
|
||||
|
||||
@SysUISingleton
|
||||
@CoordinatorScope
|
||||
class RemoteInputCoordinator @Inject constructor(
|
||||
dumpManager: DumpManager,
|
||||
private val mRebuilder: RemoteInputNotificationRebuilder,
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.collection.coordinator
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStats
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
|
||||
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
|
||||
import com.android.systemui.statusbar.phone.NotificationIconAreaController
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* A small coordinator which updates the notif stack (the view layer which holds notifications)
|
||||
* with high-level data after the stack is populated with the final entries.
|
||||
*/
|
||||
@CoordinatorScope
|
||||
class StackCoordinator @Inject internal constructor(
|
||||
private val notificationIconAreaController: NotificationIconAreaController
|
||||
) : Coordinator {
|
||||
|
||||
override fun attach(pipeline: NotifPipeline) {
|
||||
pipeline.addOnAfterRenderListListener(::onAfterRenderList)
|
||||
}
|
||||
|
||||
fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) {
|
||||
controller.setNotifStats(calculateNotifStats(entries))
|
||||
notificationIconAreaController.updateNotificationIcons(entries)
|
||||
}
|
||||
|
||||
private fun calculateNotifStats(entries: List<ListEntry>): NotifStats {
|
||||
var hasNonClearableAlertingNotifs = false
|
||||
var hasClearableAlertingNotifs = false
|
||||
var hasNonClearableSilentNotifs = false
|
||||
var hasClearableSilentNotifs = false
|
||||
entries.forEach {
|
||||
val isSilent = it.section!!.bucket == BUCKET_SILENT
|
||||
// NOTE: NotificationEntry.isClearable will internally check group children to ensure
|
||||
// the group itself definitively clearable.
|
||||
val isClearable = it.representativeEntry!!.isClearable
|
||||
when {
|
||||
isSilent && isClearable -> hasClearableSilentNotifs = true
|
||||
isSilent && !isClearable -> hasNonClearableSilentNotifs = true
|
||||
!isSilent && isClearable -> hasClearableAlertingNotifs = true
|
||||
!isSilent && !isClearable -> hasNonClearableAlertingNotifs = true
|
||||
}
|
||||
}
|
||||
val stats = NotifStats(
|
||||
numActiveNotifs = entries.size,
|
||||
hasNonClearableAlertingNotifs = hasNonClearableAlertingNotifs,
|
||||
hasClearableAlertingNotifs = hasClearableAlertingNotifs,
|
||||
hasNonClearableSilentNotifs = hasNonClearableSilentNotifs,
|
||||
hasClearableSilentNotifs = hasClearableSilentNotifs
|
||||
)
|
||||
return stats
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.systemui.statusbar.notification.collection.inflation
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifViewController
|
||||
|
||||
/**
|
||||
* Used by the [PreparationCoordinator]. When notifications are added or updated, the
|
||||
@@ -49,7 +50,7 @@ interface NotifInflater {
|
||||
* Callback once all the views are inflated and bound for a given NotificationEntry.
|
||||
*/
|
||||
interface InflationCallback {
|
||||
fun onInflationFinished(entry: NotificationEntry)
|
||||
fun onInflationFinished(entry: NotificationEntry, controller: NotifViewController)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,8 @@ import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
|
||||
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
|
||||
import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
|
||||
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
|
||||
import com.android.systemui.statusbar.notification.collection.render.RenderStageManager;
|
||||
import com.android.systemui.statusbar.notification.collection.render.ShadeViewManagerFactory;
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
|
||||
|
||||
@@ -47,6 +49,7 @@ public class NotifPipelineInitializer implements Dumpable {
|
||||
private final GroupCoalescer mGroupCoalescer;
|
||||
private final NotifCollection mNotifCollection;
|
||||
private final ShadeListBuilder mListBuilder;
|
||||
private final RenderStageManager mRenderStageManager;
|
||||
private final NotifCoordinators mNotifPluggableCoordinators;
|
||||
private final NotifInflaterImpl mNotifInflater;
|
||||
private final DumpManager mDumpManager;
|
||||
@@ -60,15 +63,18 @@ public class NotifPipelineInitializer implements Dumpable {
|
||||
GroupCoalescer groupCoalescer,
|
||||
NotifCollection notifCollection,
|
||||
ShadeListBuilder listBuilder,
|
||||
RenderStageManager renderStageManager,
|
||||
NotifCoordinators notifCoordinators,
|
||||
NotifInflaterImpl notifInflater,
|
||||
DumpManager dumpManager,
|
||||
ShadeViewManagerFactory shadeViewManagerFactory,
|
||||
NotifPipelineFlags notifPipelineFlags) {
|
||||
NotifPipelineFlags notifPipelineFlags
|
||||
) {
|
||||
mPipelineWrapper = pipelineWrapper;
|
||||
mGroupCoalescer = groupCoalescer;
|
||||
mNotifCollection = notifCollection;
|
||||
mListBuilder = listBuilder;
|
||||
mRenderStageManager = renderStageManager;
|
||||
mNotifPluggableCoordinators = notifCoordinators;
|
||||
mDumpManager = dumpManager;
|
||||
mNotifInflater = notifInflater;
|
||||
@@ -80,7 +86,8 @@ public class NotifPipelineInitializer implements Dumpable {
|
||||
public void initialize(
|
||||
NotificationListener notificationService,
|
||||
NotificationRowBinderImpl rowBinder,
|
||||
NotificationListContainer listContainer) {
|
||||
NotificationListContainer listContainer,
|
||||
NotifStackController stackController) {
|
||||
|
||||
mDumpManager.registerDumpable("NotifPipeline", this);
|
||||
|
||||
@@ -94,8 +101,11 @@ public class NotifPipelineInitializer implements Dumpable {
|
||||
|
||||
// Wire up pipeline
|
||||
if (mNotifPipelineFlags.isNewPipelineEnabled()) {
|
||||
mShadeViewManagerFactory.create(listContainer).attach(mListBuilder);
|
||||
mShadeViewManagerFactory
|
||||
.create(listContainer, stackController)
|
||||
.attach(mRenderStageManager);
|
||||
}
|
||||
mRenderStageManager.attach(mListBuilder);
|
||||
mListBuilder.attach(mNotifCollection);
|
||||
mNotifCollection.attach(mGroupCoalescer);
|
||||
mGroupCoalescer.attach(notificationService);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.collection.listbuilder;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifRowController;
|
||||
|
||||
/** See {@link NotifPipeline#addOnAfterRenderEntryListener(OnAfterRenderEntryListener)} */
|
||||
public interface OnAfterRenderEntryListener {
|
||||
/**
|
||||
* Called at the end of the pipeline after an entry has been handed off to the view layer.
|
||||
* This will be called for every top level entry, every group summary, and every group child.
|
||||
*
|
||||
* @param entry the entry to read from.
|
||||
* @param controller the object to which data can be pushed.
|
||||
*/
|
||||
void onAfterRenderEntry(
|
||||
@NonNull NotificationEntry entry,
|
||||
@NonNull NotifRowController controller);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.collection.listbuilder;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifGroupController;
|
||||
|
||||
/** See {@link NotifPipeline#addOnAfterRenderGroupListener(OnAfterRenderGroupListener)} */
|
||||
public interface OnAfterRenderGroupListener {
|
||||
/**
|
||||
* Called at the end of the pipeline after a group has been handed off to the view layer.
|
||||
*
|
||||
* @param groupEntry the entry for the group itself.
|
||||
* @param controller the object to which data can be pushed.
|
||||
*/
|
||||
void onAfterRenderGroup(
|
||||
@NonNull GroupEntry groupEntry,
|
||||
@NonNull NotifGroupController controller);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.collection.listbuilder;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** See {@link NotifPipeline#addOnAfterRenderListListener(OnAfterRenderListListener)} */
|
||||
public interface OnAfterRenderListListener {
|
||||
/**
|
||||
* Called at the end of the pipeline after the notif list has been handed off to the view layer.
|
||||
*
|
||||
* @param entries The current list of top-level entries. Note that this is a live view into the
|
||||
* current list and will change whenever the pipeline is rerun.
|
||||
* @param controller An object for setting state on the shade.
|
||||
*/
|
||||
void onAfterRenderList(
|
||||
@NonNull List<ListEntry> entries,
|
||||
@NonNull NotifStackController controller);
|
||||
}
|
||||
@@ -64,12 +64,13 @@ class NodeSpecBuilder(
|
||||
root.children.add(buildNotifNode(root, entry))
|
||||
}
|
||||
|
||||
return root
|
||||
return@traceSection root
|
||||
}
|
||||
|
||||
private fun buildNotifNode(parent: NodeSpec, entry: ListEntry): NodeSpec = when (entry) {
|
||||
is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireView(entry))
|
||||
is GroupEntry -> NodeSpecImpl(parent, viewBarn.requireView(checkNotNull(entry.summary)))
|
||||
is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireNodeController(entry))
|
||||
is GroupEntry ->
|
||||
NodeSpecImpl(parent, viewBarn.requireNodeController(checkNotNull(entry.summary)))
|
||||
.apply { entry.children.forEach { children.add(buildNotifNode(this, it)) } }
|
||||
else -> throw RuntimeException("Unexpected entry: $entry")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.collection.render
|
||||
|
||||
/** A view controller for a notification group row */
|
||||
interface NotifGroupController {
|
||||
/** Set the number of children that this group would have if not for the 8-child max */
|
||||
fun setUntruncatedChildCount(untruncatedChildCount: Int)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.collection.render
|
||||
|
||||
/** A view controller for a notification row */
|
||||
interface NotifRowController
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.collection.render
|
||||
|
||||
/** An interface by which the pipeline can make updates to the notification root view. */
|
||||
interface NotifStackController {
|
||||
/** Provides stats about the list of notifications attached to the shade */
|
||||
fun setNotifStats(stats: NotifStats)
|
||||
}
|
||||
|
||||
/** Data provided to the NotificationRootController whenever the pipeline runs */
|
||||
data class NotifStats(
|
||||
val numActiveNotifs: Int,
|
||||
val hasNonClearableAlertingNotifs: Boolean,
|
||||
val hasClearableAlertingNotifs: Boolean,
|
||||
val hasNonClearableSilentNotifs: Boolean,
|
||||
val hasClearableSilentNotifs: Boolean
|
||||
) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val empty = NotifStats(0, false, false, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of NotifStackController which provides default, no-op implementations of each
|
||||
* method. This is used by ArcSystemUI so that that implementation can opt-in to overriding
|
||||
* methods, rather than forcing us to add no-op implementations in their implementation every time
|
||||
* a method is added.
|
||||
*/
|
||||
open class DefaultNotifStackController : NotifStackController {
|
||||
override fun setNotifStats(stats: NotifStats) {}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render
|
||||
import android.view.textclassifier.Log
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@@ -26,30 +27,39 @@ import javax.inject.Inject
|
||||
*/
|
||||
@SysUISingleton
|
||||
class NotifViewBarn @Inject constructor() {
|
||||
private val rowMap = mutableMapOf<String, NodeController>()
|
||||
private val rowMap = mutableMapOf<String, NotifViewController>()
|
||||
|
||||
fun requireView(forEntry: ListEntry): NodeController {
|
||||
fun requireNodeController(entry: ListEntry): NodeController {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "requireView: $forEntry.key")
|
||||
Log.d(TAG, "requireNodeController: ${entry.key}")
|
||||
}
|
||||
val li = rowMap[forEntry.key]
|
||||
if (li == null) {
|
||||
throw IllegalStateException("No view has been registered for entry: $forEntry")
|
||||
}
|
||||
|
||||
return li
|
||||
return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}")
|
||||
}
|
||||
|
||||
fun registerViewForEntry(entry: ListEntry, controller: NodeController) {
|
||||
fun requireGroupController(entry: NotificationEntry): NotifGroupController {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "registerViewForEntry: $entry.key")
|
||||
Log.d(TAG, "requireGroupController: ${entry.key}")
|
||||
}
|
||||
return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}")
|
||||
}
|
||||
|
||||
fun requireRowController(entry: NotificationEntry): NotifRowController {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "requireRowController: ${entry.key}")
|
||||
}
|
||||
return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}")
|
||||
}
|
||||
|
||||
fun registerViewForEntry(entry: ListEntry, controller: NotifViewController) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "registerViewForEntry: ${entry.key}")
|
||||
}
|
||||
rowMap[entry.key] = controller
|
||||
}
|
||||
|
||||
fun removeViewForEntry(entry: ListEntry) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "removeViewForEntry: $entry.key")
|
||||
Log.d(TAG, "removeViewForEntry: ${entry.key}")
|
||||
}
|
||||
rowMap.remove(entry.key)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.collection.render
|
||||
|
||||
interface NotifViewController : NotifGroupController, NotifRowController, NodeController
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.collection.render
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntry
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
|
||||
/**
|
||||
* This interface and the interfaces it returns define the main API surface that must be
|
||||
* implemented by the view implementation. The term "render" is used to indicate a handoff
|
||||
* to the view system, whether that be to attach views to the hierarchy or to update independent
|
||||
* view models, data stores, or adapters.
|
||||
*/
|
||||
interface NotifViewRenderer {
|
||||
|
||||
/**
|
||||
* Hand off the list of notifications to the view implementation. This may attach views to the
|
||||
* hierarchy or simply update an independent datastore, but once called, the implementer myst
|
||||
* also ensure that future calls to [getStackController], [getGroupController], and
|
||||
* [getRowController] will provide valid results.
|
||||
*/
|
||||
fun onRenderList(notifList: List<ListEntry>)
|
||||
|
||||
/**
|
||||
* Provides an interface for the pipeline to update the overall shade.
|
||||
* This will be called at most once for each time [onRenderList] is called.
|
||||
*/
|
||||
fun getStackController(): NotifStackController
|
||||
|
||||
/**
|
||||
* Provides an interface for the pipeline to update individual groups.
|
||||
* This will be called at most once for each group in the most recent call to [onRenderList].
|
||||
*/
|
||||
fun getGroupController(group: GroupEntry): NotifGroupController
|
||||
|
||||
/**
|
||||
* Provides an interface for the pipeline to update individual entries.
|
||||
* This will be called at most once for each entry in the most recent call to [onRenderList].
|
||||
* This includes top level entries, group summaries, and group children.
|
||||
*/
|
||||
fun getRowController(entry: NotificationEntry): NotifRowController
|
||||
|
||||
/**
|
||||
* Invoked after the render stage manager has finished dispatching to all of the listeners.
|
||||
*
|
||||
* This is an opportunity for the view system to do any cleanup or trigger any finalization
|
||||
* logic now that all data from the pipeline is known to have been set for this execution.
|
||||
*
|
||||
* When this is called, the view system can expect that no more calls will be made to the
|
||||
* getters on this interface until after the next call to [onRenderList]. Additionally, there
|
||||
* should be no further calls made on the objects previously returned by those getters.
|
||||
*/
|
||||
fun onDispatchComplete() {}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.collection.render
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntry
|
||||
|
||||
/**
|
||||
* Extension used during the render stage which assumes the summary exists, and throws a more
|
||||
* helpful error if not.
|
||||
*/
|
||||
inline val GroupEntry.requireSummary get() = checkNotNull(summary) { "No Summary: $this" }
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.collection.render
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntry
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
|
||||
import com.android.systemui.util.traceSection
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* The class which is part of the pipeline which guarantees a consistent that coordinators get a
|
||||
* consistent interface to the view system regardless of the [NotifViewRenderer] implementation
|
||||
* provided to [setViewRenderer].
|
||||
*/
|
||||
@SysUISingleton
|
||||
class RenderStageManager @Inject constructor() {
|
||||
private val onAfterRenderListListeners = mutableListOf<OnAfterRenderListListener>()
|
||||
private val onAfterRenderGroupListeners = mutableListOf<OnAfterRenderGroupListener>()
|
||||
private val onAfterRenderEntryListeners = mutableListOf<OnAfterRenderEntryListener>()
|
||||
private var viewRenderer: NotifViewRenderer? = null
|
||||
|
||||
/** Attach this stage to the rest of the pipeline */
|
||||
fun attach(listBuilder: ShadeListBuilder) {
|
||||
listBuilder.setOnRenderListListener(::onRenderList)
|
||||
}
|
||||
|
||||
private fun onRenderList(notifList: List<ListEntry>) {
|
||||
traceSection("RenderStageManager.onRenderList") {
|
||||
val viewRenderer = viewRenderer ?: return
|
||||
viewRenderer.onRenderList(notifList)
|
||||
dispatchOnAfterRenderList(viewRenderer, notifList)
|
||||
dispatchOnAfterRenderGroups(viewRenderer, notifList)
|
||||
dispatchOnAfterRenderEntries(viewRenderer, notifList)
|
||||
viewRenderer.onDispatchComplete()
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides this class with the view rendering implementation. */
|
||||
fun setViewRenderer(renderer: NotifViewRenderer) {
|
||||
viewRenderer = renderer
|
||||
}
|
||||
|
||||
/** Adds a listener that will get a single callback after rendering the list. */
|
||||
fun addOnAfterRenderListListener(listener: OnAfterRenderListListener) {
|
||||
onAfterRenderListListeners.add(listener)
|
||||
}
|
||||
|
||||
/** Adds a listener that will get a callback for each group rendered. */
|
||||
fun addOnAfterRenderGroupListener(listener: OnAfterRenderGroupListener) {
|
||||
onAfterRenderGroupListeners.add(listener)
|
||||
}
|
||||
|
||||
/** Adds a listener that will get a callback for each entry rendered. */
|
||||
fun addOnAfterRenderEntryListener(listener: OnAfterRenderEntryListener) {
|
||||
onAfterRenderEntryListeners.add(listener)
|
||||
}
|
||||
|
||||
private fun dispatchOnAfterRenderList(
|
||||
viewRenderer: NotifViewRenderer,
|
||||
entries: List<ListEntry>
|
||||
) {
|
||||
traceSection("RenderStageManager.dispatchOnAfterRenderList") {
|
||||
val stackController = viewRenderer.getStackController()
|
||||
onAfterRenderListListeners.forEach { listener ->
|
||||
listener.onAfterRenderList(entries, stackController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dispatchOnAfterRenderGroups(
|
||||
viewRenderer: NotifViewRenderer,
|
||||
entries: List<ListEntry>
|
||||
) {
|
||||
traceSection("RenderStageManager.dispatchOnAfterRenderGroups") {
|
||||
if (onAfterRenderGroupListeners.isEmpty()) {
|
||||
return
|
||||
}
|
||||
entries.asSequence().filterIsInstance<GroupEntry>().forEach { group ->
|
||||
val controller = viewRenderer.getGroupController(group)
|
||||
onAfterRenderGroupListeners.forEach { listener ->
|
||||
listener.onAfterRenderGroup(group, controller)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dispatchOnAfterRenderEntries(
|
||||
viewRenderer: NotifViewRenderer,
|
||||
entries: List<ListEntry>
|
||||
) {
|
||||
traceSection("RenderStageManager.dispatchOnAfterRenderEntries") {
|
||||
if (onAfterRenderEntryListeners.isEmpty()) {
|
||||
return
|
||||
}
|
||||
entries.forEachNotificationEntry { entry ->
|
||||
val controller = viewRenderer.getRowController(entry)
|
||||
onAfterRenderEntryListeners.forEach { listener ->
|
||||
listener.onAfterRenderEntry(entry, controller)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a forward, depth-first traversal of the list where the group's summary
|
||||
* immediately precedes the group's children.
|
||||
*/
|
||||
private inline fun List<ListEntry>.forEachNotificationEntry(
|
||||
action: (NotificationEntry) -> Unit
|
||||
) {
|
||||
forEach { entry ->
|
||||
when (entry) {
|
||||
is NotificationEntry -> action(entry)
|
||||
is GroupEntry -> {
|
||||
action(entry.requireSummary)
|
||||
entry.children.forEach(action)
|
||||
}
|
||||
else -> error("Unhandled entry: $entry")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,8 @@ import android.content.Context
|
||||
import android.view.View
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntry
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry
|
||||
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
|
||||
import com.android.systemui.statusbar.phone.NotificationIconAreaController
|
||||
import com.android.systemui.util.traceSection
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -34,9 +32,9 @@ import javax.inject.Inject
|
||||
class ShadeViewManager constructor(
|
||||
context: Context,
|
||||
listContainer: NotificationListContainer,
|
||||
private val stackController: NotifStackController,
|
||||
logger: ShadeViewDifferLogger,
|
||||
private val viewBarn: NotifViewBarn,
|
||||
private val notificationIconAreaController: NotificationIconAreaController
|
||||
private val viewBarn: NotifViewBarn
|
||||
) {
|
||||
// We pass a shim view here because the listContainer may not actually have a view associated
|
||||
// with it and the differ never actually cares about the root node's view.
|
||||
@@ -44,39 +42,39 @@ class ShadeViewManager constructor(
|
||||
private val specBuilder = NodeSpecBuilder(viewBarn)
|
||||
private val viewDiffer = ShadeViewDiffer(rootController, logger)
|
||||
|
||||
fun attach(listBuilder: ShadeListBuilder) =
|
||||
listBuilder.setOnRenderListListener(::onNewNotifTree)
|
||||
|
||||
private fun onNewNotifTree(notifList: List<ListEntry>) {
|
||||
traceSection("ShadeViewManager.onNewNotifTree") {
|
||||
viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
|
||||
updateGroupCounts(notifList)
|
||||
notificationIconAreaController.updateNotificationIcons(notifList)
|
||||
}
|
||||
/** Method for attaching this manager to the pipeline. */
|
||||
fun attach(renderStageManager: RenderStageManager) {
|
||||
renderStageManager.setViewRenderer(viewRenderer)
|
||||
}
|
||||
|
||||
private fun updateGroupCounts(notifList: List<ListEntry>) {
|
||||
traceSection("ShadeViewManager.updateGroupCounts") {
|
||||
notifList.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
|
||||
val controller = viewBarn.requireView(checkNotNull(groupEntry.summary))
|
||||
val row = controller.view as ExpandableNotificationRow
|
||||
row.setUntruncatedChildCount(groupEntry.untruncatedChildCount)
|
||||
private val viewRenderer = object : NotifViewRenderer {
|
||||
|
||||
override fun onRenderList(notifList: List<ListEntry>) {
|
||||
traceSection("ShadeViewManager.onRenderList") {
|
||||
viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStackController(): NotifStackController = stackController
|
||||
|
||||
override fun getGroupController(group: GroupEntry): NotifGroupController =
|
||||
viewBarn.requireGroupController(group.requireSummary)
|
||||
|
||||
override fun getRowController(entry: NotificationEntry): NotifRowController =
|
||||
viewBarn.requireRowController(entry)
|
||||
}
|
||||
}
|
||||
|
||||
class ShadeViewManagerFactory @Inject constructor(
|
||||
private val context: Context,
|
||||
private val logger: ShadeViewDifferLogger,
|
||||
private val viewBarn: NotifViewBarn,
|
||||
private val notificationIconAreaController: NotificationIconAreaController
|
||||
private val viewBarn: NotifViewBarn
|
||||
) {
|
||||
fun create(listContainer: NotificationListContainer) =
|
||||
ShadeViewManager(
|
||||
context,
|
||||
listContainer,
|
||||
logger,
|
||||
viewBarn,
|
||||
notificationIconAreaController)
|
||||
fun create(listContainer: NotificationListContainer, stackController: NotifStackController) =
|
||||
ShadeViewManager(
|
||||
context,
|
||||
listContainer,
|
||||
stackController,
|
||||
logger,
|
||||
viewBarn)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.Snoo
|
||||
import com.android.systemui.statusbar.NotificationPresenter
|
||||
import com.android.systemui.statusbar.notification.NotificationActivityStarter
|
||||
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
|
||||
import com.android.systemui.statusbar.phone.StatusBar
|
||||
import com.android.wm.shell.bubbles.Bubbles
|
||||
@@ -40,6 +41,7 @@ interface NotificationsController {
|
||||
bubblesOptional: Optional<Bubbles>,
|
||||
presenter: NotificationPresenter,
|
||||
listContainer: NotificationListContainer,
|
||||
stackController: NotifStackController,
|
||||
notificationActivityStarter: NotificationActivityStarter,
|
||||
bindRowCallback: NotificationRowBinderImpl.BindRowCallback
|
||||
)
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
|
||||
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
|
||||
import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
|
||||
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
|
||||
import com.android.systemui.statusbar.notification.interruption.HeadsUpController
|
||||
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
|
||||
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
|
||||
@@ -47,7 +48,7 @@ import com.android.wm.shell.bubbles.Bubbles
|
||||
import dagger.Lazy
|
||||
import java.io.FileDescriptor
|
||||
import java.io.PrintWriter
|
||||
import java.util.*
|
||||
import java.util.Optional
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@@ -85,6 +86,7 @@ class NotificationsControllerImpl @Inject constructor(
|
||||
bubblesOptional: Optional<Bubbles>,
|
||||
presenter: NotificationPresenter,
|
||||
listContainer: NotificationListContainer,
|
||||
stackController: NotifStackController,
|
||||
notificationActivityStarter: NotificationActivityStarter,
|
||||
bindRowCallback: NotificationRowBinderImpl.BindRowCallback
|
||||
) {
|
||||
@@ -112,7 +114,8 @@ class NotificationsControllerImpl @Inject constructor(
|
||||
newNotifPipeline.get().initialize(
|
||||
notificationListener,
|
||||
notificationRowBinder,
|
||||
listContainer)
|
||||
listContainer,
|
||||
stackController)
|
||||
}
|
||||
|
||||
if (notifPipelineFlags.isNewPipelineEnabled()) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.android.systemui.statusbar.NotificationListener
|
||||
import com.android.systemui.statusbar.NotificationPresenter
|
||||
import com.android.systemui.statusbar.notification.NotificationActivityStarter
|
||||
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
|
||||
import com.android.systemui.statusbar.phone.StatusBar
|
||||
import com.android.wm.shell.bubbles.Bubbles
|
||||
@@ -42,6 +43,7 @@ class NotificationsControllerStub @Inject constructor(
|
||||
bubblesOptional: Optional<Bubbles>,
|
||||
presenter: NotificationPresenter,
|
||||
listContainer: NotificationListContainer,
|
||||
stackController: NotifStackController,
|
||||
notificationActivityStarter: NotificationActivityStarter,
|
||||
bindRowCallback: NotificationRowBinderImpl.BindRowCallback
|
||||
) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME
|
||||
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
|
||||
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@@ -36,6 +37,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
|
||||
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NodeController;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
|
||||
import com.android.systemui.statusbar.notification.row.dagger.AppName;
|
||||
@@ -58,7 +60,8 @@ import javax.inject.Named;
|
||||
* Controller for {@link ExpandableNotificationRow}.
|
||||
*/
|
||||
@NotificationRowScope
|
||||
public class ExpandableNotificationRowController implements NodeController {
|
||||
public class ExpandableNotificationRowController implements NotifViewController {
|
||||
private static final String TAG = "NotifRowController";
|
||||
private final ExpandableNotificationRow mView;
|
||||
private final NotificationListContainer mListContainer;
|
||||
private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory;
|
||||
@@ -267,4 +270,13 @@ public class ExpandableNotificationRowController implements NodeController {
|
||||
final List<ExpandableNotificationRow> mChildren = mView.getAttachedChildren();
|
||||
return mChildren != null ? mChildren.size() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUntruncatedChildCount(int childCount) {
|
||||
if (mView.isSummaryWithChildren()) {
|
||||
mView.setUntruncatedChildCount(childCount);
|
||||
} else {
|
||||
Log.w(TAG, "Called setUntruncatedChildCount(" + childCount + ") on a leaf row");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,7 +678,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
// TODO: move this logic to controller, which will invoke updateFooterView directly
|
||||
boolean showDismissView = mClearAllEnabled &&
|
||||
mController.hasActiveClearableNotifications(ROWS_ALL);
|
||||
boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0)
|
||||
boolean showFooterView = (showDismissView || mController.getVisibleNotificationCount() > 0)
|
||||
&& mIsCurrentUserSetup // see: b/193149550
|
||||
&& mStatusBarState != StatusBarState.KEYGUARD
|
||||
&& !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
|
||||
@@ -1173,20 +1173,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns best effort count of visible notifications.
|
||||
*/
|
||||
public int getVisibleNotificationCount() {
|
||||
int count = 0;
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
final View child = getChildAt(i);
|
||||
if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
|
||||
private boolean isCurrentlyAnimating() {
|
||||
return mStateAnimator.isRunning();
|
||||
@@ -1458,7 +1444,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
|
||||
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
|
||||
private float getAppearEndPosition() {
|
||||
int appearPosition = 0;
|
||||
int visibleNotifCount = getVisibleNotificationCount();
|
||||
int visibleNotifCount = mController.getVisibleNotificationCount();
|
||||
if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) {
|
||||
if (isHeadsUpTransition()
|
||||
|| (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) {
|
||||
|
||||
@@ -48,6 +48,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -98,6 +99,8 @@ import com.android.systemui.statusbar.notification.collection.legacy.VisualStabi
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
|
||||
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
|
||||
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
|
||||
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
|
||||
@@ -192,6 +195,8 @@ public class NotificationStackScrollLayoutController {
|
||||
|
||||
private final NotificationListContainerImpl mNotificationListContainer =
|
||||
new NotificationListContainerImpl();
|
||||
private final NotifStackController mNotifStackController =
|
||||
new NotifStackControllerImpl();
|
||||
|
||||
@Nullable
|
||||
private NotificationActivityStarter mNotificationActivityStarter;
|
||||
@@ -294,6 +299,8 @@ public class NotificationStackScrollLayoutController {
|
||||
}
|
||||
};
|
||||
|
||||
private NotifStats mNotifStats = NotifStats.getEmpty();
|
||||
|
||||
private void updateResources() {
|
||||
mNotificationDragDownMovement = mResources.getDimensionPixelSize(
|
||||
R.dimen.lockscreen_shade_notification_movement);
|
||||
@@ -988,7 +995,7 @@ public class NotificationStackScrollLayoutController {
|
||||
}
|
||||
|
||||
public int getVisibleNotificationCount() {
|
||||
return mView.getVisibleNotificationCount();
|
||||
return mNotifStats.getNumActiveNotifs();
|
||||
}
|
||||
|
||||
public int getIntrinsicContentHeight() {
|
||||
@@ -1183,7 +1190,7 @@ public class NotificationStackScrollLayoutController {
|
||||
public void updateShowEmptyShadeView() {
|
||||
mShowEmptyShadeView = mBarState != KEYGUARD
|
||||
&& (!mView.isQsExpanded() || mView.isUsingSplitNotificationShade())
|
||||
&& mView.getVisibleNotificationCount() == 0;
|
||||
&& getVisibleNotificationCount() == 0;
|
||||
|
||||
mView.updateEmptyShadeView(
|
||||
mShowEmptyShadeView,
|
||||
@@ -1246,29 +1253,22 @@ public class NotificationStackScrollLayoutController {
|
||||
}
|
||||
|
||||
public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
|
||||
if (mDynamicPrivacyController.isInLockedDownShade()) {
|
||||
return false;
|
||||
boolean hasAlertingMatchingClearable = isClearable
|
||||
? mNotifStats.getHasClearableAlertingNotifs()
|
||||
: mNotifStats.getHasNonClearableAlertingNotifs();
|
||||
boolean hasSilentMatchingClearable = isClearable
|
||||
? mNotifStats.getHasClearableSilentNotifs()
|
||||
: mNotifStats.getHasNonClearableSilentNotifs();
|
||||
switch (selection) {
|
||||
case ROWS_GENTLE:
|
||||
return hasSilentMatchingClearable;
|
||||
case ROWS_HIGH_PRIORITY:
|
||||
return hasAlertingMatchingClearable;
|
||||
case ROWS_ALL:
|
||||
return hasSilentMatchingClearable || hasAlertingMatchingClearable;
|
||||
default:
|
||||
throw new IllegalStateException("Bad selection: " + selection);
|
||||
}
|
||||
int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (!(child instanceof ExpandableNotificationRow)) {
|
||||
continue;
|
||||
}
|
||||
final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
|
||||
final boolean matchClearable =
|
||||
isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed();
|
||||
final boolean inSection =
|
||||
NotificationStackScrollLayout.matchesSelection(row, selection);
|
||||
if (matchClearable && inSection) {
|
||||
if (mLegacyGroupManager == null
|
||||
|| !mLegacyGroupManager.isSummaryOfSuppressedGroup(
|
||||
row.getEntry().getSbn())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1383,6 +1383,10 @@ public class NotificationStackScrollLayoutController {
|
||||
return mNotificationListContainer;
|
||||
}
|
||||
|
||||
public NotifStackController getNotifStackController() {
|
||||
return mNotifStackController;
|
||||
}
|
||||
|
||||
public void resetCheckSnoozeLeavebehind() {
|
||||
mView.resetCheckSnoozeLeavebehind();
|
||||
}
|
||||
@@ -1394,17 +1398,6 @@ public class NotificationStackScrollLayoutController {
|
||||
mVisibilityProvider.obtain(entry, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the shade has currently any active notifications.
|
||||
*/
|
||||
public boolean hasActiveNotifications() {
|
||||
if (mNotifPipelineFlags.isNewPipelineEnabled()) {
|
||||
return !mNotifPipeline.getShadeList().isEmpty();
|
||||
} else {
|
||||
return mNotificationEntryManager.hasActiveNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
public void closeControlsIfOutsideTouch(MotionEvent ev) {
|
||||
NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
|
||||
NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
|
||||
@@ -1876,4 +1869,13 @@ public class NotificationStackScrollLayoutController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NotifStackControllerImpl implements NotifStackController {
|
||||
@Override
|
||||
public void setNotifStats(@NonNull NotifStats notifStats) {
|
||||
mNotifStats = notifStats;
|
||||
updateFooter();
|
||||
updateShowEmptyShadeView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1488,6 +1488,7 @@ public class StatusBar extends CoreStartable implements
|
||||
mBubblesOptional,
|
||||
mPresenter,
|
||||
mStackScrollerController.getNotificationListContainer(),
|
||||
mStackScrollerController.getNotifStackController(),
|
||||
mNotificationActivityStarter,
|
||||
mPresenter);
|
||||
}
|
||||
|
||||
@@ -192,6 +192,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
|
||||
initController.addPostInitTask(() -> {
|
||||
mKeyguardIndicationController.init();
|
||||
mViewHierarchyManager.setUpWithPresenter(this,
|
||||
stackScrollerController.getNotifStackController(),
|
||||
stackScrollerController.getNotificationListContainer());
|
||||
mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
|
||||
mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.android.systemui.Dependency;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
|
||||
@@ -54,6 +55,7 @@ import org.mockito.MockitoAnnotations;
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
public class NonPhoneDependencyTest extends SysuiTestCase {
|
||||
@Mock private NotificationPresenter mPresenter;
|
||||
@Mock private NotifStackController mStackController;
|
||||
@Mock private NotificationListContainer mListContainer;
|
||||
@Mock
|
||||
private NotificationEntryListener mEntryListener;
|
||||
@@ -95,7 +97,7 @@ public class NonPhoneDependencyTest extends SysuiTestCase {
|
||||
remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback,
|
||||
mDelegate);
|
||||
lockscreenUserManager.setUpWithPresenter(mPresenter);
|
||||
viewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
|
||||
viewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
|
||||
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
|
||||
|
||||
@@ -48,6 +48,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
|
||||
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
|
||||
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableView;
|
||||
@@ -74,6 +75,7 @@ import java.util.Optional;
|
||||
@TestableLooper.RunWithLooper
|
||||
public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
|
||||
@Mock private NotificationPresenter mPresenter;
|
||||
@Mock private NotifStackController mStackController;
|
||||
@Spy private FakeListContainer mListContainer = new FakeListContainer();
|
||||
|
||||
// Dependency mocks:
|
||||
@@ -122,7 +124,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
|
||||
mock(LowPriorityInflationHelper.class),
|
||||
mock(AssistantFeedbackController.class),
|
||||
mNotifPipelineFlags);
|
||||
mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
|
||||
mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
|
||||
}
|
||||
|
||||
private NotificationEntry createEntry() throws Exception {
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.notification.collection.render.RenderStageManager
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@@ -33,12 +34,13 @@ class NotifPipelineTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var notifCollection: NotifCollection
|
||||
@Mock private lateinit var shadeListBuilder: ShadeListBuilder
|
||||
@Mock private lateinit var renderStageManager: RenderStageManager
|
||||
private lateinit var notifPipeline: NotifPipeline
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
notifPipeline = NotifPipeline(notifCollection, shadeListBuilder)
|
||||
notifPipeline = NotifPipeline(notifCollection, shadeListBuilder, renderStageManager)
|
||||
whenever(shadeListBuilder.shadeList).thenReturn(listOf(
|
||||
NotificationEntryBuilder().setPkg("foo").setId(1).build(),
|
||||
NotificationEntryBuilder().setPkg("foo").setId(2).build(),
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.collection.coordinator
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.withArgCaptor
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations.initMocks
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@RunWithLooper
|
||||
class GroupCountCoordinatorTest : SysuiTestCase() {
|
||||
private lateinit var coordinator: GroupCountCoordinator
|
||||
private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
|
||||
private lateinit var afterRenderGroupListener: OnAfterRenderGroupListener
|
||||
|
||||
private lateinit var summaryEntry: NotificationEntry
|
||||
private lateinit var childEntry1: NotificationEntry
|
||||
private lateinit var childEntry2: NotificationEntry
|
||||
|
||||
@Mock private lateinit var pipeline: NotifPipeline
|
||||
@Mock private lateinit var groupController: NotifGroupController
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
initMocks(this)
|
||||
coordinator = GroupCountCoordinator()
|
||||
coordinator.attach(pipeline)
|
||||
beforeFinalizeFilterListener = withArgCaptor {
|
||||
verify(pipeline).addOnBeforeFinalizeFilterListener(capture())
|
||||
}
|
||||
afterRenderGroupListener = withArgCaptor {
|
||||
verify(pipeline).addOnAfterRenderGroupListener(capture())
|
||||
}
|
||||
summaryEntry = NotificationEntryBuilder().setId(0).build()
|
||||
childEntry1 = NotificationEntryBuilder().setId(1).build()
|
||||
childEntry2 = NotificationEntryBuilder().setId(2).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetUntruncatedChildCount() {
|
||||
val groupEntry = GroupEntryBuilder()
|
||||
.setSummary(summaryEntry)
|
||||
.setChildren(listOf(childEntry1, childEntry2))
|
||||
.build()
|
||||
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
|
||||
afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
|
||||
verify(groupController).setUntruncatedChildCount(eq(2))
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,6 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
|
||||
@Captor private ArgumentCaptor<NotifCollectionListener> mCollectionListenerCaptor;
|
||||
@Captor private ArgumentCaptor<OnBeforeFinalizeFilterListener> mBeforeFilterListenerCaptor;
|
||||
@Captor private ArgumentCaptor<NotifInflater.InflationCallback> mCallbackCaptor;
|
||||
@Captor private ArgumentCaptor<NotifInflater.Params> mParamsCaptor;
|
||||
|
||||
@Mock private NotifSectioner mNotifSectioner;
|
||||
@@ -180,8 +179,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
// GIVEN an inflated notification
|
||||
mCollectionListener.onEntryAdded(mEntry);
|
||||
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture());
|
||||
mCallbackCaptor.getValue().onInflationFinished(mEntry);
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
|
||||
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
|
||||
|
||||
// WHEN notification is updated
|
||||
mCollectionListener.onEntryUpdated(mEntry);
|
||||
@@ -199,8 +198,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
// GIVEN an inflated notification
|
||||
mCollectionListener.onEntryAdded(mEntry);
|
||||
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture());
|
||||
mCallbackCaptor.getValue().onInflationFinished(mEntry);
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
|
||||
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
|
||||
|
||||
// WHEN notification ranking now has smart replies
|
||||
mEntry.setRanking(new RankingBuilder(mEntry.getRanking()).setSmartReplies("yes").build());
|
||||
@@ -218,10 +217,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
// GIVEN an inflated notification
|
||||
mCollectionListener.onEntryAdded(mEntry);
|
||||
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry),
|
||||
mParamsCaptor.capture(), mCallbackCaptor.capture());
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
|
||||
assertFalse(mParamsCaptor.getValue().isLowPriority());
|
||||
mCallbackCaptor.getValue().onInflationFinished(mEntry);
|
||||
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
|
||||
|
||||
// WHEN notification moves to a min priority section
|
||||
mAdjustmentProvider.setSectionIsLowPriority(true);
|
||||
@@ -241,10 +239,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
mAdjustmentProvider.setSectionIsLowPriority(true);
|
||||
mCollectionListener.onEntryAdded(mEntry);
|
||||
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry),
|
||||
mParamsCaptor.capture(), mCallbackCaptor.capture());
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
|
||||
assertTrue(mParamsCaptor.getValue().isLowPriority());
|
||||
mCallbackCaptor.getValue().onInflationFinished(mEntry);
|
||||
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
|
||||
|
||||
// WHEN notification is moved under a parent
|
||||
NotificationEntryBuilder.setNewParent(mEntry, mock(GroupEntry.class));
|
||||
@@ -263,8 +260,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
// GIVEN an inflated notification
|
||||
mCollectionListener.onEntryAdded(mEntry);
|
||||
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture());
|
||||
mCallbackCaptor.getValue().onInflationFinished(mEntry);
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
|
||||
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
|
||||
|
||||
// WHEN notification ranking changes rank, which does not affect views
|
||||
mEntry.setRanking(new RankingBuilder(mEntry.getRanking()).setRank(100).build());
|
||||
@@ -282,8 +279,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
// GIVEN an inflated notification
|
||||
mCollectionListener.onEntryAdded(mEntry);
|
||||
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture());
|
||||
mCallbackCaptor.getValue().onInflationFinished(mEntry);
|
||||
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
|
||||
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
|
||||
|
||||
// THEN it isn't filtered from shade list
|
||||
assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0));
|
||||
@@ -347,7 +344,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
|
||||
|
||||
// WHEN one of this children finishes inflating
|
||||
mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
|
||||
mNotifInflater.invokeInflateCallbackForEntry(child0);
|
||||
|
||||
// THEN the inflated child is still filtered out
|
||||
assertTrue(mUninflatedFilter.shouldFilterOut(child0, 401));
|
||||
@@ -369,8 +366,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
|
||||
|
||||
// WHEN all of the children (but not the summary) finish inflating
|
||||
mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
|
||||
mNotifInflater.getInflateCallback(child1).onInflationFinished(child1);
|
||||
mNotifInflater.invokeInflateCallbackForEntry(child0);
|
||||
mNotifInflater.invokeInflateCallbackForEntry(child1);
|
||||
|
||||
// THEN the entire group is still filtered out
|
||||
assertTrue(mUninflatedFilter.shouldFilterOut(summary, 401));
|
||||
@@ -394,9 +391,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
|
||||
|
||||
// WHEN all of the children (and the summary) finish inflating
|
||||
mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
|
||||
mNotifInflater.getInflateCallback(child1).onInflationFinished(child1);
|
||||
mNotifInflater.getInflateCallback(summary).onInflationFinished(summary);
|
||||
mNotifInflater.invokeInflateCallbackForEntry(child0);
|
||||
mNotifInflater.invokeInflateCallbackForEntry(child1);
|
||||
mNotifInflater.invokeInflateCallbackForEntry(summary);
|
||||
|
||||
// THEN the entire group is still filtered out
|
||||
assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
|
||||
@@ -418,7 +415,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
|
||||
|
||||
// WHEN one of this children finishes inflating and enough time passes
|
||||
mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
|
||||
mNotifInflater.invokeInflateCallbackForEntry(child0);
|
||||
|
||||
// THEN the inflated child is not filtered out even though the rest of the group hasn't
|
||||
// finished inflating yet
|
||||
@@ -446,6 +443,10 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
|
||||
public InflationCallback getInflateCallback(NotificationEntry entry) {
|
||||
return requireNonNull(mInflateCallbacks.get(entry));
|
||||
}
|
||||
|
||||
public void invokeInflateCallbackForEntry(NotificationEntry entry) {
|
||||
getInflateCallback(entry).onInflationFinished(entry, entry.getRowController());
|
||||
}
|
||||
}
|
||||
|
||||
private void fireAddEvents(List<? extends ListEntry> entries) {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.collection.coordinator
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
|
||||
import com.android.systemui.statusbar.notification.collection.render.NotifStats
|
||||
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
|
||||
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
|
||||
import com.android.systemui.statusbar.phone.NotificationIconAreaController
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.withArgCaptor
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations.initMocks
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@RunWithLooper
|
||||
class StackCoordinatorTest : SysuiTestCase() {
|
||||
private lateinit var coordinator: StackCoordinator
|
||||
private lateinit var afterRenderListListener: OnAfterRenderListListener
|
||||
|
||||
private lateinit var entry: NotificationEntry
|
||||
|
||||
@Mock private lateinit var pipeline: NotifPipeline
|
||||
@Mock private lateinit var notificationIconAreaController: NotificationIconAreaController
|
||||
@Mock private lateinit var stackController: NotifStackController
|
||||
@Mock private lateinit var section: NotifSection
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
initMocks(this)
|
||||
coordinator = StackCoordinator(notificationIconAreaController)
|
||||
coordinator.attach(pipeline)
|
||||
afterRenderListListener = withArgCaptor {
|
||||
verify(pipeline).addOnAfterRenderListListener(capture())
|
||||
}
|
||||
entry = NotificationEntryBuilder().setSection(section).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateNotificationIcons() {
|
||||
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
|
||||
verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetNotificationStats_clearableAlerting() {
|
||||
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
|
||||
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
|
||||
verify(stackController).setNotifStats(NotifStats(1, false, true, false, false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetNotificationStats_clearableSilent() {
|
||||
whenever(section.bucket).thenReturn(BUCKET_SILENT)
|
||||
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
|
||||
verify(stackController).setNotifStats(NotifStats(1, false, false, false, true))
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class NodeSpecBuilderTest : SysuiTestCase() {
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
`when`(viewBarn.requireView(any())).thenAnswer {
|
||||
`when`(viewBarn.requireNodeController(any())).thenAnswer {
|
||||
fakeViewBarn.getViewByEntry(it.getArgument(0))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.collection.render
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntry
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.withArgCaptor
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.inOrder
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.verifyNoMoreInteractions
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
class RenderStageManagerTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var shadeListBuilder: ShadeListBuilder
|
||||
@Mock private lateinit var onAfterRenderListListener: OnAfterRenderListListener
|
||||
@Mock private lateinit var onAfterRenderGroupListener: OnAfterRenderGroupListener
|
||||
@Mock private lateinit var onAfterRenderEntryListener: OnAfterRenderEntryListener
|
||||
|
||||
private lateinit var onRenderListListener: ShadeListBuilder.OnRenderListListener
|
||||
private lateinit var renderStageManager: RenderStageManager
|
||||
private val spyViewRenderer = spy(FakeNotifViewRenderer())
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
renderStageManager = RenderStageManager()
|
||||
renderStageManager.attach(shadeListBuilder)
|
||||
onRenderListListener = withArgCaptor {
|
||||
verify(shadeListBuilder).setOnRenderListListener(capture())
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpRenderer() {
|
||||
renderStageManager.setViewRenderer(spyViewRenderer)
|
||||
}
|
||||
|
||||
private fun setUpListeners() {
|
||||
renderStageManager.addOnAfterRenderListListener(onAfterRenderListListener)
|
||||
renderStageManager.addOnAfterRenderGroupListener(onAfterRenderGroupListener)
|
||||
renderStageManager.addOnAfterRenderEntryListener(onAfterRenderEntryListener)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoCallbacksWithoutRenderer() {
|
||||
// GIVEN listeners but no renderer
|
||||
setUpListeners()
|
||||
|
||||
// WHEN a shade list is built
|
||||
onRenderListListener.onRenderList(listWith2Groups8Entries())
|
||||
|
||||
// VERIFY that no listeners are called
|
||||
verifyNoMoreInteractions(
|
||||
onAfterRenderListListener,
|
||||
onAfterRenderGroupListener,
|
||||
onAfterRenderEntryListener
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDoesNotQueryControllerIfNoListeners() {
|
||||
// GIVEN a renderer but no listeners
|
||||
setUpRenderer()
|
||||
|
||||
// WHEN a shade list is built
|
||||
onRenderListListener.onRenderList(listWith2Groups8Entries())
|
||||
|
||||
// VERIFY that the renderer is not queried for group or row controllers
|
||||
inOrder(spyViewRenderer).apply {
|
||||
verify(spyViewRenderer, times(1)).onRenderList(any())
|
||||
verify(spyViewRenderer, times(1)).getStackController()
|
||||
verify(spyViewRenderer, never()).getGroupController(any())
|
||||
verify(spyViewRenderer, never()).getRowController(any())
|
||||
verify(spyViewRenderer, times(1)).onDispatchComplete()
|
||||
verifyNoMoreInteractions(spyViewRenderer)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDoesQueryControllerIfListeners() {
|
||||
// GIVEN a renderer and listeners
|
||||
setUpRenderer()
|
||||
setUpListeners()
|
||||
|
||||
// WHEN a shade list is built
|
||||
onRenderListListener.onRenderList(listWith2Groups8Entries())
|
||||
|
||||
// VERIFY that the renderer is queried once per group/entry
|
||||
inOrder(spyViewRenderer).apply {
|
||||
verify(spyViewRenderer, times(1)).onRenderList(any())
|
||||
verify(spyViewRenderer, times(1)).getStackController()
|
||||
verify(spyViewRenderer, times(2)).getGroupController(any())
|
||||
verify(spyViewRenderer, times(8)).getRowController(any())
|
||||
verify(spyViewRenderer, times(1)).onDispatchComplete()
|
||||
verifyNoMoreInteractions(spyViewRenderer)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDoesNotQueryControllerTwice() {
|
||||
// GIVEN a renderer and multiple distinct listeners
|
||||
setUpRenderer()
|
||||
setUpListeners()
|
||||
renderStageManager.addOnAfterRenderListListener(mock())
|
||||
renderStageManager.addOnAfterRenderGroupListener(mock())
|
||||
renderStageManager.addOnAfterRenderEntryListener(mock())
|
||||
|
||||
// WHEN a shade list is built
|
||||
onRenderListListener.onRenderList(listWith2Groups8Entries())
|
||||
|
||||
// VERIFY that the renderer is queried once per group/entry
|
||||
inOrder(spyViewRenderer).apply {
|
||||
verify(spyViewRenderer, times(1)).onRenderList(any())
|
||||
verify(spyViewRenderer, times(1)).getStackController()
|
||||
verify(spyViewRenderer, times(2)).getGroupController(any())
|
||||
verify(spyViewRenderer, times(8)).getRowController(any())
|
||||
verify(spyViewRenderer, times(1)).onDispatchComplete()
|
||||
verifyNoMoreInteractions(spyViewRenderer)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDoesCallListenerWithEachGroupAndEntry() {
|
||||
// GIVEN a renderer and multiple distinct listeners
|
||||
setUpRenderer()
|
||||
setUpListeners()
|
||||
|
||||
// WHEN a shade list is built
|
||||
onRenderListListener.onRenderList(listWith2Groups8Entries())
|
||||
|
||||
// VERIFY that the listeners are invoked once per group and once per entry
|
||||
verify(onAfterRenderListListener, times(1)).onAfterRenderList(any(), any())
|
||||
verify(onAfterRenderGroupListener, times(2)).onAfterRenderGroup(any(), any())
|
||||
verify(onAfterRenderEntryListener, times(8)).onAfterRenderEntry(any(), any())
|
||||
verifyNoMoreInteractions(
|
||||
onAfterRenderListListener,
|
||||
onAfterRenderGroupListener,
|
||||
onAfterRenderEntryListener
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDoesNotCallGroupAndEntryListenersIfTheListIsEmpty() {
|
||||
// GIVEN a renderer and multiple distinct listeners
|
||||
setUpRenderer()
|
||||
setUpListeners()
|
||||
|
||||
// WHEN a shade list is built empty
|
||||
onRenderListListener.onRenderList(listOf())
|
||||
|
||||
// VERIFY that the stack listener is invoked once but other listeners are not
|
||||
verify(onAfterRenderListListener, times(1)).onAfterRenderList(any(), any())
|
||||
verify(onAfterRenderGroupListener, never()).onAfterRenderGroup(any(), any())
|
||||
verify(onAfterRenderEntryListener, never()).onAfterRenderEntry(any(), any())
|
||||
verifyNoMoreInteractions(
|
||||
onAfterRenderListListener,
|
||||
onAfterRenderGroupListener,
|
||||
onAfterRenderEntryListener
|
||||
)
|
||||
}
|
||||
|
||||
private fun listWith2Groups8Entries() = listOf(
|
||||
group(
|
||||
notif(1),
|
||||
notif(2),
|
||||
notif(3)
|
||||
),
|
||||
notif(4),
|
||||
group(
|
||||
notif(5),
|
||||
notif(6),
|
||||
notif(7)
|
||||
),
|
||||
notif(8)
|
||||
)
|
||||
|
||||
private class FakeNotifViewRenderer : NotifViewRenderer {
|
||||
override fun onRenderList(notifList: List<ListEntry>) {}
|
||||
override fun getStackController(): NotifStackController = mock()
|
||||
override fun getGroupController(group: GroupEntry): NotifGroupController = mock()
|
||||
override fun getRowController(entry: NotificationEntry): NotifRowController = mock()
|
||||
override fun onDispatchComplete() {}
|
||||
}
|
||||
|
||||
private fun notif(id: Int): NotificationEntry = NotificationEntryBuilder().setId(id).build()
|
||||
|
||||
private fun group(summary: NotificationEntry, vararg children: NotificationEntry): GroupEntry =
|
||||
GroupEntryBuilder().setSummary(summary).setChildren(children.toList()).build()
|
||||
}
|
||||
@@ -30,7 +30,6 @@ import static junit.framework.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
@@ -296,14 +295,10 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
|
||||
setBarStateForTest(StatusBarState.SHADE);
|
||||
mStackScroller.setCurrentUserSetup(true);
|
||||
|
||||
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
|
||||
when(row.canViewBeDismissed()).thenReturn(true);
|
||||
when(mStackScroller.getChildCount()).thenReturn(1);
|
||||
when(mStackScroller.getChildAt(anyInt())).thenReturn(row);
|
||||
mStackScroller.setIsRemoteInputActive(true);
|
||||
when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
|
||||
when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
|
||||
when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
|
||||
.thenReturn(true);
|
||||
when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
|
||||
|
||||
FooterView view = mock(FooterView.class);
|
||||
mStackScroller.setFooterView(view);
|
||||
@@ -311,15 +306,29 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
|
||||
verify(mStackScroller).updateFooterView(false, true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateFooter_withoutNotifications() {
|
||||
setBarStateForTest(StatusBarState.SHADE);
|
||||
mStackScroller.setCurrentUserSetup(true);
|
||||
|
||||
when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
|
||||
when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
|
||||
.thenReturn(false);
|
||||
|
||||
FooterView view = mock(FooterView.class);
|
||||
mStackScroller.setFooterView(view);
|
||||
mStackScroller.updateFooter();
|
||||
verify(mStackScroller).updateFooterView(false, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateFooter_oneClearableNotification() {
|
||||
setBarStateForTest(StatusBarState.SHADE);
|
||||
mStackScroller.setCurrentUserSetup(true);
|
||||
|
||||
when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
|
||||
when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
|
||||
when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
|
||||
when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
|
||||
.thenReturn(true);
|
||||
when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
|
||||
|
||||
FooterView view = mock(FooterView.class);
|
||||
mStackScroller.setFooterView(view);
|
||||
@@ -332,10 +341,9 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
|
||||
setBarStateForTest(StatusBarState.SHADE);
|
||||
mStackScroller.setCurrentUserSetup(false);
|
||||
|
||||
when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
|
||||
when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
|
||||
when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
|
||||
when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
|
||||
.thenReturn(true);
|
||||
when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
|
||||
|
||||
FooterView view = mock(FooterView.class);
|
||||
mStackScroller.setFooterView(view);
|
||||
@@ -348,12 +356,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
|
||||
setBarStateForTest(StatusBarState.SHADE);
|
||||
mStackScroller.setCurrentUserSetup(true);
|
||||
|
||||
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
|
||||
when(row.canViewBeDismissed()).thenReturn(false);
|
||||
when(mStackScroller.getChildCount()).thenReturn(1);
|
||||
when(mStackScroller.getChildAt(anyInt())).thenReturn(row);
|
||||
when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
|
||||
when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
|
||||
when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
|
||||
when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
|
||||
.thenReturn(false);
|
||||
when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user