Merge changes I4d1d38ea,Ibd3f73bc,Ic476fbb0 into rvc-dev
* changes: Rework Incoming section logic Fix TODO in #updateFirstAndLastViewsForAllSections Convert NotificationSectionsManager to Kotlin
This commit is contained in:
committed by
Android (Google) Code Review
commit
2a952d33f6
@@ -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 android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
|
||||||
|
|
||||||
import static com.android.systemui.DejankUtils.whitelistIpcs;
|
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.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
|
||||||
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
|
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
|
||||||
|
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.KeyguardManager;
|
import android.app.KeyguardManager;
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import android.provider.DeviceConfig
|
|||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting
|
import com.android.internal.annotations.VisibleForTesting
|
||||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
|
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.BUCKET_ALERTING
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
|
import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
|
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS
|
import com.android.systemui.statusbar.notification.stack.BUCKET_MEDIA_CONTROLS
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
|
import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
|
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
|
||||||
import com.android.systemui.util.DeviceConfigProxy
|
import com.android.systemui.util.DeviceConfigProxy
|
||||||
import com.android.systemui.util.Utils
|
import com.android.systemui.util.Utils
|
||||||
|
|
||||||
|
|||||||
@@ -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 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.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;
|
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.ExpandableNotificationRow;
|
||||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
|
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
|
||||||
import com.android.systemui.statusbar.notification.row.NotificationGuts;
|
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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -409,12 +409,12 @@ public final class NotificationEntry extends ListEntry {
|
|||||||
return wasBubble != isBubble();
|
return wasBubble != isBubble();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotificationSectionsManager.PriorityBucket
|
@PriorityBucket
|
||||||
public int getBucket() {
|
public int getBucket() {
|
||||||
return mBucket;
|
return mBucket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBucket(@NotificationSectionsManager.PriorityBucket int bucket) {
|
public void setBucket(@PriorityBucket int bucket) {
|
||||||
mBucket = bucket;
|
mBucket = bucket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.collection.provider.HighPriorityProvider
|
||||||
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
|
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.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
|
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
|
import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
|
import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
|
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
|
import com.android.systemui.statusbar.notification.stack.PriorityBucket
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.PriorityBucket
|
|
||||||
import com.android.systemui.statusbar.phone.NotificationGroupManager
|
import com.android.systemui.statusbar.phone.NotificationGroupManager
|
||||||
import com.android.systemui.statusbar.policy.HeadsUpManager
|
import com.android.systemui.statusbar.policy.HeadsUpManager
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
@@ -138,23 +137,8 @@ open class NotificationRankingManager @Inject constructor(
|
|||||||
.filterNot(notifFilter::shouldFilterOut)
|
.filterNot(notifFilter::shouldFilterOut)
|
||||||
.sortedWith(rankingComparator)
|
.sortedWith(rankingComparator)
|
||||||
.toList()
|
.toList()
|
||||||
assignBuckets(filtered)
|
|
||||||
return filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun assignBuckets(entries: List<NotificationEntry>) {
|
|
||||||
entries.forEach { it.bucket = getBucketForEntry(it) }
|
entries.forEach { it.bucket = getBucketForEntry(it) }
|
||||||
if (!usePeopleFiltering) {
|
return filtered
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PriorityBucket
|
@PriorityBucket
|
||||||
|
|||||||
@@ -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
|
* Represents the bounds of a section of the notification shade and handles animation when the
|
||||||
* bounds change.
|
* bounds change.
|
||||||
*/
|
*/
|
||||||
class NotificationSection {
|
public class NotificationSection {
|
||||||
private @NotificationSectionsManager.PriorityBucket int mBucket;
|
private @PriorityBucket int mBucket;
|
||||||
private View mOwningView;
|
private View mOwningView;
|
||||||
private Rect mBounds = new Rect();
|
private Rect mBounds = new Rect();
|
||||||
private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
|
private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
|
||||||
@@ -44,7 +44,7 @@ class NotificationSection {
|
|||||||
private ActivatableNotificationView mFirstVisibleChild;
|
private ActivatableNotificationView mFirstVisibleChild;
|
||||||
private ActivatableNotificationView mLastVisibleChild;
|
private ActivatableNotificationView mLastVisibleChild;
|
||||||
|
|
||||||
NotificationSection(View owningView, @NotificationSectionsManager.PriorityBucket int bucket) {
|
NotificationSection(View owningView, @PriorityBucket int bucket) {
|
||||||
mOwningView = owningView;
|
mOwningView = owningView;
|
||||||
mBucket = bucket;
|
mBucket = bucket;
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ class NotificationSection {
|
|||||||
return mBottomAnimator != null || mTopAnimator != null;
|
return mBottomAnimator != null || mTopAnimator != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotificationSectionsManager.PriorityBucket
|
@PriorityBucket
|
||||||
public int getBucket() {
|
public int getBucket() {
|
||||||
return mBucket;
|
return mBucket;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,14 +52,35 @@ class NotificationSectionsLogger @Inject constructor(
|
|||||||
{ "$int1: other ($str1)" }
|
{ "$int1: other ($str1)" }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun logHeadsUp(position: Int) = logPosition(position, "Heads Up")
|
fun logHeadsUp(position: Int, isHeadsUp: Boolean) =
|
||||||
fun logConversation(position: Int) = logPosition(position, "Conversation")
|
logPosition(position, "Heads Up", isHeadsUp)
|
||||||
fun logAlerting(position: Int) = logPosition(position, "Alerting")
|
fun logConversation(position: Int, isHeadsUp: Boolean) =
|
||||||
fun logSilent(position: Int) = logPosition(position, "Silent")
|
logPosition(position, "Conversation", isHeadsUp)
|
||||||
fun logForegroundService(position: Int) = logPosition(position, "Foreground Service")
|
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" })
|
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(
|
private fun logPosition(position: Int, label: String) = logBuffer.log(
|
||||||
TAG,
|
TAG,
|
||||||
LogLevel.DEBUG,
|
LogLevel.DEBUG,
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
@@ -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.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
|
||||||
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
|
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.StackScrollAlgorithm.ANCHOR_SCROLLING;
|
||||||
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
|
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
|
||||||
import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
|
import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
|
||||||
@@ -577,7 +577,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
|
|||||||
|
|
||||||
mSectionsManager = notificationSectionsManager;
|
mSectionsManager = notificationSectionsManager;
|
||||||
mSectionsManager.initialize(this, LayoutInflater.from(context));
|
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
|
// Leave the shade open if there will be other notifs left over to clear
|
||||||
final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
|
final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
|
||||||
clearNotifications(ROWS_GENTLE, closeShade);
|
clearNotifications(ROWS_GENTLE, closeShade);
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -20,9 +20,9 @@ import static android.app.NotificationManager.IMPORTANCE_LOW;
|
|||||||
import static android.content.Intent.ACTION_USER_SWITCHED;
|
import static android.content.Intent.ACTION_USER_SWITCHED;
|
||||||
import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL;
|
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.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
|
||||||
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
|
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
|
||||||
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
|
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
|
||||||
|
|
||||||
import static junit.framework.Assert.assertFalse;
|
import static junit.framework.Assert.assertFalse;
|
||||||
import static junit.framework.Assert.assertTrue;
|
import static junit.framework.Assert.assertTrue;
|
||||||
|
|||||||
@@ -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_IMPORTANT_PERSON
|
||||||
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_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.row.ExpandableNotificationRow
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
|
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
|
||||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
|
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
|
||||||
import com.android.systemui.statusbar.phone.NotificationGroupManager
|
import com.android.systemui.statusbar.phone.NotificationGroupManager
|
||||||
import com.android.systemui.statusbar.policy.HeadsUpManager
|
import com.android.systemui.statusbar.policy.HeadsUpManager
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.statusbar.notification.logging;
|
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.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ package com.android.systemui.statusbar.notification.stack;
|
|||||||
|
|
||||||
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
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.NotificationSectionsManagerKt.BUCKET_ALERTING;
|
||||||
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE;
|
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE;
|
||||||
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP;
|
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP;
|
||||||
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
|
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
|
||||||
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.google.common.truth.Truth.assertThat;
|
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.plugins.statusbar.StatusBarStateController;
|
||||||
import com.android.systemui.statusbar.StatusBarState;
|
import com.android.systemui.statusbar.StatusBarState;
|
||||||
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
|
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.people.PeopleHubViewAdapter;
|
||||||
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
|
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
|
||||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||||
@@ -135,140 +136,152 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testInsertHeader() {
|
public void testInsertHeader() {
|
||||||
// GIVEN a stack with HI and LO rows but no section headers
|
// 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
|
// WHEN we update the section headers
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// THEN a LO section header is added
|
// THEN a LO section header is added
|
||||||
verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3);
|
verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoveHeader() {
|
public void testRemoveHeader() {
|
||||||
// GIVEN a stack that originally had a header between the HI and LO sections
|
// 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();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// WHEN the last LO row is replaced with a HI row
|
// WHEN the last LO row is replaced with a HI row
|
||||||
setStackState(
|
setStackState(
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE_HEADER,
|
GENTLE_HEADER,
|
||||||
ChildType.ALERTING);
|
ALERTING);
|
||||||
clearInvocations(mNssl);
|
clearInvocations(mNssl);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// THEN the LO section header is removed
|
// THEN the LO section header is removed
|
||||||
verify(mNssl).removeView(mSectionsManager.getGentleHeaderView());
|
verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDoNothingIfHeaderAlreadyRemoved() {
|
public void testDoNothingIfHeaderAlreadyRemoved() {
|
||||||
// GIVEN a stack with only HI rows
|
// GIVEN a stack with only HI rows
|
||||||
setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING);
|
setStackState(
|
||||||
|
ALERTING,
|
||||||
|
ALERTING,
|
||||||
|
ALERTING);
|
||||||
|
|
||||||
// WHEN we update the sections headers
|
// WHEN we update the sections headers
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// THEN we don't add any section headers
|
// 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
|
@Test
|
||||||
public void testMoveHeaderForward() {
|
public void testMoveHeaderForward() {
|
||||||
// GIVEN a stack that originally had a header between the HI and LO sections
|
// GIVEN a stack that originally had a header between the HI and LO sections
|
||||||
setStackState(
|
setStackState(
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE);
|
GENTLE);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// WHEN the LO section moves forward
|
// WHEN the LO section moves forward
|
||||||
setStackState(
|
setStackState(
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE,
|
GENTLE,
|
||||||
ChildType.GENTLE_HEADER,
|
GENTLE_HEADER,
|
||||||
ChildType.GENTLE);
|
GENTLE);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// THEN the LO section header is also moved forward
|
// THEN the LO section header is also moved forward
|
||||||
verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2);
|
verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMoveHeaderBackward() {
|
public void testMoveHeaderBackward() {
|
||||||
// GIVEN a stack that originally had a header between the HI and LO sections
|
// GIVEN a stack that originally had a header between the HI and LO sections
|
||||||
setStackState(
|
setStackState(
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE,
|
GENTLE,
|
||||||
ChildType.GENTLE,
|
GENTLE,
|
||||||
ChildType.GENTLE);
|
GENTLE);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// WHEN the LO section moves backward
|
// WHEN the LO section moves backward
|
||||||
setStackState(
|
setStackState(
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE_HEADER,
|
GENTLE_HEADER,
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE);
|
GENTLE);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// THEN the LO section header is also moved backward (with appropriate index shifting)
|
// 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
|
@Test
|
||||||
public void testHeaderRemovedFromTransientParent() {
|
public void testHeaderRemovedFromTransientParent() {
|
||||||
// GIVEN a stack where the header is animating away
|
// GIVEN a stack where the header is animating away
|
||||||
setStackState(
|
setStackState(
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE,
|
GENTLE_HEADER);
|
||||||
ChildType.GENTLE,
|
|
||||||
ChildType.GENTLE);
|
|
||||||
mSectionsManager.updateSectionBoundaries();
|
|
||||||
setStackState(
|
|
||||||
ChildType.ALERTING,
|
|
||||||
ChildType.GENTLE_HEADER);
|
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
clearInvocations(mNssl);
|
clearInvocations(mNssl);
|
||||||
|
|
||||||
ViewGroup transientParent = mock(ViewGroup.class);
|
ViewGroup transientParent = mock(ViewGroup.class);
|
||||||
mSectionsManager.getGentleHeaderView().setTransientContainer(transientParent);
|
mSectionsManager.getSilentHeaderView().setTransientContainer(transientParent);
|
||||||
|
|
||||||
// WHEN the LO section reappears
|
// WHEN the LO section reappears
|
||||||
setStackState(
|
setStackState(
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE);
|
GENTLE);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// THEN the header is first removed from the transient parent before being added to the
|
// THEN the header is first removed from the transient parent before being added to the
|
||||||
// NSSL.
|
// NSSL.
|
||||||
verify(transientParent).removeTransientView(mSectionsManager.getGentleHeaderView());
|
verify(transientParent).removeTransientView(mSectionsManager.getSilentHeaderView());
|
||||||
verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 1);
|
verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHeaderNotShownOnLockscreen() {
|
public void testHeaderNotShownOnLockscreen() {
|
||||||
// GIVEN a stack of HI and LO notifs on the lockscreen
|
// GIVEN a stack of HI and LO notifs on the lockscreen
|
||||||
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
|
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
|
// WHEN we update the section headers
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// Then the section header is not added
|
// Then the section header is not added
|
||||||
verify(mNssl, never()).addView(eq(mSectionsManager.getGentleHeaderView()), anyInt());
|
verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHeaderShownWhenEnterLockscreen() {
|
public void testHeaderShownWhenEnterLockscreen() {
|
||||||
// GIVEN a stack of HI and LO notifs on the lockscreen
|
// GIVEN a stack of HI and LO notifs on the lockscreen
|
||||||
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
|
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
|
||||||
setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
|
setStackState(
|
||||||
|
ALERTING,
|
||||||
|
ALERTING,
|
||||||
|
ALERTING,
|
||||||
|
GENTLE);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// WHEN we unlock
|
// WHEN we unlock
|
||||||
@@ -276,20 +289,23 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// Then the section header is added
|
// Then the section header is added
|
||||||
verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 3);
|
verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHeaderHiddenWhenEnterLockscreen() {
|
public void testHeaderHiddenWhenEnterLockscreen() {
|
||||||
// GIVEN a stack of HI and LO notifs on the shade
|
// 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 we go back to the keyguard
|
||||||
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
|
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
// Then the section header is removed
|
// Then the section header is removed
|
||||||
verify(mNssl).removeView(mSectionsManager.getGentleHeaderView());
|
verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -297,13 +313,13 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
enablePeopleFiltering();
|
enablePeopleFiltering();
|
||||||
|
|
||||||
setStackState(
|
setStackState(
|
||||||
ChildType.GENTLE_HEADER,
|
GENTLE_HEADER,
|
||||||
ChildType.PERSON,
|
PERSON,
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE);
|
GENTLE);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2);
|
verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2);
|
||||||
verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
|
verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
|
||||||
verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
|
verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
|
||||||
}
|
}
|
||||||
@@ -313,12 +329,12 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
enablePeopleFiltering();
|
enablePeopleFiltering();
|
||||||
|
|
||||||
setStackState(
|
setStackState(
|
||||||
ChildType.PERSON,
|
PERSON,
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE);
|
GENTLE);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 2);
|
verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 2);
|
||||||
verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
|
verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
|
||||||
verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
|
verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
|
||||||
}
|
}
|
||||||
@@ -328,15 +344,15 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
enablePeopleFiltering();
|
enablePeopleFiltering();
|
||||||
|
|
||||||
setStackState(
|
setStackState(
|
||||||
ChildType.PEOPLE_HEADER,
|
PEOPLE_HEADER,
|
||||||
ChildType.ALERTING_HEADER,
|
ALERTING_HEADER,
|
||||||
ChildType.GENTLE_HEADER,
|
GENTLE_HEADER,
|
||||||
ChildType.PERSON,
|
PERSON,
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE);
|
GENTLE);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 4);
|
verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 4);
|
||||||
verify(mNssl).changeViewPosition(mSectionsManager.getAlertingHeaderView(), 2);
|
verify(mNssl).changeViewPosition(mSectionsManager.getAlertingHeaderView(), 2);
|
||||||
verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0);
|
verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0);
|
||||||
}
|
}
|
||||||
@@ -347,12 +363,11 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
enablePeopleFiltering();
|
enablePeopleFiltering();
|
||||||
|
|
||||||
setStackState(
|
setStackState(
|
||||||
ChildType.PEOPLE_HEADER,
|
PEOPLE_HEADER,
|
||||||
ChildType.ALERTING_HEADER,
|
ALERTING_HEADER,
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE_HEADER,
|
GENTLE_HEADER,
|
||||||
ChildType.GENTLE
|
GENTLE);
|
||||||
);
|
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
verify(mNssl, never()).removeView(mSectionsManager.getPeopleHeaderView());
|
verify(mNssl, never()).removeView(mSectionsManager.getPeopleHeaderView());
|
||||||
@@ -360,16 +375,16 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPeopleFiltering_HunWhilePeopleVisible() {
|
public void testPeopleFiltering_AlertingHunWhilePeopleVisible() {
|
||||||
enablePeopleFiltering();
|
enablePeopleFiltering();
|
||||||
|
|
||||||
setupMockStack(
|
setupMockStack(
|
||||||
ChildType.PEOPLE_HEADER,
|
PEOPLE_HEADER,
|
||||||
ChildType.HEADS_UP,
|
ALERTING.headsUp(),
|
||||||
ChildType.PERSON,
|
PERSON,
|
||||||
ChildType.ALERTING_HEADER,
|
ALERTING_HEADER,
|
||||||
ChildType.GENTLE_HEADER,
|
GENTLE_HEADER,
|
||||||
ChildType.GENTLE
|
GENTLE
|
||||||
);
|
);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
@@ -383,18 +398,75 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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.PERSON
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPeopleFiltering_AlertingHunWhilePersonHunning() {
|
||||||
|
enablePeopleFiltering();
|
||||||
|
|
||||||
|
setupMockStack(
|
||||||
|
ALERTING.headsUp(),
|
||||||
|
PERSON.headsUp()
|
||||||
|
);
|
||||||
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
verifyMockStack(
|
||||||
|
ChildType.INCOMING_HEADER,
|
||||||
|
ChildType.HEADS_UP,
|
||||||
|
ChildType.PEOPLE_HEADER,
|
||||||
|
ChildType.PERSON
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPeopleFiltering_Fsn() {
|
public void testPeopleFiltering_Fsn() {
|
||||||
enablePeopleFiltering();
|
enablePeopleFiltering();
|
||||||
|
|
||||||
setupMockStack(
|
setupMockStack(
|
||||||
ChildType.INCOMING_HEADER,
|
INCOMING_HEADER,
|
||||||
ChildType.HEADS_UP,
|
ALERTING.headsUp(),
|
||||||
ChildType.PEOPLE_HEADER,
|
PEOPLE_HEADER,
|
||||||
ChildType.FSN,
|
FSN,
|
||||||
ChildType.PERSON,
|
PERSON,
|
||||||
ChildType.ALERTING,
|
ALERTING,
|
||||||
ChildType.GENTLE
|
GENTLE
|
||||||
);
|
);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
@@ -416,7 +488,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
enableMediaControls();
|
enableMediaControls();
|
||||||
|
|
||||||
// GIVEN a stack that doesn't include media controls
|
// 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 we go back to the keyguard
|
||||||
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
|
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
|
||||||
@@ -431,14 +503,20 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
enableMediaControls();
|
enableMediaControls();
|
||||||
|
|
||||||
// GIVEN a stack that doesn't include media controls but includes HEADS_UP
|
// GIVEN a stack that doesn't include media controls but includes HEADS_UP
|
||||||
setupMockStack(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER,
|
setupMockStack(
|
||||||
ChildType.GENTLE);
|
ALERTING.headsUp(),
|
||||||
|
ALERTING,
|
||||||
|
GENTLE_HEADER,
|
||||||
|
GENTLE);
|
||||||
|
|
||||||
// WHEN we go back to the keyguard
|
// WHEN we go back to the keyguard
|
||||||
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
|
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
|
||||||
mSectionsManager.updateSectionBoundaries();
|
mSectionsManager.updateSectionBoundaries();
|
||||||
|
|
||||||
verifyMockStack(ChildType.HEADS_UP, ChildType.MEDIA_CONTROLS, ChildType.ALERTING,
|
verifyMockStack(
|
||||||
|
ChildType.MEDIA_CONTROLS,
|
||||||
|
ChildType.ALERTING,
|
||||||
|
ChildType.ALERTING,
|
||||||
ChildType.GENTLE);
|
ChildType.GENTLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,11 +533,12 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
FSN, PERSON, ALERTING, GENTLE, OTHER
|
FSN, PERSON, ALERTING, GENTLE, OTHER
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setStackState(ChildType... children) {
|
private void setStackState(StackEntry... children) {
|
||||||
when(mNssl.getChildCount()).thenReturn(children.length);
|
when(mNssl.getChildCount()).thenReturn(children.length);
|
||||||
for (int i = 0; i < children.length; i++) {
|
for (int i = 0; i < children.length; i++) {
|
||||||
View child;
|
View child;
|
||||||
switch (children[i]) {
|
StackEntry entry = children[i];
|
||||||
|
switch (entry.mChildType) {
|
||||||
case INCOMING_HEADER:
|
case INCOMING_HEADER:
|
||||||
child = mSectionsManager.getIncomingHeaderView();
|
child = mSectionsManager.getIncomingHeaderView();
|
||||||
break;
|
break;
|
||||||
@@ -473,22 +552,19 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
child = mSectionsManager.getAlertingHeaderView();
|
child = mSectionsManager.getAlertingHeaderView();
|
||||||
break;
|
break;
|
||||||
case GENTLE_HEADER:
|
case GENTLE_HEADER:
|
||||||
child = mSectionsManager.getGentleHeaderView();
|
child = mSectionsManager.getSilentHeaderView();
|
||||||
break;
|
|
||||||
case HEADS_UP:
|
|
||||||
child = mockNotification(BUCKET_HEADS_UP);
|
|
||||||
break;
|
break;
|
||||||
case FSN:
|
case FSN:
|
||||||
child = mockNotification(BUCKET_FOREGROUND_SERVICE);
|
child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsHeadsUp);
|
||||||
break;
|
break;
|
||||||
case PERSON:
|
case PERSON:
|
||||||
child = mockNotification(BUCKET_PEOPLE);
|
child = mockNotification(BUCKET_PEOPLE, entry.mIsHeadsUp);
|
||||||
break;
|
break;
|
||||||
case ALERTING:
|
case ALERTING:
|
||||||
child = mockNotification(BUCKET_ALERTING);
|
child = mockNotification(BUCKET_ALERTING, entry.mIsHeadsUp);
|
||||||
break;
|
break;
|
||||||
case GENTLE:
|
case GENTLE:
|
||||||
child = mockNotification(BUCKET_SILENT);
|
child = mockNotification(BUCKET_SILENT, entry.mIsHeadsUp);
|
||||||
break;
|
break;
|
||||||
case OTHER:
|
case OTHER:
|
||||||
child = mock(View.class);
|
child = mock(View.class);
|
||||||
@@ -503,12 +579,24 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private View mockNotification(int bucket) {
|
private View mockNotification(int bucket, boolean headsUp) {
|
||||||
ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
|
ExpandableNotificationRow notifRow =
|
||||||
RETURNS_DEEP_STUBS);
|
mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
|
||||||
when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
|
when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
|
||||||
when(notifRow.getEntry().getBucket()).thenReturn(bucket);
|
|
||||||
when(notifRow.getParent()).thenReturn(mNssl);
|
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;
|
return notifRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,7 +621,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
actual.add(ChildType.ALERTING_HEADER);
|
actual.add(ChildType.ALERTING_HEADER);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (child == mSectionsManager.getGentleHeaderView()) {
|
if (child == mSectionsManager.getSilentHeaderView()) {
|
||||||
actual.add(ChildType.GENTLE_HEADER);
|
actual.add(ChildType.GENTLE_HEADER);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -565,7 +653,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
assertThat(actual).containsExactly((Object[]) expected).inOrder();
|
assertThat(actual).containsExactly((Object[]) expected).inOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupMockStack(ChildType... childTypes) {
|
private void setupMockStack(StackEntry... entries) {
|
||||||
final List<View> children = new ArrayList<>();
|
final List<View> children = new ArrayList<>();
|
||||||
when(mNssl.getChildCount()).thenAnswer(invocation -> children.size());
|
when(mNssl.getChildCount()).thenAnswer(invocation -> children.size());
|
||||||
when(mNssl.getChildAt(anyInt()))
|
when(mNssl.getChildAt(anyInt()))
|
||||||
@@ -590,9 +678,9 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
children.add(newIndex, child);
|
children.add(newIndex, child);
|
||||||
return null;
|
return null;
|
||||||
}).when(mNssl).changeViewPosition(any(), anyInt());
|
}).when(mNssl).changeViewPosition(any(), anyInt());
|
||||||
for (ChildType childType : childTypes) {
|
for (StackEntry entry : entries) {
|
||||||
View child;
|
View child;
|
||||||
switch (childType) {
|
switch (entry.mChildType) {
|
||||||
case INCOMING_HEADER:
|
case INCOMING_HEADER:
|
||||||
child = mSectionsManager.getIncomingHeaderView();
|
child = mSectionsManager.getIncomingHeaderView();
|
||||||
break;
|
break;
|
||||||
@@ -606,22 +694,19 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
child = mSectionsManager.getAlertingHeaderView();
|
child = mSectionsManager.getAlertingHeaderView();
|
||||||
break;
|
break;
|
||||||
case GENTLE_HEADER:
|
case GENTLE_HEADER:
|
||||||
child = mSectionsManager.getGentleHeaderView();
|
child = mSectionsManager.getSilentHeaderView();
|
||||||
break;
|
|
||||||
case HEADS_UP:
|
|
||||||
child = mockNotification(BUCKET_HEADS_UP);
|
|
||||||
break;
|
break;
|
||||||
case FSN:
|
case FSN:
|
||||||
child = mockNotification(BUCKET_FOREGROUND_SERVICE);
|
child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsHeadsUp);
|
||||||
break;
|
break;
|
||||||
case PERSON:
|
case PERSON:
|
||||||
child = mockNotification(BUCKET_PEOPLE);
|
child = mockNotification(BUCKET_PEOPLE, entry.mIsHeadsUp);
|
||||||
break;
|
break;
|
||||||
case ALERTING:
|
case ALERTING:
|
||||||
child = mockNotification(BUCKET_ALERTING);
|
child = mockNotification(BUCKET_ALERTING, entry.mIsHeadsUp);
|
||||||
break;
|
break;
|
||||||
case GENTLE:
|
case GENTLE:
|
||||||
child = mockNotification(BUCKET_SILENT);
|
child = mockNotification(BUCKET_SILENT, entry.mIsHeadsUp);
|
||||||
break;
|
break;
|
||||||
case OTHER:
|
case OTHER:
|
||||||
child = mock(View.class);
|
child = mock(View.class);
|
||||||
@@ -629,9 +714,48 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
|||||||
when(child.getParent()).thenReturn(mNssl);
|
when(child.getParent()).thenReturn(mNssl);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Unknown ChildType: " + childType);
|
throw new RuntimeException("Unknown ChildType: " + entry.mChildType);
|
||||||
}
|
}
|
||||||
children.add(child);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user