summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java689
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt596
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt136
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java358
15 files changed, 1054 insertions, 857 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2647c04ff586..2baab61d861b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,8 +19,8 @@ import static android.app.Notification.VISIBILITY_SECRET;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
import android.app.ActivityManager;
import android.app.KeyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index d7b391ff03e4..ce6013f776af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -21,12 +21,12 @@ import android.provider.DeviceConfig
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
+import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
+import com.android.systemui.statusbar.notification.stack.BUCKET_MEDIA_CONTROLS
+import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.Utils
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 634872d9d761..22ac1a2e5cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -31,7 +31,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
import static java.util.Objects.requireNonNull;
@@ -68,7 +68,7 @@ import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
+import com.android.systemui.statusbar.notification.stack.PriorityBucket;
import java.util.ArrayList;
import java.util.List;
@@ -409,12 +409,12 @@ public final class NotificationEntry extends ListEntry {
return wasBubble != isBubble();
}
- @NotificationSectionsManager.PriorityBucket
+ @PriorityBucket
public int getBucket() {
return mBucket;
}
- public void setBucket(@NotificationSectionsManager.PriorityBucket int bucket) {
+ public void setBucket(@PriorityBucket int bucket) {
mBucket = bucket;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 9ac42298e539..cbf680c5b782 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -28,12 +28,11 @@ import com.android.systemui.statusbar.notification.NotificationSectionsFeatureMa
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.PriorityBucket
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
+import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
import com.android.systemui.statusbar.phone.NotificationGroupManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import dagger.Lazy
@@ -138,23 +137,8 @@ open class NotificationRankingManager @Inject constructor(
.filterNot(notifFilter::shouldFilterOut)
.sortedWith(rankingComparator)
.toList()
- assignBuckets(filtered)
- return filtered
- }
-
- private fun assignBuckets(entries: List<NotificationEntry>) {
entries.forEach { it.bucket = getBucketForEntry(it) }
- if (!usePeopleFiltering) {
- // If we don't have a Conversation section, just assign buckets normally based on the
- // content.
- return
- }
- // If HUNs are not continuous with the top section, break out into a new Incoming section.
- entries.asReversed().asSequence().zipWithNext().forEach { (next, entry) ->
- if (entry.isRowHeadsUp && entry.bucket > next.bucket) {
- entry.bucket = BUCKET_HEADS_UP
- }
- }
+ return filtered
}
@PriorityBucket
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index 9d456ef785a8..bad36bf3de64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -32,8 +32,8 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
* Represents the bounds of a section of the notification shade and handles animation when the
* bounds change.
*/
-class NotificationSection {
- private @NotificationSectionsManager.PriorityBucket int mBucket;
+public class NotificationSection {
+ private @PriorityBucket int mBucket;
private View mOwningView;
private Rect mBounds = new Rect();
private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
@@ -44,7 +44,7 @@ class NotificationSection {
private ActivatableNotificationView mFirstVisibleChild;
private ActivatableNotificationView mLastVisibleChild;
- NotificationSection(View owningView, @NotificationSectionsManager.PriorityBucket int bucket) {
+ NotificationSection(View owningView, @PriorityBucket int bucket) {
mOwningView = owningView;
mBucket = bucket;
}
@@ -74,7 +74,7 @@ class NotificationSection {
return mBottomAnimator != null || mTopAnimator != null;
}
- @NotificationSectionsManager.PriorityBucket
+ @PriorityBucket
public int getBucket() {
return mBucket;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index 92fdd6441539..17b414379f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -52,14 +52,35 @@ class NotificationSectionsLogger @Inject constructor(
{ "$int1: other ($str1)" }
)
- fun logHeadsUp(position: Int) = logPosition(position, "Heads Up")
- fun logConversation(position: Int) = logPosition(position, "Conversation")
- fun logAlerting(position: Int) = logPosition(position, "Alerting")
- fun logSilent(position: Int) = logPosition(position, "Silent")
- fun logForegroundService(position: Int) = logPosition(position, "Foreground Service")
+ fun logHeadsUp(position: Int, isHeadsUp: Boolean) =
+ logPosition(position, "Heads Up", isHeadsUp)
+ fun logConversation(position: Int, isHeadsUp: Boolean) =
+ logPosition(position, "Conversation", isHeadsUp)
+ fun logAlerting(position: Int, isHeadsUp: Boolean) =
+ logPosition(position, "Alerting", isHeadsUp)
+ fun logSilent(position: Int, isHeadsUp: Boolean) =
+ logPosition(position, "Silent", isHeadsUp)
+ fun logForegroundService(position: Int, isHeadsUp: Boolean) =
+ logPosition(position, "Foreground Service", isHeadsUp)
fun logStr(str: String) = logBuffer.log(TAG, LogLevel.DEBUG, { str1 = str }, { "$str1" })
+ private fun logPosition(position: Int, label: String, isHeadsUp: Boolean) {
+ val headsUpTag = if (isHeadsUp) " (HUN)" else ""
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = position
+ str1 = label
+ str2 = headsUpTag
+ },
+ {
+ "$int1: $str1$str2"
+ }
+ )
+ }
+
private fun logPosition(position: Int, label: String) = logBuffer.log(
TAG,
LogLevel.DEBUG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
deleted file mode 100644
index f3ee2ceaf8ee..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ /dev/null
@@ -1,689 +0,0 @@
-/*
- * 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.stack;
-
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.ColorInt;
-import android.annotation.IntDef;
-import android.annotation.LayoutRes;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Intent;
-import android.provider.Settings;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-import com.android.systemui.media.KeyguardMediaController;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
-import com.android.systemui.statusbar.notification.people.DataListener;
-import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter;
-import com.android.systemui.statusbar.notification.people.PeopleHubViewBoundary;
-import com.android.systemui.statusbar.notification.people.PersonViewModel;
-import com.android.systemui.statusbar.notification.people.Subscription;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-
-import java.lang.annotation.Retention;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import javax.inject.Inject;
-
-import kotlin.sequences.Sequence;
-
-/**
- * Manages the boundaries of the two notification sections (high priority and low priority). Also
- * shows/hides the headers for those sections where appropriate.
- *
- * TODO: Move remaining sections logic from NSSL into this class.
- */
-public class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvider {
-
- private static final String TAG = "NotifSectionsManager";
- private static final boolean DEBUG = false;
- private static final boolean ENABLE_SNOOZED_CONVERSATION_HUB = false;
-
- private final ActivityStarter mActivityStarter;
- private final StatusBarStateController mStatusBarStateController;
- private final ConfigurationController mConfigurationController;
- private final PeopleHubViewAdapter mPeopleHubViewAdapter;
- private final NotificationSectionsFeatureManager mSectionsFeatureManager;
- private final KeyguardMediaController mKeyguardMediaController;
- private final int mNumberOfSections;
- private final NotificationSectionsLogger mLogger;
- private final PeopleHubViewBoundary mPeopleHubViewBoundary = new PeopleHubViewBoundary() {
- @Override
- public void setVisible(boolean isVisible) {
- if (mPeopleHubVisible != isVisible) {
- mPeopleHubVisible = isVisible;
- if (mInitialized) {
- updateSectionBoundaries("PeopleHub visibility changed");
- }
- }
- }
-
- @NonNull
- @Override
- public View getAssociatedViewForClickAnimation() {
- return mPeopleHubView;
- }
-
- @NonNull
- @Override
- public Sequence<DataListener<PersonViewModel>> getPersonViewAdapters() {
- return mPeopleHubView.getPersonViewAdapters();
- }
- };
-
- private NotificationStackScrollLayout mParent;
- private boolean mInitialized = false;
-
- private SectionHeaderView mGentleHeader;
- @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener;
-
- private SectionHeaderView mAlertingHeader;
- private SectionHeaderView mIncomingHeader;
-
- private PeopleHubView mPeopleHubView;
- private boolean mPeopleHubVisible = false;
- @Nullable private Subscription mPeopleHubSubscription;
-
- private MediaHeaderView mMediaControlsView;
-
- @Inject
- NotificationSectionsManager(
- ActivityStarter activityStarter,
- StatusBarStateController statusBarStateController,
- ConfigurationController configurationController,
- PeopleHubViewAdapter peopleHubViewAdapter,
- KeyguardMediaController keyguardMediaController,
- NotificationSectionsFeatureManager sectionsFeatureManager,
- NotificationSectionsLogger logger) {
-
- mActivityStarter = activityStarter;
- mStatusBarStateController = statusBarStateController;
- mConfigurationController = configurationController;
- mPeopleHubViewAdapter = peopleHubViewAdapter;
- mSectionsFeatureManager = sectionsFeatureManager;
- mNumberOfSections = mSectionsFeatureManager.getNumberOfBuckets();
- mKeyguardMediaController = keyguardMediaController;
- mLogger = logger;
- }
-
- NotificationSection[] createSectionsForBuckets() {
- int[] buckets = mSectionsFeatureManager.getNotificationBuckets();
- NotificationSection[] sections = new NotificationSection[buckets.length];
- for (int i = 0; i < buckets.length; i++) {
- sections[i] = new NotificationSection(mParent, buckets[i] /* bucket */);
- }
-
- return sections;
- }
-
- /** Must be called before use. */
- void initialize(
- NotificationStackScrollLayout parent, LayoutInflater layoutInflater) {
- if (mInitialized) {
- throw new IllegalStateException("NotificationSectionsManager already initialized");
- }
- mInitialized = true;
- mParent = parent;
- reinflateViews(layoutInflater);
- mConfigurationController.addCallback(mConfigurationListener);
- }
-
- private <T extends ExpandableView> T reinflateView(
- T view, LayoutInflater layoutInflater, @LayoutRes int layoutResId) {
- int oldPos = -1;
- if (view != null) {
- if (view.getTransientContainer() != null) {
- view.getTransientContainer().removeView(mGentleHeader);
- } else if (view.getParent() != null) {
- oldPos = mParent.indexOfChild(view);
- mParent.removeView(view);
- }
- }
-
- view = (T) layoutInflater.inflate(layoutResId, mParent, false);
-
- if (oldPos != -1) {
- mParent.addView(view, oldPos);
- }
-
- return view;
- }
-
- /**
- * Reinflates the entire notification header, including all decoration views.
- */
- void reinflateViews(LayoutInflater layoutInflater) {
- mGentleHeader = reinflateView(
- mGentleHeader, layoutInflater, R.layout.status_bar_notification_section_header);
- mGentleHeader.setHeaderText(R.string.notification_section_header_gentle);
- mGentleHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
- mGentleHeader.setOnClearAllClickListener(this::onClearGentleNotifsClick);
-
- mAlertingHeader = reinflateView(
- mAlertingHeader, layoutInflater, R.layout.status_bar_notification_section_header);
- mAlertingHeader.setHeaderText(R.string.notification_section_header_alerting);
- mAlertingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
-
- if (mPeopleHubSubscription != null) {
- mPeopleHubSubscription.unsubscribe();
- }
- mPeopleHubView = reinflateView(mPeopleHubView, layoutInflater, R.layout.people_strip);
- if (ENABLE_SNOOZED_CONVERSATION_HUB) {
- mPeopleHubSubscription = mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
- }
-
- mIncomingHeader = reinflateView(
- mIncomingHeader, layoutInflater, R.layout.status_bar_notification_section_header);
- mIncomingHeader.setHeaderText(R.string.notification_section_header_incoming);
- mIncomingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
-
- mMediaControlsView = reinflateView(mMediaControlsView, layoutInflater,
- R.layout.keyguard_media_header);
- mKeyguardMediaController.attach(mMediaControlsView);
- }
-
- /** Listener for when the "clear all" button is clicked on the gentle notification header. */
- void setOnClearGentleNotifsClickListener(View.OnClickListener listener) {
- mOnClearGentleNotifsClickListener = listener;
- }
-
- @Override
- public boolean beginsSection(@NonNull View view, @Nullable View previous) {
- return view == mGentleHeader
- || view == mMediaControlsView
- || view == mPeopleHubView
- || view == mAlertingHeader
- || view == mIncomingHeader
- || !Objects.equals(getBucket(view), getBucket(previous));
- }
-
- private boolean isUsingMultipleSections() {
- return mNumberOfSections > 1;
- }
-
- @Nullable
- private Integer getBucket(View view) {
- if (view == mGentleHeader) {
- return BUCKET_SILENT;
- } else if (view == mIncomingHeader) {
- return BUCKET_HEADS_UP;
- } else if (view == mMediaControlsView) {
- return BUCKET_MEDIA_CONTROLS;
- } else if (view == mPeopleHubView) {
- return BUCKET_PEOPLE;
- } else if (view == mAlertingHeader) {
- return BUCKET_ALERTING;
- } else if (view instanceof ExpandableNotificationRow) {
- return ((ExpandableNotificationRow) view).getEntry().getBucket();
- }
- return null;
- }
-
- private void logShadeContents() {
- final int childCount = mParent.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mParent.getChildAt(i);
- if (child == mIncomingHeader) {
- mLogger.logIncomingHeader(i);
- continue;
- }
- if (child == mMediaControlsView) {
- mLogger.logMediaControls(i);
- continue;
- }
- if (child == mPeopleHubView) {
- mLogger.logConversationsHeader(i);
- continue;
- }
- if (child == mAlertingHeader) {
- mLogger.logAlertingHeader(i);
- continue;
- }
- if (child == mGentleHeader) {
- mLogger.logSilentHeader(i);
- continue;
- }
-
- if (!(child instanceof ExpandableNotificationRow)) {
- mLogger.logOther(i, child.getClass());
- continue;
- }
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- // Once we enter a new section, calculate the target position for the header.
- switch (row.getEntry().getBucket()) {
- case BUCKET_HEADS_UP:
- mLogger.logHeadsUp(i);
- break;
- case BUCKET_PEOPLE:
- mLogger.logConversation(i);
- break;
- case BUCKET_ALERTING:
- mLogger.logAlerting(i);
- break;
- case BUCKET_SILENT:
- mLogger.logSilent(i);
- break;
- }
- }
- }
-
- @VisibleForTesting
- void updateSectionBoundaries() {
- updateSectionBoundaries("test");
- }
-
- /**
- * Should be called whenever notifs are added, removed, or updated. Updates section boundary
- * bookkeeping and adds/moves/removes section headers if appropriate.
- */
- void updateSectionBoundaries(String reason) {
- if (!isUsingMultipleSections()) {
- return;
- }
-
- mLogger.logStartSectionUpdate(reason);
-
- // The overall strategy here is to iterate over the current children of mParent, looking
- // for where the sections headers are currently positioned, and where each section begins.
- // Then, once we find the start of a new section, we track that position as the "target" for
- // the section header, adjusted for the case where existing headers are in front of that
- // target, but won't be once they are moved / removed after the pass has completed.
-
- final boolean showHeaders = mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
- final boolean usingPeopleFiltering = mSectionsFeatureManager.isFilteringEnabled();
- final boolean usingMediaControls = mSectionsFeatureManager.isMediaControlsEnabled();
-
- boolean peopleNotifsPresent = false;
-
- int currentMediaControlsIdx = -1;
- int mediaControlsTarget = usingMediaControls ? 0 : -1;
- int currentIncomingHeaderIdx = -1;
- int incomingHeaderTarget = -1;
- int currentPeopleHeaderIdx = -1;
- int peopleHeaderTarget = -1;
- int currentAlertingHeaderIdx = -1;
- int alertingHeaderTarget = -1;
- int currentGentleHeaderIdx = -1;
- int gentleHeaderTarget = -1;
-
- int lastNotifIndex = 0;
-
- final int childCount = mParent.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mParent.getChildAt(i);
-
- // Track the existing positions of the headers
- if (child == mIncomingHeader) {
- mLogger.logIncomingHeader(i);
- currentIncomingHeaderIdx = i;
- continue;
- }
- if (child == mMediaControlsView) {
- mLogger.logMediaControls(i);
- currentMediaControlsIdx = i;
- continue;
- }
- if (child == mPeopleHubView) {
- mLogger.logConversationsHeader(i);
- currentPeopleHeaderIdx = i;
- continue;
- }
- if (child == mAlertingHeader) {
- mLogger.logAlertingHeader(i);
- currentAlertingHeaderIdx = i;
- continue;
- }
- if (child == mGentleHeader) {
- mLogger.logSilentHeader(i);
- currentGentleHeaderIdx = i;
- continue;
- }
-
- if (!(child instanceof ExpandableNotificationRow)) {
- mLogger.logOther(i, child.getClass());
- continue;
- }
- lastNotifIndex = i;
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- // Once we enter a new section, calculate the target position for the header.
- switch (row.getEntry().getBucket()) {
- case BUCKET_HEADS_UP:
- mLogger.logHeadsUp(i);
- if (showHeaders && incomingHeaderTarget == -1) {
- incomingHeaderTarget = i;
- // Offset the target if there are other headers before this that will be
- // moved.
- if (currentIncomingHeaderIdx != -1) {
- incomingHeaderTarget--;
- }
- if (currentMediaControlsIdx != -1) {
- incomingHeaderTarget--;
- }
- if (currentPeopleHeaderIdx != -1) {
- incomingHeaderTarget--;
- }
- if (currentAlertingHeaderIdx != -1) {
- incomingHeaderTarget--;
- }
- if (currentGentleHeaderIdx != -1) {
- incomingHeaderTarget--;
- }
- }
- if (mediaControlsTarget != -1) {
- mediaControlsTarget++;
- }
- break;
- case BUCKET_FOREGROUND_SERVICE:
- mLogger.logForegroundService(i);
- if (mediaControlsTarget != -1) {
- mediaControlsTarget++;
- }
- break;
- case BUCKET_PEOPLE:
- mLogger.logConversation(i);
- peopleNotifsPresent = true;
- if (showHeaders && peopleHeaderTarget == -1) {
- peopleHeaderTarget = i;
- // Offset the target if there are other headers before this that will be
- // moved.
- if (currentPeopleHeaderIdx != -1) {
- peopleHeaderTarget--;
- }
- if (currentAlertingHeaderIdx != -1) {
- peopleHeaderTarget--;
- }
- if (currentGentleHeaderIdx != -1) {
- peopleHeaderTarget--;
- }
- }
- break;
- case BUCKET_ALERTING:
- mLogger.logAlerting(i);
- if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
- alertingHeaderTarget = i;
- // Offset the target if there are other headers before this that will be
- // moved.
- if (currentAlertingHeaderIdx != -1) {
- alertingHeaderTarget--;
- }
- if (currentGentleHeaderIdx != -1) {
- alertingHeaderTarget--;
- }
- }
- break;
- case BUCKET_SILENT:
- mLogger.logSilent(i);
- if (showHeaders && gentleHeaderTarget == -1) {
- gentleHeaderTarget = i;
- // Offset the target if there are other headers before this that will be
- // moved.
- if (currentGentleHeaderIdx != -1) {
- gentleHeaderTarget--;
- }
- }
- break;
- default:
- throw new IllegalStateException("Cannot find section bucket for view");
- }
- }
- if (showHeaders && usingPeopleFiltering && mPeopleHubVisible && peopleHeaderTarget == -1) {
- // Insert the people header even if there are no people visible, in order to show
- // the hub. Put it directly above the next header.
- if (alertingHeaderTarget != -1) {
- peopleHeaderTarget = alertingHeaderTarget;
- } else if (gentleHeaderTarget != -1) {
- peopleHeaderTarget = gentleHeaderTarget;
- } else {
- // Put it at the end of the list.
- peopleHeaderTarget = lastNotifIndex;
- }
- // Offset the target to account for the current position of the people header.
- if (currentPeopleHeaderIdx != -1 && currentPeopleHeaderIdx < peopleHeaderTarget) {
- peopleHeaderTarget--;
- }
- }
-
- mLogger.logStr("New header target positions:");
- mLogger.logIncomingHeader(incomingHeaderTarget);
- mLogger.logMediaControls(mediaControlsTarget);
- mLogger.logConversationsHeader(peopleHeaderTarget);
- mLogger.logAlertingHeader(alertingHeaderTarget);
- mLogger.logSilentHeader(gentleHeaderTarget);
-
- // Add headers in reverse order to preserve indices
- adjustHeaderVisibilityAndPosition(
- gentleHeaderTarget, mGentleHeader, currentGentleHeaderIdx);
- adjustHeaderVisibilityAndPosition(
- alertingHeaderTarget, mAlertingHeader, currentAlertingHeaderIdx);
- adjustHeaderVisibilityAndPosition(
- peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx);
- adjustViewPosition(mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx);
- adjustHeaderVisibilityAndPosition(incomingHeaderTarget, mIncomingHeader,
- currentIncomingHeaderIdx);
-
- mLogger.logStr("Final order:");
- logShadeContents();
- mLogger.logStr("Section boundary update complete");
-
- // Update headers to reflect state of section contents
- mGentleHeader.setAreThereDismissableGentleNotifs(
- mParent.hasActiveClearableNotifications(ROWS_GENTLE));
- mPeopleHubView.setCanSwipe(showHeaders && mPeopleHubVisible && !peopleNotifsPresent);
- if (peopleHeaderTarget != currentPeopleHeaderIdx) {
- mPeopleHubView.resetTranslation();
- }
- }
-
- private void adjustHeaderVisibilityAndPosition(
- int targetPosition, StackScrollerDecorView header, int currentPosition) {
- adjustViewPosition(targetPosition, header, currentPosition);
- if (targetPosition != -1 && currentPosition == -1) {
- header.setContentVisible(true);
- }
- }
-
- private void adjustViewPosition(int targetPosition, ExpandableView view, int currentPosition) {
- if (targetPosition == -1) {
- if (currentPosition != -1) {
- mParent.removeView(view);
- }
- } else {
- if (currentPosition == -1) {
- // If the header is animating away, it will still have a parent, so detach it first
- // TODO: We should really cancel the active animations here. This will happen
- // automatically when the view's intro animation starts, but it's a fragile link.
- if (view.getTransientContainer() != null) {
- view.getTransientContainer().removeTransientView(view);
- view.setTransientContainer(null);
- }
- mParent.addView(view, targetPosition);
- } else {
- mParent.changeViewPosition(view, targetPosition);
- }
- }
- }
-
- /**
- * Updates the boundaries (as tracked by their first and last views) of the priority sections.
- *
- * @return {@code true} If the last view in the top section changed (so we need to animate).
- */
- boolean updateFirstAndLastViewsForAllSections(
- NotificationSection[] sections,
- List<ActivatableNotificationView> children) {
-
- if (sections.length <= 0 || children.size() <= 0) {
- for (NotificationSection s : sections) {
- s.setFirstVisibleChild(null);
- s.setLastVisibleChild(null);
- }
- return false;
- }
-
- boolean changed = false;
- ArrayList<ActivatableNotificationView> viewsInBucket = new ArrayList<>();
- for (NotificationSection s : sections) {
- int filter = s.getBucket();
- viewsInBucket.clear();
-
- //TODO: do this in a single pass, and more better
- for (ActivatableNotificationView v : children) {
- Integer bucket = getBucket(v);
- if (bucket == null) {
- throw new IllegalArgumentException("Cannot find section bucket for view");
- }
-
- if (bucket == filter) {
- viewsInBucket.add(v);
- }
-
- if (viewsInBucket.size() >= 1) {
- changed |= s.setFirstVisibleChild(viewsInBucket.get(0));
- changed |= s.setLastVisibleChild(viewsInBucket.get(viewsInBucket.size() - 1));
- } else {
- changed |= s.setFirstVisibleChild(null);
- changed |= s.setLastVisibleChild(null);
- }
- }
- }
-
- if (DEBUG) {
- logSections(sections);
- }
-
- return changed;
- }
-
- private void logSections(NotificationSection[] sections) {
- for (int i = 0; i < sections.length; i++) {
- NotificationSection s = sections[i];
- ActivatableNotificationView first = s.getFirstVisibleChild();
- String fs = first == null ? "(null)"
- : (first instanceof ExpandableNotificationRow)
- ? ((ExpandableNotificationRow) first).getEntry().getKey()
- : Integer.toHexString(System.identityHashCode(first));
- ActivatableNotificationView last = s.getLastVisibleChild();
- String ls = last == null ? "(null)"
- : (last instanceof ExpandableNotificationRow)
- ? ((ExpandableNotificationRow) last).getEntry().getKey()
- : Integer.toHexString(System.identityHashCode(last));
- android.util.Log.d(TAG, "updateSections: f=" + fs + " s=" + i);
- android.util.Log.d(TAG, "updateSections: l=" + ls + " s=" + i);
- }
- }
-
- @VisibleForTesting
- ExpandableView getGentleHeaderView() {
- return mGentleHeader;
- }
-
- @VisibleForTesting
- ExpandableView getAlertingHeaderView() {
- return mAlertingHeader;
- }
-
- @VisibleForTesting
- ExpandableView getPeopleHeaderView() {
- return mPeopleHubView;
- }
-
- @VisibleForTesting
- ExpandableView getMediaControlsView() {
- return mMediaControlsView;
- }
-
- @VisibleForTesting
- ExpandableView getIncomingHeaderView() {
- return mIncomingHeader;
- }
-
- @VisibleForTesting
- void setPeopleHubVisible(boolean visible) {
- mPeopleHubVisible = visible;
- }
-
- private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
- @Override
- public void onLocaleListChanged() {
- reinflateViews(LayoutInflater.from(mParent.getContext()));
- }
- };
-
- private void onGentleHeaderClick(View v) {
- Intent intent = new Intent(Settings.ACTION_NOTIFICATION_SETTINGS);
- mActivityStarter.startActivity(
- intent,
- true,
- true,
- Intent.FLAG_ACTIVITY_SINGLE_TOP);
- }
-
- private void onClearGentleNotifsClick(View v) {
- if (mOnClearGentleNotifsClickListener != null) {
- mOnClearGentleNotifsClickListener.onClick(v);
- }
- }
-
- void hidePeopleRow() {
- mPeopleHubVisible = false;
- updateSectionBoundaries("PeopleHub dismissed");
- }
-
- void setHeaderForegroundColor(@ColorInt int color) {
- mPeopleHubView.setTextColor(color);
- mGentleHeader.setForegroundColor(color);
- mAlertingHeader.setForegroundColor(color);
- }
-
- /**
- * For now, declare the available notification buckets (sections) here so that other
- * presentation code can decide what to do based on an entry's buckets
- */
- @Retention(SOURCE)
- @IntDef(prefix = { "BUCKET_" }, value = {
- BUCKET_HEADS_UP,
- BUCKET_FOREGROUND_SERVICE,
- BUCKET_MEDIA_CONTROLS,
- BUCKET_PEOPLE,
- BUCKET_ALERTING,
- BUCKET_SILENT
- })
- public @interface PriorityBucket {}
- public static final int BUCKET_HEADS_UP = 0;
- public static final int BUCKET_FOREGROUND_SERVICE = 1;
- public static final int BUCKET_MEDIA_CONTROLS = 2;
- public static final int BUCKET_PEOPLE = 3;
- public static final int BUCKET_ALERTING = 4;
- public static final int BUCKET_SILENT = 5;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
new file mode 100644
index 000000000000..65633a2e209f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -0,0 +1,596 @@
+/*
+ * 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.stack
+
+import android.annotation.ColorInt
+import android.annotation.IntDef
+import android.annotation.LayoutRes
+import android.content.Intent
+import android.provider.Settings
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.media.KeyguardMediaController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.people.DataListener
+import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter
+import com.android.systemui.statusbar.notification.people.PeopleHubViewBoundary
+import com.android.systemui.statusbar.notification.people.PersonViewModel
+import com.android.systemui.statusbar.notification.people.Subscription
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.row.StackScrollerDecorView
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.children
+import com.android.systemui.util.foldToSparseArray
+import javax.inject.Inject
+
+/**
+ * Manages the boundaries of the two notification sections (high priority and low priority). Also
+ * shows/hides the headers for those sections where appropriate.
+ *
+ * TODO: Move remaining sections logic from NSSL into this class.
+ */
+class NotificationSectionsManager @Inject internal constructor(
+ private val activityStarter: ActivityStarter,
+ private val statusBarStateController: StatusBarStateController,
+ private val configurationController: ConfigurationController,
+ private val peopleHubViewAdapter: PeopleHubViewAdapter,
+ private val keyguardMediaController: KeyguardMediaController,
+ private val sectionsFeatureManager: NotificationSectionsFeatureManager,
+ private val logger: NotificationSectionsLogger
+) : SectionProvider {
+
+ private val configurationListener = object : ConfigurationController.ConfigurationListener {
+ override fun onLocaleListChanged() {
+ reinflateViews(LayoutInflater.from(parent.context))
+ }
+ }
+
+ private val peopleHubViewBoundary: PeopleHubViewBoundary = object : PeopleHubViewBoundary {
+ override fun setVisible(isVisible: Boolean) {
+ if (peopleHubVisible != isVisible) {
+ peopleHubVisible = isVisible
+ if (initialized) {
+ updateSectionBoundaries("PeopleHub visibility changed")
+ }
+ }
+ }
+
+ override val associatedViewForClickAnimation: View
+ get() = peopleHeaderView!!
+
+ override val personViewAdapters: Sequence<DataListener<PersonViewModel?>>
+ get() = peopleHeaderView!!.personViewAdapters
+ }
+
+ private lateinit var parent: NotificationStackScrollLayout
+ private var initialized = false
+ private var onClearSilentNotifsClickListener: View.OnClickListener? = null
+
+ @get:VisibleForTesting
+ var silentHeaderView: SectionHeaderView? = null
+ private set
+
+ @get:VisibleForTesting
+ var alertingHeaderView: SectionHeaderView? = null
+ private set
+
+ @get:VisibleForTesting
+ var incomingHeaderView: SectionHeaderView? = null
+ private set
+
+ @get:VisibleForTesting
+ var peopleHeaderView: PeopleHubView? = null
+ private set
+
+ @set:VisibleForTesting
+ var peopleHubVisible = false
+ private var peopleHubSubscription: Subscription? = null
+
+ @get:VisibleForTesting
+ var mediaControlsView: MediaHeaderView? = null
+ private set
+
+ /** Must be called before use. */
+ fun initialize(parent: NotificationStackScrollLayout, layoutInflater: LayoutInflater) {
+ check(!initialized) { "NotificationSectionsManager already initialized" }
+ initialized = true
+ this.parent = parent
+ reinflateViews(layoutInflater)
+ configurationController.addCallback(configurationListener)
+ }
+
+ private fun <T : ExpandableView> reinflateView(
+ view: T?,
+ layoutInflater: LayoutInflater,
+ @LayoutRes layoutResId: Int
+ ): T {
+ var oldPos = -1
+ view?.let {
+ view.transientContainer?.removeView(view)
+ if (view.parent === parent) {
+ oldPos = parent.indexOfChild(view)
+ parent.removeView(view)
+ }
+ }
+ val inflated = layoutInflater.inflate(layoutResId, parent, false) as T
+ if (oldPos != -1) {
+ parent.addView(inflated, oldPos)
+ }
+ return inflated
+ }
+
+ fun createSectionsForBuckets(): Array<NotificationSection> =
+ sectionsFeatureManager.getNotificationBuckets()
+ .map { NotificationSection(parent, it) }
+ .toTypedArray()
+
+ /**
+ * Reinflates the entire notification header, including all decoration views.
+ */
+ fun reinflateViews(layoutInflater: LayoutInflater) {
+ silentHeaderView = reinflateView(
+ silentHeaderView, layoutInflater, R.layout.status_bar_notification_section_header
+ ).apply {
+ setHeaderText(R.string.notification_section_header_gentle)
+ setOnHeaderClickListener { onGentleHeaderClick() }
+ setOnClearAllClickListener { onClearGentleNotifsClick(it) }
+ }
+ alertingHeaderView = reinflateView(
+ alertingHeaderView, layoutInflater, R.layout.status_bar_notification_section_header
+ ).apply {
+ setHeaderText(R.string.notification_section_header_alerting)
+ setOnHeaderClickListener { onGentleHeaderClick() }
+ }
+ peopleHubSubscription?.unsubscribe()
+ peopleHubSubscription = null
+ peopleHeaderView = reinflateView(peopleHeaderView, layoutInflater, R.layout.people_strip)
+ if (ENABLE_SNOOZED_CONVERSATION_HUB) {
+ peopleHubSubscription = peopleHubViewAdapter.bindView(peopleHubViewBoundary)
+ }
+ incomingHeaderView = reinflateView(
+ incomingHeaderView, layoutInflater, R.layout.status_bar_notification_section_header
+ ).apply {
+ setHeaderText(R.string.notification_section_header_incoming)
+ setOnHeaderClickListener { onGentleHeaderClick() }
+ }
+ mediaControlsView =
+ reinflateView(mediaControlsView, layoutInflater, R.layout.keyguard_media_header)
+ .also(keyguardMediaController::attach)
+ }
+
+ override fun beginsSection(view: View, previous: View?): Boolean =
+ view === silentHeaderView ||
+ view === mediaControlsView ||
+ view === peopleHeaderView ||
+ view === alertingHeaderView ||
+ view === incomingHeaderView ||
+ getBucket(view) != getBucket(previous)
+
+ private fun getBucket(view: View?): Int? = when {
+ view === silentHeaderView -> BUCKET_SILENT
+ view === incomingHeaderView -> BUCKET_HEADS_UP
+ view === mediaControlsView -> BUCKET_MEDIA_CONTROLS
+ view === peopleHeaderView -> BUCKET_PEOPLE
+ view === alertingHeaderView -> BUCKET_ALERTING
+ view is ExpandableNotificationRow -> view.entry.bucket
+ else -> null
+ }
+
+ private fun logShadeContents() = parent.children.forEachIndexed { i, child ->
+ when {
+ child === incomingHeaderView -> logger.logIncomingHeader(i)
+ child === mediaControlsView -> logger.logMediaControls(i)
+ child === peopleHeaderView -> logger.logConversationsHeader(i)
+ child === alertingHeaderView -> logger.logAlertingHeader(i)
+ child === silentHeaderView -> logger.logSilentHeader(i)
+ child !is ExpandableNotificationRow -> logger.logOther(i, child.javaClass)
+ else -> {
+ val isHeadsUp = child.isHeadsUp
+ when (child.entry.bucket) {
+ BUCKET_HEADS_UP -> logger.logHeadsUp(i, isHeadsUp)
+ BUCKET_PEOPLE -> logger.logConversation(i, isHeadsUp)
+ BUCKET_ALERTING -> logger.logAlerting(i, isHeadsUp)
+ BUCKET_SILENT -> logger.logSilent(i, isHeadsUp)
+ }
+ }
+ }
+ }
+
+ private val isUsingMultipleSections: Boolean
+ get() = sectionsFeatureManager.getNumberOfBuckets() > 1
+
+ @VisibleForTesting
+ fun updateSectionBoundaries() = updateSectionBoundaries("test")
+
+ /**
+ * Should be called whenever notifs are added, removed, or updated. Updates section boundary
+ * bookkeeping and adds/moves/removes section headers if appropriate.
+ */
+ fun updateSectionBoundaries(reason: String) {
+ if (!isUsingMultipleSections) {
+ return
+ }
+ logger.logStartSectionUpdate(reason)
+
+ // The overall strategy here is to iterate over the current children of mParent, looking
+ // for where the sections headers are currently positioned, and where each section begins.
+ // Then, once we find the start of a new section, we track that position as the "target" for
+ // the section header, adjusted for the case where existing headers are in front of that
+ // target, but won't be once they are moved / removed after the pass has completed.
+ val showHeaders = statusBarStateController.state != StatusBarState.KEYGUARD
+ val usingPeopleFiltering = sectionsFeatureManager.isFilteringEnabled()
+ val usingMediaControls = sectionsFeatureManager.isMediaControlsEnabled()
+
+ var peopleNotifsPresent = false
+ var currentMediaControlsIdx = -1
+ val mediaControlsTarget = if (usingMediaControls) 0 else -1
+ var currentIncomingHeaderIdx = -1
+ var incomingHeaderTarget = -1
+ var currentPeopleHeaderIdx = -1
+ var peopleHeaderTarget = -1
+ var currentAlertingHeaderIdx = -1
+ var alertingHeaderTarget = -1
+ var currentGentleHeaderIdx = -1
+ var gentleHeaderTarget = -1
+
+ var lastNotifIndex = 0
+ var lastIncomingIndex = -1
+ var prev: ExpandableNotificationRow? = null
+
+ for ((i, child) in parent.children.withIndex()) {
+ when {
+ // Track the existing positions of the headers
+ child === incomingHeaderView -> {
+ logger.logIncomingHeader(i)
+ currentIncomingHeaderIdx = i
+ }
+ child === mediaControlsView -> {
+ logger.logMediaControls(i)
+ currentMediaControlsIdx = i
+ }
+ child === peopleHeaderView -> {
+ logger.logConversationsHeader(i)
+ currentPeopleHeaderIdx = i
+ }
+ child === alertingHeaderView -> {
+ logger.logAlertingHeader(i)
+ currentAlertingHeaderIdx = i
+ }
+ child === silentHeaderView -> {
+ logger.logSilentHeader(i)
+ currentGentleHeaderIdx = i
+ }
+ child !is ExpandableNotificationRow -> logger.logOther(i, child.javaClass)
+ else -> {
+ lastNotifIndex = i
+ // Is there a section discontinuity? This usually occurs due to HUNs
+ if (prev?.entry?.bucket?.let { it > child.entry.bucket } == true) {
+ // Remove existing headers, and move the Incoming header if necessary
+ if (alertingHeaderTarget != -1) {
+ if (showHeaders && incomingHeaderTarget != -1) {
+ incomingHeaderTarget = alertingHeaderTarget
+ }
+ alertingHeaderTarget = -1
+ }
+ if (peopleHeaderTarget != -1) {
+ if (showHeaders && incomingHeaderTarget != -1) {
+ incomingHeaderTarget = peopleHeaderTarget
+ }
+ peopleHeaderTarget = -1
+ }
+ if (showHeaders && incomingHeaderTarget == -1) {
+ incomingHeaderTarget = 0
+ }
+ // Walk backwards changing all previous notifications to the Incoming
+ // section
+ for (j in i - 1 downTo lastIncomingIndex + 1) {
+ val prevChild = parent.getChildAt(j)
+ if (prevChild is ExpandableNotificationRow) {
+ prevChild.entry.bucket = BUCKET_HEADS_UP
+ }
+ }
+ // Track the new bottom of the Incoming section
+ lastIncomingIndex = i - 1
+ }
+ val isHeadsUp = child.isHeadsUp
+ when (child.entry.bucket) {
+ BUCKET_FOREGROUND_SERVICE -> logger.logForegroundService(i, isHeadsUp)
+ BUCKET_PEOPLE -> {
+ logger.logConversation(i, isHeadsUp)
+ peopleNotifsPresent = true
+ if (showHeaders && peopleHeaderTarget == -1) {
+ peopleHeaderTarget = i
+ // Offset the target if there are other headers before this that
+ // will be moved.
+ if (currentPeopleHeaderIdx != -1) {
+ peopleHeaderTarget--
+ }
+ if (currentAlertingHeaderIdx != -1) {
+ peopleHeaderTarget--
+ }
+ if (currentGentleHeaderIdx != -1) {
+ peopleHeaderTarget--
+ }
+ }
+ }
+ BUCKET_ALERTING -> {
+ logger.logAlerting(i, isHeadsUp)
+ if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
+ alertingHeaderTarget = i
+ // Offset the target if there are other headers before this that
+ // will be moved.
+ if (currentAlertingHeaderIdx != -1) {
+ alertingHeaderTarget--
+ }
+ if (currentGentleHeaderIdx != -1) {
+ alertingHeaderTarget--
+ }
+ }
+ }
+ BUCKET_SILENT -> {
+ logger.logSilent(i, isHeadsUp)
+ if (showHeaders && gentleHeaderTarget == -1) {
+ gentleHeaderTarget = i
+ // Offset the target if there are other headers before this that
+ // will be moved.
+ if (currentGentleHeaderIdx != -1) {
+ gentleHeaderTarget--
+ }
+ }
+ }
+ else -> throw IllegalStateException("Cannot find section bucket for view")
+ }
+
+ prev = child
+ }
+ }
+ }
+
+ if (showHeaders && usingPeopleFiltering && peopleHubVisible && peopleHeaderTarget == -1) {
+ // Insert the people header even if there are no people visible, in order to show
+ // the hub. Put it directly above the next header.
+ peopleHeaderTarget = when {
+ alertingHeaderTarget != -1 -> alertingHeaderTarget
+ gentleHeaderTarget != -1 -> gentleHeaderTarget
+ else -> lastNotifIndex // Put it at the end of the list.
+ }
+ // Offset the target to account for the current position of the people header.
+ if (currentPeopleHeaderIdx != -1 && currentPeopleHeaderIdx < peopleHeaderTarget) {
+ peopleHeaderTarget--
+ }
+ }
+
+ logger.logStr("New header target positions:")
+ logger.logIncomingHeader(incomingHeaderTarget)
+ logger.logMediaControls(mediaControlsTarget)
+ logger.logConversationsHeader(peopleHeaderTarget)
+ logger.logAlertingHeader(alertingHeaderTarget)
+ logger.logSilentHeader(gentleHeaderTarget)
+
+ // Add headers in reverse order to preserve indices
+ silentHeaderView?.let {
+ adjustHeaderVisibilityAndPosition(gentleHeaderTarget, it, currentGentleHeaderIdx)
+ }
+ alertingHeaderView?.let {
+ adjustHeaderVisibilityAndPosition(alertingHeaderTarget, it, currentAlertingHeaderIdx)
+ }
+ peopleHeaderView?.let {
+ adjustHeaderVisibilityAndPosition(peopleHeaderTarget, it, currentPeopleHeaderIdx)
+ }
+ incomingHeaderView?.let {
+ adjustHeaderVisibilityAndPosition(incomingHeaderTarget, it, currentIncomingHeaderIdx)
+ }
+ mediaControlsView?.let {
+ adjustViewPosition(mediaControlsTarget, it, currentMediaControlsIdx)
+ }
+
+ logger.logStr("Final order:")
+ logShadeContents()
+ logger.logStr("Section boundary update complete")
+
+ // Update headers to reflect state of section contents
+ silentHeaderView?.setAreThereDismissableGentleNotifs(
+ parent.hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE)
+ )
+ peopleHeaderView?.canSwipe = showHeaders && peopleHubVisible && !peopleNotifsPresent
+ if (peopleHeaderTarget != currentPeopleHeaderIdx) {
+ peopleHeaderView?.resetTranslation()
+ }
+ }
+
+ private fun adjustHeaderVisibilityAndPosition(
+ targetPosition: Int,
+ header: StackScrollerDecorView,
+ currentPosition: Int
+ ) {
+ adjustViewPosition(targetPosition, header, currentPosition)
+ if (targetPosition != -1 && currentPosition == -1) {
+ header.isContentVisible = true
+ }
+ }
+
+ private fun adjustViewPosition(
+ targetPosition: Int,
+ view: ExpandableView,
+ currentPosition: Int
+ ) {
+ if (targetPosition == -1) {
+ if (currentPosition != -1) {
+ parent.removeView(view)
+ }
+ } else {
+ if (currentPosition == -1) {
+ // If the header is animating away, it will still have a parent, so detach it first
+ // TODO: We should really cancel the active animations here. This will happen
+ // automatically when the view's intro animation starts, but it's a fragile link.
+ view.transientContainer?.removeTransientView(view)
+ view.transientContainer = null
+ parent.addView(view, targetPosition)
+ } else {
+ parent.changeViewPosition(view, targetPosition)
+ }
+ }
+ }
+
+ private sealed class SectionBounds {
+
+ data class Many(
+ val first: ActivatableNotificationView,
+ val last: ActivatableNotificationView
+ ) : SectionBounds()
+
+ data class One(val lone: ActivatableNotificationView) : SectionBounds()
+ object None : SectionBounds()
+
+ fun addNotif(notif: ActivatableNotificationView): SectionBounds = when (this) {
+ is None -> One(notif)
+ is One -> Many(lone, notif)
+ is Many -> copy(last = notif)
+ }
+
+ fun updateSection(section: NotificationSection): Boolean = when (this) {
+ is None -> section.setFirstAndLastVisibleChildren(null, null)
+ is One -> section.setFirstAndLastVisibleChildren(lone, lone)
+ is Many -> section.setFirstAndLastVisibleChildren(first, last)
+ }
+
+ private fun NotificationSection.setFirstAndLastVisibleChildren(
+ first: ActivatableNotificationView?,
+ last: ActivatableNotificationView?
+ ): Boolean {
+ val firstChanged = setFirstVisibleChild(first)
+ val lastChanged = setLastVisibleChild(last)
+ return firstChanged || lastChanged
+ }
+ }
+
+ /**
+ * Updates the boundaries (as tracked by their first and last views) of the priority sections.
+ *
+ * @return `true` If the last view in the top section changed (so we need to animate).
+ */
+ fun updateFirstAndLastViewsForAllSections(
+ sections: Array<NotificationSection>,
+ children: List<ActivatableNotificationView>
+ ): Boolean {
+ // Create mapping of bucket to section
+ val sectionBounds = children.asSequence()
+ // Group children by bucket
+ .groupingBy {
+ getBucket(it)
+ ?: throw IllegalArgumentException("Cannot find section bucket for view")
+ }
+ // Combine each bucket into a SectionBoundary
+ .foldToSparseArray(
+ SectionBounds.None,
+ size = sections.size,
+ operation = SectionBounds::addNotif
+ )
+ // Update each section with the associated boundary, tracking if there was a change
+ val changed = sections.fold(false) { changed, section ->
+ val bounds = sectionBounds[section.bucket] ?: SectionBounds.None
+ bounds.updateSection(section) || changed
+ }
+ if (DEBUG) {
+ logSections(sections)
+ }
+ return changed
+ }
+
+ private fun logSections(sections: Array<NotificationSection>) {
+ for (i in sections.indices) {
+ val s = sections[i]
+ val fs = when (val first = s.firstVisibleChild) {
+ null -> "(null)"
+ is ExpandableNotificationRow -> first.entry.key
+ else -> Integer.toHexString(System.identityHashCode(first))
+ }
+ val ls = when (val last = s.lastVisibleChild) {
+ null -> "(null)"
+ is ExpandableNotificationRow -> last.entry.key
+ else -> Integer.toHexString(System.identityHashCode(last))
+ }
+ Log.d(TAG, "updateSections: f=$fs s=$i")
+ Log.d(TAG, "updateSections: l=$ls s=$i")
+ }
+ }
+
+ private fun onGentleHeaderClick() {
+ val intent = Intent(Settings.ACTION_NOTIFICATION_SETTINGS)
+ activityStarter.startActivity(
+ intent,
+ true,
+ true,
+ Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ }
+
+ private fun onClearGentleNotifsClick(v: View) {
+ onClearSilentNotifsClickListener?.onClick(v)
+ }
+
+ /** Listener for when the "clear all" button is clicked on the gentle notification header. */
+ fun setOnClearSilentNotifsClickListener(listener: View.OnClickListener) {
+ onClearSilentNotifsClickListener = listener
+ }
+
+ fun hidePeopleRow() {
+ peopleHubVisible = false
+ updateSectionBoundaries("PeopleHub dismissed")
+ }
+
+ fun setHeaderForegroundColor(@ColorInt color: Int) {
+ peopleHeaderView?.setTextColor(color)
+ silentHeaderView?.setForegroundColor(color)
+ alertingHeaderView?.setForegroundColor(color)
+ }
+
+ companion object {
+ private const val TAG = "NotifSectionsManager"
+ private const val DEBUG = false
+ private const val ENABLE_SNOOZED_CONVERSATION_HUB = false
+ }
+}
+
+/**
+ * For now, declare the available notification buckets (sections) here so that other
+ * presentation code can decide what to do based on an entry's buckets
+ */
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+ prefix = ["BUCKET_"],
+ value = [
+ BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE,
+ BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT
+ ]
+)
+annotation class PriorityBucket
+
+const val BUCKET_UNKNOWN = 0
+const val BUCKET_MEDIA_CONTROLS = 1
+const val BUCKET_HEADS_UP = 2
+const val BUCKET_FOREGROUND_SERVICE = 3
+const val BUCKET_PEOPLE = 4
+const val BUCKET_ALERTING = 5
+const val BUCKET_SILENT = 6
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 1ccc2bde2288..3db4b6f7ffbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -21,7 +21,7 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
@@ -577,7 +577,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
mSectionsManager = notificationSectionsManager;
mSectionsManager.initialize(this, LayoutInflater.from(context));
- mSectionsManager.setOnClearGentleNotifsClickListener(v -> {
+ mSectionsManager.setOnClearSilentNotifsClickListener(v -> {
// Leave the shade open if there will be other notifs left over to clear
final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
clearNotifications(ROWS_GENTLE, closeShade);
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
new file mode 100644
index 000000000000..c91033e4745a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 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.util
+
+import android.view.ViewGroup
+
+/** [Sequence] that yields all of the direct children of this [ViewGroup] */
+val ViewGroup.children
+ get() = sequence {
+ for (i in 0 until childCount) yield(getChildAt(i))
+ } \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt b/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt
new file mode 100644
index 000000000000..accb81eae32a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 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.util
+
+import android.util.SparseArray
+
+/**
+ * Transforms an [Array] into a [SparseArray], by applying each element to [keySelector] in order to
+ * generate the index at which it will be placed. If two elements produce the same index, the latter
+ * replaces the former in the final result.
+ *
+ * See [Array.associateBy].
+ */
+inline fun <T> Array<T>.associateByToSparseArray(
+ crossinline keySelector: (T) -> Int
+): SparseArray<T> {
+ val sparseArray = SparseArray<T>(size)
+ for (value in this) {
+ sparseArray.put(keySelector(value), value)
+ }
+ return sparseArray
+}
+
+/**
+ * Folds a [Grouping] into a [SparseArray]. See [Grouping.fold].
+ */
+inline fun <T, R> Grouping<T, Int>.foldToSparseArray(
+ initial: R,
+ size: Int = -1,
+ crossinline operation: (R, T) -> R
+): SparseArray<R> {
+ val sparseArray = when {
+ size < 0 -> SparseArray<R>()
+ else -> SparseArray<R>(size)
+ }
+ sourceIterator().forEach { elem ->
+ val key = keyOf(elem)
+ val acc = sparseArray.get(key) ?: initial
+ sparseArray.put(key, operation(acc, elem))
+ }
+ return sparseArray
+}
+
+/**
+ * Wraps this [SparseArray] into an immutable [Map], the methods of which forward to this
+ * [SparseArray].
+ */
+fun <T> SparseArray<T>.asMap(): Map<Int, T> = SparseArrayMapWrapper(this)
+
+private class SparseArrayMapWrapper<T>(
+ private val sparseArray: SparseArray<T>
+) : Map<Int, T> {
+
+ private data class Entry<T>(override val key: Int, override val value: T) : Map.Entry<Int, T>
+
+ private val entrySequence = sequence {
+ val size = sparseArray.size()
+ for (i in 0 until size) {
+ val key = sparseArray.keyAt(i)
+ val value = sparseArray.get(key)
+ yield(Entry(key, value))
+ }
+ }
+
+ override val entries: Set<Map.Entry<Int, T>>
+ get() = object : Set<Map.Entry<Int, T>> {
+ override val size: Int
+ get() = this@SparseArrayMapWrapper.size
+
+ override fun contains(element: Map.Entry<Int, T>): Boolean =
+ sparseArray[element.key]?.let { it == element.value } == true
+
+ override fun containsAll(elements: Collection<Map.Entry<Int, T>>): Boolean =
+ elements.all { contains(it) }
+
+ override fun isEmpty(): Boolean = size == 0
+
+ override fun iterator(): Iterator<Map.Entry<Int, T>> = entrySequence.iterator()
+ }
+
+ override val keys: Set<Int> = object : Set<Int> {
+ private val keySequence = entrySequence.map { it.key }
+
+ override val size: Int
+ get() = this@SparseArrayMapWrapper.size
+
+ override fun contains(element: Int): Boolean = containsKey(element)
+
+ override fun containsAll(elements: Collection<Int>): Boolean =
+ elements.all { contains(it) }
+
+ override fun isEmpty(): Boolean = size == 0
+
+ override fun iterator(): Iterator<Int> = keySequence.iterator()
+ }
+ override val size: Int
+ get() = sparseArray.size()
+ override val values: Collection<T>
+ get() = object : Collection<T> {
+ private val valueSequence = entrySequence.map { it.value }
+
+ override val size: Int
+ get() = this@SparseArrayMapWrapper.size
+
+ override fun contains(element: T): Boolean = containsValue(element)
+
+ override fun containsAll(elements: Collection<T>): Boolean =
+ elements.all { contains(it) }
+
+ override fun isEmpty(): Boolean = this@SparseArrayMapWrapper.isEmpty()
+
+ override fun iterator(): Iterator<T> = valueSequence.iterator()
+ }
+
+ override fun containsKey(key: Int): Boolean = sparseArray.contains(key)
+
+ override fun containsValue(value: T): Boolean = sparseArray.indexOfValue(value) >= 0
+
+ override fun get(key: Int): T? = sparseArray.get(key)
+
+ override fun isEmpty(): Boolean = sparseArray.size() == 0
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index d124bad438c3..a24fa842eca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -20,9 +20,9 @@ import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.content.Intent.ACTION_USER_SWITCHED;
import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index b4cabfd1855d..a83de139bbca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -36,8 +36,8 @@ import com.android.systemui.statusbar.notification.people.PeopleNotificationIden
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationGroupManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import dagger.Lazy
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index d39b2c202fd9..a3a46f67ee40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.logging;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 546bce81a260..3dc941a0bd20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -18,11 +18,11 @@ package com.android.systemui.statusbar.notification.stack;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
import static com.google.common.truth.Truth.assertThat;
@@ -52,6 +52,7 @@ import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -135,140 +136,152 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
@Test
public void testInsertHeader() {
// GIVEN a stack with HI and LO rows but no section headers
- setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+ setStackState(
+ ALERTING,
+ ALERTING,
+ ALERTING,
+ GENTLE);
// WHEN we update the section headers
mSectionsManager.updateSectionBoundaries();
// THEN a LO section header is added
- verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3);
+ verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
}
@Test
public void testRemoveHeader() {
// GIVEN a stack that originally had a header between the HI and LO sections
- setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+ setStackState(
+ ALERTING,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN the last LO row is replaced with a HI row
setStackState(
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.GENTLE_HEADER,
- ChildType.ALERTING);
+ ALERTING,
+ ALERTING,
+ GENTLE_HEADER,
+ ALERTING);
clearInvocations(mNssl);
mSectionsManager.updateSectionBoundaries();
// THEN the LO section header is removed
- verify(mNssl).removeView(mSectionsManager.getGentleHeaderView());
+ verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
}
@Test
public void testDoNothingIfHeaderAlreadyRemoved() {
// GIVEN a stack with only HI rows
- setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING);
+ setStackState(
+ ALERTING,
+ ALERTING,
+ ALERTING);
// WHEN we update the sections headers
mSectionsManager.updateSectionBoundaries();
// THEN we don't add any section headers
- verify(mNssl, never()).addView(eq(mSectionsManager.getGentleHeaderView()), anyInt());
+ verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
}
@Test
public void testMoveHeaderForward() {
// GIVEN a stack that originally had a header between the HI and LO sections
setStackState(
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.GENTLE);
+ ALERTING,
+ ALERTING,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN the LO section moves forward
setStackState(
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.GENTLE,
- ChildType.GENTLE_HEADER,
- ChildType.GENTLE);
+ ALERTING,
+ ALERTING,
+ GENTLE,
+ GENTLE_HEADER,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// THEN the LO section header is also moved forward
- verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2);
+ verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2);
}
@Test
public void testMoveHeaderBackward() {
// GIVEN a stack that originally had a header between the HI and LO sections
setStackState(
- ChildType.ALERTING,
- ChildType.GENTLE,
- ChildType.GENTLE,
- ChildType.GENTLE);
+ ALERTING,
+ GENTLE,
+ GENTLE,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN the LO section moves backward
setStackState(
- ChildType.ALERTING,
- ChildType.GENTLE_HEADER,
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.GENTLE);
+ ALERTING,
+ GENTLE_HEADER,
+ ALERTING,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// THEN the LO section header is also moved backward (with appropriate index shifting)
- verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 3);
+ verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 3);
}
@Test
public void testHeaderRemovedFromTransientParent() {
// GIVEN a stack where the header is animating away
setStackState(
- ChildType.ALERTING,
- ChildType.GENTLE,
- ChildType.GENTLE,
- ChildType.GENTLE);
- mSectionsManager.updateSectionBoundaries();
- setStackState(
- ChildType.ALERTING,
- ChildType.GENTLE_HEADER);
+ ALERTING,
+ GENTLE_HEADER);
mSectionsManager.updateSectionBoundaries();
clearInvocations(mNssl);
ViewGroup transientParent = mock(ViewGroup.class);
- mSectionsManager.getGentleHeaderView().setTransientContainer(transientParent);
+ mSectionsManager.getSilentHeaderView().setTransientContainer(transientParent);
// WHEN the LO section reappears
setStackState(
- ChildType.ALERTING,
- ChildType.GENTLE);
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// THEN the header is first removed from the transient parent before being added to the
// NSSL.
- verify(transientParent).removeTransientView(mSectionsManager.getGentleHeaderView());
- verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 1);
+ verify(transientParent).removeTransientView(mSectionsManager.getSilentHeaderView());
+ verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 1);
}
@Test
public void testHeaderNotShownOnLockscreen() {
// GIVEN a stack of HI and LO notifs on the lockscreen
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+ setStackState(
+ ALERTING,
+ ALERTING,
+ ALERTING,
+ GENTLE);
// WHEN we update the section headers
mSectionsManager.updateSectionBoundaries();
// Then the section header is not added
- verify(mNssl, never()).addView(eq(mSectionsManager.getGentleHeaderView()), anyInt());
+ verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
}
@Test
public void testHeaderShownWhenEnterLockscreen() {
// GIVEN a stack of HI and LO notifs on the lockscreen
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
+ setStackState(
+ ALERTING,
+ ALERTING,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
// WHEN we unlock
@@ -276,20 +289,23 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
mSectionsManager.updateSectionBoundaries();
// Then the section header is added
- verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3);
+ verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
}
@Test
public void testHeaderHiddenWhenEnterLockscreen() {
// GIVEN a stack of HI and LO notifs on the shade
- setStackState(ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE);
+ setStackState(
+ ALERTING,
+ GENTLE_HEADER,
+ GENTLE);
// WHEN we go back to the keyguard
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mSectionsManager.updateSectionBoundaries();
// Then the section header is removed
- verify(mNssl).removeView(mSectionsManager.getGentleHeaderView());
+ verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
}
@Test
@@ -297,13 +313,13 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
enablePeopleFiltering();
setStackState(
- ChildType.GENTLE_HEADER,
- ChildType.PERSON,
- ChildType.ALERTING,
- ChildType.GENTLE);
+ GENTLE_HEADER,
+ PERSON,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
- verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2);
+ verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2);
verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
}
@@ -313,12 +329,12 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
enablePeopleFiltering();
setStackState(
- ChildType.PERSON,
- ChildType.ALERTING,
- ChildType.GENTLE);
+ PERSON,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
- verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 2);
+ verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 2);
verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
}
@@ -328,15 +344,15 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
enablePeopleFiltering();
setStackState(
- ChildType.PEOPLE_HEADER,
- ChildType.ALERTING_HEADER,
- ChildType.GENTLE_HEADER,
- ChildType.PERSON,
- ChildType.ALERTING,
- ChildType.GENTLE);
+ PEOPLE_HEADER,
+ ALERTING_HEADER,
+ GENTLE_HEADER,
+ PERSON,
+ ALERTING,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
- verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 4);
+ verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 4);
verify(mNssl).changeViewPosition(mSectionsManager.getAlertingHeaderView(), 2);
verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0);
}
@@ -347,12 +363,11 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
enablePeopleFiltering();
setStackState(
- ChildType.PEOPLE_HEADER,
- ChildType.ALERTING_HEADER,
- ChildType.ALERTING,
- ChildType.GENTLE_HEADER,
- ChildType.GENTLE
- );
+ PEOPLE_HEADER,
+ ALERTING_HEADER,
+ ALERTING,
+ GENTLE_HEADER,
+ GENTLE);
mSectionsManager.updateSectionBoundaries();
verify(mNssl, never()).removeView(mSectionsManager.getPeopleHeaderView());
@@ -360,41 +375,98 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
}
@Test
- public void testPeopleFiltering_HunWhilePeopleVisible() {
+ public void testPeopleFiltering_AlertingHunWhilePeopleVisible() {
enablePeopleFiltering();
setupMockStack(
- ChildType.PEOPLE_HEADER,
+ PEOPLE_HEADER,
+ ALERTING.headsUp(),
+ PERSON,
+ ALERTING_HEADER,
+ GENTLE_HEADER,
+ GENTLE
+ );
+ mSectionsManager.updateSectionBoundaries();
+
+ verifyMockStack(
+ ChildType.INCOMING_HEADER,
ChildType.HEADS_UP,
+ ChildType.PEOPLE_HEADER,
ChildType.PERSON,
- ChildType.ALERTING_HEADER,
ChildType.GENTLE_HEADER,
ChildType.GENTLE
);
+ }
+
+ @Test
+ public void testPeopleFiltering_PersonHunWhileAlertingHunVisible() {
+ enablePeopleFiltering();
+
+ setupMockStack(
+ PERSON.headsUp(),
+ INCOMING_HEADER,
+ ALERTING.headsUp(),
+ PEOPLE_HEADER,
+ PERSON
+ );
mSectionsManager.updateSectionBoundaries();
verifyMockStack(
ChildType.INCOMING_HEADER,
ChildType.HEADS_UP,
+ ChildType.HEADS_UP,
+ ChildType.PEOPLE_HEADER,
+ ChildType.PERSON
+ );
+ }
+
+ @Test
+ public void testPeopleFiltering_PersonHun() {
+ enablePeopleFiltering();
+
+ setupMockStack(
+ PERSON.headsUp(),
+ PEOPLE_HEADER,
+ PERSON
+ );
+ mSectionsManager.updateSectionBoundaries();
+
+ verifyMockStack(
ChildType.PEOPLE_HEADER,
ChildType.PERSON,
- ChildType.GENTLE_HEADER,
- ChildType.GENTLE
+ ChildType.PERSON
);
}
@Test
- public void testPeopleFiltering_Fsn() {
+ public void testPeopleFiltering_AlertingHunWhilePersonHunning() {
enablePeopleFiltering();
setupMockStack(
+ ALERTING.headsUp(),
+ PERSON.headsUp()
+ );
+ mSectionsManager.updateSectionBoundaries();
+ verifyMockStack(
ChildType.INCOMING_HEADER,
ChildType.HEADS_UP,
ChildType.PEOPLE_HEADER,
- ChildType.FSN,
- ChildType.PERSON,
- ChildType.ALERTING,
- ChildType.GENTLE
+ ChildType.PERSON
+ );
+ }
+
+ @Test
+ public void testPeopleFiltering_Fsn() {
+ enablePeopleFiltering();
+
+ setupMockStack(
+ INCOMING_HEADER,
+ ALERTING.headsUp(),
+ PEOPLE_HEADER,
+ FSN,
+ PERSON,
+ ALERTING,
+ GENTLE
);
mSectionsManager.updateSectionBoundaries();
@@ -416,7 +488,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
enableMediaControls();
// GIVEN a stack that doesn't include media controls
- setStackState(ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE);
+ setStackState(ALERTING, GENTLE_HEADER, GENTLE);
// WHEN we go back to the keyguard
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
@@ -431,14 +503,20 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
enableMediaControls();
// GIVEN a stack that doesn't include media controls but includes HEADS_UP
- setupMockStack(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER,
- ChildType.GENTLE);
+ setupMockStack(
+ ALERTING.headsUp(),
+ ALERTING,
+ GENTLE_HEADER,
+ GENTLE);
// WHEN we go back to the keyguard
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mSectionsManager.updateSectionBoundaries();
- verifyMockStack(ChildType.HEADS_UP, ChildType.MEDIA_CONTROLS, ChildType.ALERTING,
+ verifyMockStack(
+ ChildType.MEDIA_CONTROLS,
+ ChildType.ALERTING,
+ ChildType.ALERTING,
ChildType.GENTLE);
}
@@ -455,11 +533,12 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
FSN, PERSON, ALERTING, GENTLE, OTHER
}
- private void setStackState(ChildType... children) {
+ private void setStackState(StackEntry... children) {
when(mNssl.getChildCount()).thenReturn(children.length);
for (int i = 0; i < children.length; i++) {
View child;
- switch (children[i]) {
+ StackEntry entry = children[i];
+ switch (entry.mChildType) {
case INCOMING_HEADER:
child = mSectionsManager.getIncomingHeaderView();
break;
@@ -473,22 +552,19 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
child = mSectionsManager.getAlertingHeaderView();
break;
case GENTLE_HEADER:
- child = mSectionsManager.getGentleHeaderView();
- break;
- case HEADS_UP:
- child = mockNotification(BUCKET_HEADS_UP);
+ child = mSectionsManager.getSilentHeaderView();
break;
case FSN:
- child = mockNotification(BUCKET_FOREGROUND_SERVICE);
+ child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsHeadsUp);
break;
case PERSON:
- child = mockNotification(BUCKET_PEOPLE);
+ child = mockNotification(BUCKET_PEOPLE, entry.mIsHeadsUp);
break;
case ALERTING:
- child = mockNotification(BUCKET_ALERTING);
+ child = mockNotification(BUCKET_ALERTING, entry.mIsHeadsUp);
break;
case GENTLE:
- child = mockNotification(BUCKET_SILENT);
+ child = mockNotification(BUCKET_SILENT, entry.mIsHeadsUp);
break;
case OTHER:
child = mock(View.class);
@@ -503,12 +579,24 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
}
}
- private View mockNotification(int bucket) {
- ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
- RETURNS_DEEP_STUBS);
+ private View mockNotification(int bucket, boolean headsUp) {
+ ExpandableNotificationRow notifRow =
+ mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
- when(notifRow.getEntry().getBucket()).thenReturn(bucket);
when(notifRow.getParent()).thenReturn(mNssl);
+
+ NotificationEntry mockEntry = mock(NotificationEntry.class);
+ when(notifRow.getEntry()).thenReturn(mockEntry);
+
+ int[] bucketRef = new int[] { bucket };
+ when(mockEntry.getBucket()).thenAnswer(invocation -> bucketRef[0]);
+ doAnswer(invocation -> {
+ bucketRef[0] = invocation.getArgument(0);
+ return null;
+ }).when(mockEntry).setBucket(anyInt());
+
+ when(notifRow.isHeadsUp()).thenReturn(headsUp);
+ when(mockEntry.isRowHeadsUp()).thenReturn(headsUp);
return notifRow;
}
@@ -533,7 +621,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
actual.add(ChildType.ALERTING_HEADER);
continue;
}
- if (child == mSectionsManager.getGentleHeaderView()) {
+ if (child == mSectionsManager.getSilentHeaderView()) {
actual.add(ChildType.GENTLE_HEADER);
continue;
}
@@ -565,7 +653,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
assertThat(actual).containsExactly((Object[]) expected).inOrder();
}
- private void setupMockStack(ChildType... childTypes) {
+ private void setupMockStack(StackEntry... entries) {
final List<View> children = new ArrayList<>();
when(mNssl.getChildCount()).thenAnswer(invocation -> children.size());
when(mNssl.getChildAt(anyInt()))
@@ -590,9 +678,9 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
children.add(newIndex, child);
return null;
}).when(mNssl).changeViewPosition(any(), anyInt());
- for (ChildType childType : childTypes) {
+ for (StackEntry entry : entries) {
View child;
- switch (childType) {
+ switch (entry.mChildType) {
case INCOMING_HEADER:
child = mSectionsManager.getIncomingHeaderView();
break;
@@ -606,22 +694,19 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
child = mSectionsManager.getAlertingHeaderView();
break;
case GENTLE_HEADER:
- child = mSectionsManager.getGentleHeaderView();
- break;
- case HEADS_UP:
- child = mockNotification(BUCKET_HEADS_UP);
+ child = mSectionsManager.getSilentHeaderView();
break;
case FSN:
- child = mockNotification(BUCKET_FOREGROUND_SERVICE);
+ child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsHeadsUp);
break;
case PERSON:
- child = mockNotification(BUCKET_PEOPLE);
+ child = mockNotification(BUCKET_PEOPLE, entry.mIsHeadsUp);
break;
case ALERTING:
- child = mockNotification(BUCKET_ALERTING);
+ child = mockNotification(BUCKET_ALERTING, entry.mIsHeadsUp);
break;
case GENTLE:
- child = mockNotification(BUCKET_SILENT);
+ child = mockNotification(BUCKET_SILENT, entry.mIsHeadsUp);
break;
case OTHER:
child = mock(View.class);
@@ -629,9 +714,48 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
when(child.getParent()).thenReturn(mNssl);
break;
default:
- throw new RuntimeException("Unknown ChildType: " + childType);
+ throw new RuntimeException("Unknown ChildType: " + entry.mChildType);
}
children.add(child);
}
}
+
+ private static final StackEntry INCOMING_HEADER = new StackEntry(ChildType.INCOMING_HEADER);
+ private static final StackEntry MEDIA_CONTROLS = new StackEntry(ChildType.MEDIA_CONTROLS);
+ private static final StackEntry PEOPLE_HEADER = new StackEntry(ChildType.PEOPLE_HEADER);
+ private static final StackEntry ALERTING_HEADER = new StackEntry(ChildType.ALERTING_HEADER);
+ private static final StackEntry GENTLE_HEADER = new StackEntry(ChildType.GENTLE_HEADER);
+ private static final StackEntry FSN = new StackEntry(ChildType.FSN);
+ private static final StackEntry.Hunnable PERSON = new StackEntry.Hunnable(ChildType.PERSON);
+ private static final StackEntry.Hunnable ALERTING = new StackEntry.Hunnable(ChildType.ALERTING);
+ private static final StackEntry GENTLE = new StackEntry(ChildType.GENTLE);
+
+ private static class StackEntry {
+ final ChildType mChildType;
+ final boolean mIsHeadsUp;
+
+ StackEntry(ChildType childType) {
+ this(childType, false);
+ }
+
+ StackEntry(ChildType childType, boolean isHeadsUp) {
+ mChildType = childType;
+ mIsHeadsUp = isHeadsUp;
+ }
+
+ static class Hunnable extends StackEntry {
+
+ Hunnable(ChildType childType) {
+ super(childType, false);
+ }
+
+ Hunnable(ChildType childType, boolean isHeadsUp) {
+ super(childType, isHeadsUp);
+ }
+
+ public Hunnable headsUp() {
+ return new Hunnable(mChildType, true);
+ }
+ }
+ }
}