Merge "New Pipeline: Extend the NotifPipeline to include a render stage with callbacks"
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
|
||||
@@ -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