blob: b5ba3a84fe0fd4a81cfe6dc3c3dd0ebbf59bc830 [file] [log] [blame]
Ned Burns9eb06332019-04-23 16:02:12 -04001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.statusbar.notification.stack;
18
Steve Elliott35956b42020-02-25 17:12:09 -050019import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
20
Evan Laird25f02752019-08-14 19:25:06 -040021import static java.lang.annotation.RetentionPolicy.SOURCE;
22
Steve Elliott577ef792020-04-02 13:49:36 -040023import android.annotation.ColorInt;
Evan Laird25f02752019-08-14 19:25:06 -040024import android.annotation.IntDef;
Steve Elliottb0940382020-02-20 14:24:02 -050025import android.annotation.LayoutRes;
Evan Laird25f02752019-08-14 19:25:06 -040026import android.annotation.NonNull;
Ned Burns9eb06332019-04-23 16:02:12 -040027import android.annotation.Nullable;
Ned Burns9eb06332019-04-23 16:02:12 -040028import android.content.Intent;
29import android.provider.Settings;
30import android.view.LayoutInflater;
31import android.view.View;
32
33import com.android.internal.annotations.VisibleForTesting;
34import com.android.systemui.R;
Selim Cinek5dbef2d2020-05-07 17:44:38 -070035import com.android.systemui.media.KeyguardMediaController;
Ned Burns9eb06332019-04-23 16:02:12 -040036import com.android.systemui.plugins.ActivityStarter;
Ned Burns7eeccdd2019-05-15 14:50:11 -040037import com.android.systemui.plugins.statusbar.StatusBarStateController;
38import com.android.systemui.statusbar.StatusBarState;
Dave Mankoff56fe9e42020-01-08 15:42:06 -050039import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
Steve Elliott58adc212019-10-15 11:07:54 -040040import com.android.systemui.statusbar.notification.people.DataListener;
Steve Elliottb0940382020-02-20 14:24:02 -050041import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter;
42import com.android.systemui.statusbar.notification.people.PeopleHubViewBoundary;
Steve Elliott58adc212019-10-15 11:07:54 -040043import com.android.systemui.statusbar.notification.people.PersonViewModel;
Steve Elliottb0940382020-02-20 14:24:02 -050044import com.android.systemui.statusbar.notification.people.Subscription;
Ned Burns9eb06332019-04-23 16:02:12 -040045import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
46import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Steve Elliottb0940382020-02-20 14:24:02 -050047import com.android.systemui.statusbar.notification.row.ExpandableView;
48import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
Ned Burns2c74c2a2019-06-13 19:06:47 -040049import com.android.systemui.statusbar.policy.ConfigurationController;
50import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
Ned Burns9eb06332019-04-23 16:02:12 -040051
Evan Laird25f02752019-08-14 19:25:06 -040052import java.lang.annotation.Retention;
53import java.util.ArrayList;
54import java.util.List;
Steve Elliottb0940382020-02-20 14:24:02 -050055import java.util.Objects;
Evan Laird25f02752019-08-14 19:25:06 -040056
Dave Mankoff56fe9e42020-01-08 15:42:06 -050057import javax.inject.Inject;
58
Steve Elliott58adc212019-10-15 11:07:54 -040059import kotlin.sequences.Sequence;
60
Ned Burns9eb06332019-04-23 16:02:12 -040061/**
62 * Manages the boundaries of the two notification sections (high priority and low priority). Also
63 * shows/hides the headers for those sections where appropriate.
64 *
65 * TODO: Move remaining sections logic from NSSL into this class.
66 */
Evan Laird25f02752019-08-14 19:25:06 -040067public class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvider {
68
69 private static final String TAG = "NotifSectionsManager";
70 private static final boolean DEBUG = false;
Steve Elliottc4064002020-03-31 11:54:26 -040071 private static final boolean ENABLE_SNOOZED_CONVERSATION_HUB = false;
Evan Laird25f02752019-08-14 19:25:06 -040072
Ned Burns9eb06332019-04-23 16:02:12 -040073 private final ActivityStarter mActivityStarter;
Ned Burns7eeccdd2019-05-15 14:50:11 -040074 private final StatusBarStateController mStatusBarStateController;
Ned Burns2c74c2a2019-06-13 19:06:47 -040075 private final ConfigurationController mConfigurationController;
Steve Elliottb0940382020-02-20 14:24:02 -050076 private final PeopleHubViewAdapter mPeopleHubViewAdapter;
Dave Mankoff56fe9e42020-01-08 15:42:06 -050077 private final NotificationSectionsFeatureManager mSectionsFeatureManager;
Selim Cinek5dbef2d2020-05-07 17:44:38 -070078 private final KeyguardMediaController mKeyguardMediaController;
Steve Elliottb0940382020-02-20 14:24:02 -050079 private final int mNumberOfSections;
Steve Elliott1ba1b7c2020-05-05 14:16:32 -040080 private final NotificationSectionsLogger mLogger;
Steve Elliottb0940382020-02-20 14:24:02 -050081 private final PeopleHubViewBoundary mPeopleHubViewBoundary = new PeopleHubViewBoundary() {
82 @Override
83 public void setVisible(boolean isVisible) {
84 if (mPeopleHubVisible != isVisible) {
85 mPeopleHubVisible = isVisible;
86 if (mInitialized) {
Steve Elliott1ba1b7c2020-05-05 14:16:32 -040087 updateSectionBoundaries("PeopleHub visibility changed");
Steve Elliottb0940382020-02-20 14:24:02 -050088 }
89 }
90 }
91
92 @NonNull
93 @Override
94 public View getAssociatedViewForClickAnimation() {
95 return mPeopleHubView;
96 }
97
98 @NonNull
99 @Override
100 public Sequence<DataListener<PersonViewModel>> getPersonViewAdapters() {
101 return mPeopleHubView.getPersonViewAdapters();
102 }
103 };
104
105 private NotificationStackScrollLayout mParent;
Ned Burns2c74c2a2019-06-13 19:06:47 -0400106 private boolean mInitialized = false;
Steve Elliott58adc212019-10-15 11:07:54 -0400107
Ned Burns9eb06332019-04-23 16:02:12 -0400108 private SectionHeaderView mGentleHeader;
Ned Burns61269442019-05-02 18:27:23 -0400109 @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener;
Ned Burns9eb06332019-04-23 16:02:12 -0400110
Steve Elliottb0940382020-02-20 14:24:02 -0500111 private SectionHeaderView mAlertingHeader;
Steve Elliott19b2c862020-05-05 14:15:14 -0400112 private SectionHeaderView mIncomingHeader;
Steve Elliottb0940382020-02-20 14:24:02 -0500113
114 private PeopleHubView mPeopleHubView;
Steve Elliottb0940382020-02-20 14:24:02 -0500115 private boolean mPeopleHubVisible = false;
116 @Nullable private Subscription mPeopleHubSubscription;
117
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400118 private MediaHeaderView mMediaControlsView;
119
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500120 @Inject
Ned Burns9eb06332019-04-23 16:02:12 -0400121 NotificationSectionsManager(
Ned Burns9eb06332019-04-23 16:02:12 -0400122 ActivityStarter activityStarter,
Ned Burns7eeccdd2019-05-15 14:50:11 -0400123 StatusBarStateController statusBarStateController,
Ned Burns2c74c2a2019-06-13 19:06:47 -0400124 ConfigurationController configurationController,
Steve Elliottb0940382020-02-20 14:24:02 -0500125 PeopleHubViewAdapter peopleHubViewAdapter,
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700126 KeyguardMediaController keyguardMediaController,
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400127 NotificationSectionsFeatureManager sectionsFeatureManager,
128 NotificationSectionsLogger logger) {
129
Ned Burns9eb06332019-04-23 16:02:12 -0400130 mActivityStarter = activityStarter;
Ned Burns7eeccdd2019-05-15 14:50:11 -0400131 mStatusBarStateController = statusBarStateController;
Ned Burns2c74c2a2019-06-13 19:06:47 -0400132 mConfigurationController = configurationController;
Steve Elliott58adc212019-10-15 11:07:54 -0400133 mPeopleHubViewAdapter = peopleHubViewAdapter;
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500134 mSectionsFeatureManager = sectionsFeatureManager;
135 mNumberOfSections = mSectionsFeatureManager.getNumberOfBuckets();
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700136 mKeyguardMediaController = keyguardMediaController;
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400137 mLogger = logger;
Evan Laird25f02752019-08-14 19:25:06 -0400138 }
139
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500140 NotificationSection[] createSectionsForBuckets() {
141 int[] buckets = mSectionsFeatureManager.getNotificationBuckets();
Evan Laird25f02752019-08-14 19:25:06 -0400142 NotificationSection[] sections = new NotificationSection[buckets.length];
143 for (int i = 0; i < buckets.length; i++) {
144 sections[i] = new NotificationSection(mParent, buckets[i] /* bucket */);
145 }
146
147 return sections;
Ned Burns9eb06332019-04-23 16:02:12 -0400148 }
149
Ned Burns2c74c2a2019-06-13 19:06:47 -0400150 /** Must be called before use. */
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500151 void initialize(
152 NotificationStackScrollLayout parent, LayoutInflater layoutInflater) {
Ned Burns2c74c2a2019-06-13 19:06:47 -0400153 if (mInitialized) {
154 throw new IllegalStateException("NotificationSectionsManager already initialized");
155 }
156 mInitialized = true;
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500157 mParent = parent;
Ned Burns2c74c2a2019-06-13 19:06:47 -0400158 reinflateViews(layoutInflater);
159 mConfigurationController.addCallback(mConfigurationListener);
160 }
161
Steve Elliottb0940382020-02-20 14:24:02 -0500162 private <T extends ExpandableView> T reinflateView(
163 T view, LayoutInflater layoutInflater, @LayoutRes int layoutResId) {
164 int oldPos = -1;
165 if (view != null) {
166 if (view.getTransientContainer() != null) {
167 view.getTransientContainer().removeView(mGentleHeader);
168 } else if (view.getParent() != null) {
169 oldPos = mParent.indexOfChild(view);
170 mParent.removeView(view);
171 }
172 }
173
174 view = (T) layoutInflater.inflate(layoutResId, mParent, false);
175
176 if (oldPos != -1) {
177 mParent.addView(view, oldPos);
178 }
179
180 return view;
181 }
182
Ned Burns9eb06332019-04-23 16:02:12 -0400183 /**
Ned Burns2c74c2a2019-06-13 19:06:47 -0400184 * Reinflates the entire notification header, including all decoration views.
Ned Burns9eb06332019-04-23 16:02:12 -0400185 */
Ned Burns2c74c2a2019-06-13 19:06:47 -0400186 void reinflateViews(LayoutInflater layoutInflater) {
Steve Elliottb0940382020-02-20 14:24:02 -0500187 mGentleHeader = reinflateView(
188 mGentleHeader, layoutInflater, R.layout.status_bar_notification_section_header);
189 mGentleHeader.setHeaderText(R.string.notification_section_header_gentle);
Ned Burns9eb06332019-04-23 16:02:12 -0400190 mGentleHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
Ned Burns61269442019-05-02 18:27:23 -0400191 mGentleHeader.setOnClearAllClickListener(this::onClearGentleNotifsClick);
Ned Burns9eb06332019-04-23 16:02:12 -0400192
Steve Elliottb0940382020-02-20 14:24:02 -0500193 mAlertingHeader = reinflateView(
194 mAlertingHeader, layoutInflater, R.layout.status_bar_notification_section_header);
195 mAlertingHeader.setHeaderText(R.string.notification_section_header_alerting);
196 mAlertingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
197
198 if (mPeopleHubSubscription != null) {
199 mPeopleHubSubscription.unsubscribe();
Steve Elliott58adc212019-10-15 11:07:54 -0400200 }
Steve Elliottb0940382020-02-20 14:24:02 -0500201 mPeopleHubView = reinflateView(mPeopleHubView, layoutInflater, R.layout.people_strip);
Steve Elliottc4064002020-03-31 11:54:26 -0400202 if (ENABLE_SNOOZED_CONVERSATION_HUB) {
203 mPeopleHubSubscription = mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
204 }
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400205
Steve Elliott19b2c862020-05-05 14:15:14 -0400206 mIncomingHeader = reinflateView(
207 mIncomingHeader, layoutInflater, R.layout.status_bar_notification_section_header);
208 mIncomingHeader.setHeaderText(R.string.notification_section_header_incoming);
209 mIncomingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
210
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400211 mMediaControlsView = reinflateView(mMediaControlsView, layoutInflater,
212 R.layout.keyguard_media_header);
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700213 mKeyguardMediaController.attach(mMediaControlsView);
Ned Burns9eb06332019-04-23 16:02:12 -0400214 }
215
Steve Elliottb0940382020-02-20 14:24:02 -0500216 /** Listener for when the "clear all" button is clicked on the gentle notification header. */
Ned Burns61269442019-05-02 18:27:23 -0400217 void setOnClearGentleNotifsClickListener(View.OnClickListener listener) {
218 mOnClearGentleNotifsClickListener = listener;
219 }
220
Ned Burns9eb06332019-04-23 16:02:12 -0400221 @Override
Evan Laird25f02752019-08-14 19:25:06 -0400222 public boolean beginsSection(@NonNull View view, @Nullable View previous) {
Steve Elliottb0940382020-02-20 14:24:02 -0500223 return view == mGentleHeader
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400224 || view == mMediaControlsView
Steve Elliottb0940382020-02-20 14:24:02 -0500225 || view == mPeopleHubView
226 || view == mAlertingHeader
Steve Elliott19b2c862020-05-05 14:15:14 -0400227 || view == mIncomingHeader
Steve Elliottb0940382020-02-20 14:24:02 -0500228 || !Objects.equals(getBucket(view), getBucket(previous));
Evan Laird25f02752019-08-14 19:25:06 -0400229 }
230
231 private boolean isUsingMultipleSections() {
232 return mNumberOfSections > 1;
233 }
234
Steve Elliottb0940382020-02-20 14:24:02 -0500235 @Nullable
236 private Integer getBucket(View view) {
237 if (view == mGentleHeader) {
Evan Laird25f02752019-08-14 19:25:06 -0400238 return BUCKET_SILENT;
Steve Elliott19b2c862020-05-05 14:15:14 -0400239 } else if (view == mIncomingHeader) {
240 return BUCKET_HEADS_UP;
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400241 } else if (view == mMediaControlsView) {
242 return BUCKET_MEDIA_CONTROLS;
Steve Elliott58adc212019-10-15 11:07:54 -0400243 } else if (view == mPeopleHubView) {
244 return BUCKET_PEOPLE;
Steve Elliottb0940382020-02-20 14:24:02 -0500245 } else if (view == mAlertingHeader) {
246 return BUCKET_ALERTING;
247 } else if (view instanceof ExpandableNotificationRow) {
248 return ((ExpandableNotificationRow) view).getEntry().getBucket();
Evan Laird25f02752019-08-14 19:25:06 -0400249 }
Steve Elliottb0940382020-02-20 14:24:02 -0500250 return null;
Ned Burns9eb06332019-04-23 16:02:12 -0400251 }
252
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400253 private void logShadeContents() {
254 final int childCount = mParent.getChildCount();
255 for (int i = 0; i < childCount; i++) {
256 View child = mParent.getChildAt(i);
257 if (child == mIncomingHeader) {
258 mLogger.logPosition(i, "INCOMING HEADER");
259 continue;
260 }
261 if (child == mMediaControlsView) {
262 mLogger.logPosition(i, "MEDIA CONTROLS");
263 continue;
264 }
265 if (child == mPeopleHubView) {
266 mLogger.logPosition(i, "CONVERSATIONS HEADER");
267 continue;
268 }
269 if (child == mAlertingHeader) {
270 mLogger.logPosition(i, "ALERTING HEADER");
271 continue;
272 }
273 if (child == mGentleHeader) {
274 mLogger.logPosition(i, "SILENT HEADER");
275 continue;
276 }
277
278 if (!(child instanceof ExpandableNotificationRow)) {
279 mLogger.logPosition(i, "other:" + child.getClass().getName());
280 continue;
281 }
282 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
283 // Once we enter a new section, calculate the target position for the header.
284 switch (row.getEntry().getBucket()) {
285 case BUCKET_HEADS_UP:
286 mLogger.logPosition(i, "Heads Up");
287 break;
288 case BUCKET_PEOPLE:
289 mLogger.logPosition(i, "Conversation");
290 break;
291 case BUCKET_ALERTING:
292 mLogger.logPosition(i, "Alerting");
293 break;
294 case BUCKET_SILENT:
295 mLogger.logPosition(i, "Silent");
296 break;
297 }
298 }
299 }
300
301 @VisibleForTesting
302 void updateSectionBoundaries() {
303 updateSectionBoundaries("test");
304 }
305
Ned Burns9eb06332019-04-23 16:02:12 -0400306 /**
307 * Should be called whenever notifs are added, removed, or updated. Updates section boundary
308 * bookkeeping and adds/moves/removes section headers if appropriate.
309 */
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400310 void updateSectionBoundaries(String reason) {
Evan Laird25f02752019-08-14 19:25:06 -0400311 if (!isUsingMultipleSections()) {
Ned Burns9eb06332019-04-23 16:02:12 -0400312 return;
313 }
314
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400315 mLogger.logStartSectionUpdate(reason);
316
Steve Elliott35956b42020-02-25 17:12:09 -0500317 // The overall strategy here is to iterate over the current children of mParent, looking
318 // for where the sections headers are currently positioned, and where each section begins.
319 // Then, once we find the start of a new section, we track that position as the "target" for
320 // the section header, adjusted for the case where existing headers are in front of that
321 // target, but won't be once they are moved / removed after the pass has completed.
322
Steve Elliottb0940382020-02-20 14:24:02 -0500323 final boolean showHeaders = mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
324 final boolean usingPeopleFiltering = mSectionsFeatureManager.isFilteringEnabled();
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400325 final boolean usingMediaControls = mSectionsFeatureManager.isMediaControlsEnabled();
Ned Burns9eb06332019-04-23 16:02:12 -0400326
Steve Elliottb0940382020-02-20 14:24:02 -0500327 boolean peopleNotifsPresent = false;
Steve Elliott35956b42020-02-25 17:12:09 -0500328
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400329 int currentMediaControlsIdx = -1;
Selim Cinek2d7be5f2020-05-01 13:16:01 -0700330 int mediaControlsTarget = usingMediaControls ? 0 : -1;
Steve Elliott19b2c862020-05-05 14:15:14 -0400331 int currentIncomingHeaderIdx = -1;
332 int incomingHeaderTarget = -1;
Steve Elliott35956b42020-02-25 17:12:09 -0500333 int currentPeopleHeaderIdx = -1;
Steve Elliottb0940382020-02-20 14:24:02 -0500334 int peopleHeaderTarget = -1;
Steve Elliott35956b42020-02-25 17:12:09 -0500335 int currentAlertingHeaderIdx = -1;
Steve Elliottb0940382020-02-20 14:24:02 -0500336 int alertingHeaderTarget = -1;
Steve Elliott35956b42020-02-25 17:12:09 -0500337 int currentGentleHeaderIdx = -1;
Steve Elliottb0940382020-02-20 14:24:02 -0500338 int gentleHeaderTarget = -1;
339
Steve Elliott35956b42020-02-25 17:12:09 -0500340 int lastNotifIndex = 0;
Steve Elliottb0940382020-02-20 14:24:02 -0500341
Steve Elliott35956b42020-02-25 17:12:09 -0500342 final int childCount = mParent.getChildCount();
343 for (int i = 0; i < childCount; i++) {
344 View child = mParent.getChildAt(i);
345
346 // Track the existing positions of the headers
Steve Elliott19b2c862020-05-05 14:15:14 -0400347 if (child == mIncomingHeader) {
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400348 mLogger.logPosition(i, "INCOMING HEADER");
Steve Elliott19b2c862020-05-05 14:15:14 -0400349 currentIncomingHeaderIdx = i;
350 continue;
351 }
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400352 if (child == mMediaControlsView) {
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400353 mLogger.logPosition(i, "MEDIA CONTROLS");
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400354 currentMediaControlsIdx = i;
355 continue;
356 }
Steve Elliott35956b42020-02-25 17:12:09 -0500357 if (child == mPeopleHubView) {
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400358 mLogger.logPosition(i, "CONVERSATIONS HEADER");
Steve Elliott35956b42020-02-25 17:12:09 -0500359 currentPeopleHeaderIdx = i;
360 continue;
Steve Elliottb0940382020-02-20 14:24:02 -0500361 }
Steve Elliott35956b42020-02-25 17:12:09 -0500362 if (child == mAlertingHeader) {
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400363 mLogger.logPosition(i, "ALERTING HEADER");
Steve Elliott35956b42020-02-25 17:12:09 -0500364 currentAlertingHeaderIdx = i;
365 continue;
366 }
367 if (child == mGentleHeader) {
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400368 mLogger.logPosition(i, "SILENT HEADER");
Steve Elliott35956b42020-02-25 17:12:09 -0500369 currentGentleHeaderIdx = i;
370 continue;
371 }
372
373 if (!(child instanceof ExpandableNotificationRow)) {
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400374 mLogger.logPosition(i, "other");
Steve Elliott35956b42020-02-25 17:12:09 -0500375 continue;
376 }
377 lastNotifIndex = i;
378 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
379 // Once we enter a new section, calculate the target position for the header.
380 switch (row.getEntry().getBucket()) {
381 case BUCKET_HEADS_UP:
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400382 mLogger.logPosition(i, "Heads Up");
Steve Elliott19b2c862020-05-05 14:15:14 -0400383 if (showHeaders && incomingHeaderTarget == -1) {
384 incomingHeaderTarget = i;
385 // Offset the target if there are other headers before this that will be
386 // moved.
387 if (currentIncomingHeaderIdx != -1) {
388 incomingHeaderTarget--;
389 }
390 if (currentMediaControlsIdx != -1) {
391 incomingHeaderTarget--;
392 }
393 if (currentPeopleHeaderIdx != -1) {
394 incomingHeaderTarget--;
395 }
396 if (currentAlertingHeaderIdx != -1) {
397 incomingHeaderTarget--;
398 }
399 if (currentGentleHeaderIdx != -1) {
400 incomingHeaderTarget--;
401 }
402 }
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400403 if (mediaControlsTarget != -1) {
404 mediaControlsTarget++;
405 }
Steve Elliott35956b42020-02-25 17:12:09 -0500406 break;
Steve Elliott49671e02020-05-12 13:51:28 -0400407 case BUCKET_FOREGROUND_SERVICE:
408 if (mediaControlsTarget != -1) {
409 mediaControlsTarget++;
410 }
411 break;
Steve Elliott35956b42020-02-25 17:12:09 -0500412 case BUCKET_PEOPLE:
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400413 mLogger.logPosition(i, "Conversation");
Steve Elliott35956b42020-02-25 17:12:09 -0500414 peopleNotifsPresent = true;
415 if (showHeaders && peopleHeaderTarget == -1) {
416 peopleHeaderTarget = i;
417 // Offset the target if there are other headers before this that will be
418 // moved.
419 if (currentPeopleHeaderIdx != -1) {
420 peopleHeaderTarget--;
421 }
422 if (currentAlertingHeaderIdx != -1) {
423 peopleHeaderTarget--;
424 }
425 if (currentGentleHeaderIdx != -1) {
426 peopleHeaderTarget--;
427 }
428 }
429 break;
430 case BUCKET_ALERTING:
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400431 mLogger.logPosition(i, "Alerting");
Steve Elliott35956b42020-02-25 17:12:09 -0500432 if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
433 alertingHeaderTarget = i;
434 // Offset the target if there are other headers before this that will be
435 // moved.
436 if (currentAlertingHeaderIdx != -1) {
437 alertingHeaderTarget--;
438 }
439 if (currentGentleHeaderIdx != -1) {
440 alertingHeaderTarget--;
441 }
442 }
443 break;
444 case BUCKET_SILENT:
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400445 mLogger.logPosition(i, "Silent");
Steve Elliott35956b42020-02-25 17:12:09 -0500446 if (showHeaders && gentleHeaderTarget == -1) {
447 gentleHeaderTarget = i;
448 // Offset the target if there are other headers before this that will be
449 // moved.
450 if (currentGentleHeaderIdx != -1) {
451 gentleHeaderTarget--;
452 }
453 }
454 break;
455 default:
456 throw new IllegalStateException("Cannot find section bucket for view");
457 }
458 }
459 if (showHeaders && usingPeopleFiltering && mPeopleHubVisible && peopleHeaderTarget == -1) {
460 // Insert the people header even if there are no people visible, in order to show
461 // the hub. Put it directly above the next header.
462 if (alertingHeaderTarget != -1) {
463 peopleHeaderTarget = alertingHeaderTarget;
464 } else if (gentleHeaderTarget != -1) {
465 peopleHeaderTarget = gentleHeaderTarget;
466 } else {
467 // Put it at the end of the list.
468 peopleHeaderTarget = lastNotifIndex;
Ned Burns9eb06332019-04-23 16:02:12 -0400469 }
Steve Elliott6de51a72020-03-05 11:00:47 -0500470 // Offset the target to account for the current position of the people header.
471 if (currentPeopleHeaderIdx != -1 && currentPeopleHeaderIdx < peopleHeaderTarget) {
472 peopleHeaderTarget--;
473 }
Ned Burns9eb06332019-04-23 16:02:12 -0400474 }
475
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400476 mLogger.logStr("New header target positions:");
477
478 mLogger.logPosition(incomingHeaderTarget, "INCOMING HEADER");
479 mLogger.logPosition(mediaControlsTarget, "MEDIA CONTROLS");
480 mLogger.logPosition(peopleHeaderTarget, "CONVERSATIONS HEADER");
481 mLogger.logPosition(alertingHeaderTarget, "ALERTING HEADER");
482 mLogger.logPosition(gentleHeaderTarget, "SILENT HEADER");
483
Steve Elliott35956b42020-02-25 17:12:09 -0500484 // Add headers in reverse order to preserve indices
485 adjustHeaderVisibilityAndPosition(
486 gentleHeaderTarget, mGentleHeader, currentGentleHeaderIdx);
487 adjustHeaderVisibilityAndPosition(
488 alertingHeaderTarget, mAlertingHeader, currentAlertingHeaderIdx);
489 adjustHeaderVisibilityAndPosition(
490 peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx);
Steve Elliott19b2c862020-05-05 14:15:14 -0400491 adjustViewPosition(mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx);
Steve Elliott49671e02020-05-12 13:51:28 -0400492 adjustHeaderVisibilityAndPosition(incomingHeaderTarget, mIncomingHeader,
493 currentIncomingHeaderIdx);
Steve Elliott58adc212019-10-15 11:07:54 -0400494
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400495
496 mLogger.logStr("Final order:");
497
498 logShadeContents();
499
500 mLogger.logStr("Section boundary update complete");
501
Steve Elliott35956b42020-02-25 17:12:09 -0500502 // Update headers to reflect state of section contents
503 mGentleHeader.setAreThereDismissableGentleNotifs(
504 mParent.hasActiveClearableNotifications(ROWS_GENTLE));
505 mPeopleHubView.setCanSwipe(showHeaders && mPeopleHubVisible && !peopleNotifsPresent);
506 if (peopleHeaderTarget != currentPeopleHeaderIdx) {
507 mPeopleHubView.resetTranslation();
508 }
Ned Burns9eb06332019-04-23 16:02:12 -0400509 }
510
Steve Elliott35956b42020-02-25 17:12:09 -0500511 private void adjustHeaderVisibilityAndPosition(
512 int targetPosition, StackScrollerDecorView header, int currentPosition) {
Steve Elliott49671e02020-05-12 13:51:28 -0400513 adjustViewPosition(targetPosition, header, currentPosition);
514 if (targetPosition != -1 && currentPosition == -1) {
515 header.setContentVisible(true);
Ned Burns9eb06332019-04-23 16:02:12 -0400516 }
517 }
518
Steve Elliott49671e02020-05-12 13:51:28 -0400519 private void adjustViewPosition(int targetPosition, ExpandableView view, int currentPosition) {
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400520 if (targetPosition == -1) {
521 if (currentPosition != -1) {
Steve Elliott49671e02020-05-12 13:51:28 -0400522 mParent.removeView(view);
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400523 }
524 } else {
525 if (currentPosition == -1) {
526 // If the header is animating away, it will still have a parent, so detach it first
527 // TODO: We should really cancel the active animations here. This will happen
528 // automatically when the view's intro animation starts, but it's a fragile link.
Steve Elliott49671e02020-05-12 13:51:28 -0400529 if (view.getTransientContainer() != null) {
530 view.getTransientContainer().removeTransientView(view);
531 view.setTransientContainer(null);
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400532 }
Steve Elliott49671e02020-05-12 13:51:28 -0400533 mParent.addView(view, targetPosition);
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400534 } else {
Steve Elliott49671e02020-05-12 13:51:28 -0400535 mParent.changeViewPosition(view, targetPosition);
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400536 }
537 }
538 }
539
Ned Burns9eb06332019-04-23 16:02:12 -0400540 /**
Evan Laird25f02752019-08-14 19:25:06 -0400541 * Updates the boundaries (as tracked by their first and last views) of the priority sections.
Ned Burns9eb06332019-04-23 16:02:12 -0400542 *
543 * @return {@code true} If the last view in the top section changed (so we need to animate).
544 */
Evan Laird25f02752019-08-14 19:25:06 -0400545 boolean updateFirstAndLastViewsForAllSections(
546 NotificationSection[] sections,
547 List<ActivatableNotificationView> children) {
548
549 if (sections.length <= 0 || children.size() <= 0) {
550 for (NotificationSection s : sections) {
551 s.setFirstVisibleChild(null);
552 s.setLastVisibleChild(null);
Ned Burns9eb06332019-04-23 16:02:12 -0400553 }
Ned Burns9eb06332019-04-23 16:02:12 -0400554 return false;
555 }
Evan Laird25f02752019-08-14 19:25:06 -0400556
557 boolean changed = false;
558 ArrayList<ActivatableNotificationView> viewsInBucket = new ArrayList<>();
559 for (NotificationSection s : sections) {
560 int filter = s.getBucket();
561 viewsInBucket.clear();
562
563 //TODO: do this in a single pass, and more better
564 for (ActivatableNotificationView v : children) {
Steve Elliottb0940382020-02-20 14:24:02 -0500565 Integer bucket = getBucket(v);
566 if (bucket == null) {
567 throw new IllegalArgumentException("Cannot find section bucket for view");
568 }
569
570 if (bucket == filter) {
Evan Laird25f02752019-08-14 19:25:06 -0400571 viewsInBucket.add(v);
572 }
573
574 if (viewsInBucket.size() >= 1) {
575 changed |= s.setFirstVisibleChild(viewsInBucket.get(0));
576 changed |= s.setLastVisibleChild(viewsInBucket.get(viewsInBucket.size() - 1));
577 } else {
578 changed |= s.setFirstVisibleChild(null);
579 changed |= s.setLastVisibleChild(null);
580 }
581 }
582 }
583
584 if (DEBUG) {
585 logSections(sections);
586 }
587
588 return changed;
Ned Burns9eb06332019-04-23 16:02:12 -0400589 }
590
Evan Laird25f02752019-08-14 19:25:06 -0400591 private void logSections(NotificationSection[] sections) {
592 for (int i = 0; i < sections.length; i++) {
593 NotificationSection s = sections[i];
594 ActivatableNotificationView first = s.getFirstVisibleChild();
595 String fs = first == null ? "(null)"
596 : (first instanceof ExpandableNotificationRow)
Ned Burns00b4b2d2019-10-17 22:09:27 -0400597 ? ((ExpandableNotificationRow) first).getEntry().getKey()
Evan Laird25f02752019-08-14 19:25:06 -0400598 : Integer.toHexString(System.identityHashCode(first));
599 ActivatableNotificationView last = s.getLastVisibleChild();
600 String ls = last == null ? "(null)"
601 : (last instanceof ExpandableNotificationRow)
Ned Burns00b4b2d2019-10-17 22:09:27 -0400602 ? ((ExpandableNotificationRow) last).getEntry().getKey()
Evan Laird25f02752019-08-14 19:25:06 -0400603 : Integer.toHexString(System.identityHashCode(last));
604 android.util.Log.d(TAG, "updateSections: f=" + fs + " s=" + i);
605 android.util.Log.d(TAG, "updateSections: l=" + ls + " s=" + i);
606 }
607 }
608
Ned Burns9eb06332019-04-23 16:02:12 -0400609 @VisibleForTesting
Steve Elliott35956b42020-02-25 17:12:09 -0500610 ExpandableView getGentleHeaderView() {
Ned Burns9eb06332019-04-23 16:02:12 -0400611 return mGentleHeader;
612 }
613
Steve Elliott35956b42020-02-25 17:12:09 -0500614 @VisibleForTesting
615 ExpandableView getAlertingHeaderView() {
616 return mAlertingHeader;
617 }
618
619 @VisibleForTesting
620 ExpandableView getPeopleHeaderView() {
621 return mPeopleHubView;
622 }
623
Steve Elliott6de51a72020-03-05 11:00:47 -0500624 @VisibleForTesting
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400625 ExpandableView getMediaControlsView() {
626 return mMediaControlsView;
627 }
628
629 @VisibleForTesting
Steve Elliott49671e02020-05-12 13:51:28 -0400630 ExpandableView getIncomingHeaderView() {
631 return mIncomingHeader;
632 }
633
634 @VisibleForTesting
Steve Elliott6de51a72020-03-05 11:00:47 -0500635 void setPeopleHubVisible(boolean visible) {
636 mPeopleHubVisible = visible;
637 }
638
Ned Burns2c74c2a2019-06-13 19:06:47 -0400639 private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
640 @Override
641 public void onLocaleListChanged() {
Steve Elliott59e23eb2020-03-18 16:13:19 -0400642 reinflateViews(LayoutInflater.from(mParent.getContext()));
Ned Burns2c74c2a2019-06-13 19:06:47 -0400643 }
644 };
645
Ned Burns9eb06332019-04-23 16:02:12 -0400646 private void onGentleHeaderClick(View v) {
647 Intent intent = new Intent(Settings.ACTION_NOTIFICATION_SETTINGS);
648 mActivityStarter.startActivity(
649 intent,
650 true,
651 true,
652 Intent.FLAG_ACTIVITY_SINGLE_TOP);
653 }
Ned Burns61269442019-05-02 18:27:23 -0400654
655 private void onClearGentleNotifsClick(View v) {
656 if (mOnClearGentleNotifsClickListener != null) {
657 mOnClearGentleNotifsClickListener.onClick(v);
658 }
659 }
Evan Laird25f02752019-08-14 19:25:06 -0400660
Steve Elliott946b00c2019-10-28 11:20:24 -0400661 void hidePeopleRow() {
662 mPeopleHubVisible = false;
Steve Elliott1ba1b7c2020-05-05 14:16:32 -0400663 updateSectionBoundaries("PeopleHub dismissed");
Steve Elliott58adc212019-10-15 11:07:54 -0400664 }
665
Steve Elliott577ef792020-04-02 13:49:36 -0400666 void setHeaderForegroundColor(@ColorInt int color) {
667 mPeopleHubView.setTextColor(color);
668 mGentleHeader.setForegroundColor(color);
669 mAlertingHeader.setForegroundColor(color);
670 }
671
Evan Laird25f02752019-08-14 19:25:06 -0400672 /**
673 * For now, declare the available notification buckets (sections) here so that other
674 * presentation code can decide what to do based on an entry's buckets
Evan Laird25f02752019-08-14 19:25:06 -0400675 */
676 @Retention(SOURCE)
677 @IntDef(prefix = { "BUCKET_" }, value = {
Steve Elliottb0940382020-02-20 14:24:02 -0500678 BUCKET_HEADS_UP,
Steve Elliott49671e02020-05-12 13:51:28 -0400679 BUCKET_FOREGROUND_SERVICE,
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400680 BUCKET_MEDIA_CONTROLS,
Evan Laird25f02752019-08-14 19:25:06 -0400681 BUCKET_PEOPLE,
682 BUCKET_ALERTING,
683 BUCKET_SILENT
684 })
685 public @interface PriorityBucket {}
Steve Elliottb0940382020-02-20 14:24:02 -0500686 public static final int BUCKET_HEADS_UP = 0;
Steve Elliott49671e02020-05-12 13:51:28 -0400687 public static final int BUCKET_FOREGROUND_SERVICE = 1;
688 public static final int BUCKET_MEDIA_CONTROLS = 2;
689 public static final int BUCKET_PEOPLE = 3;
690 public static final int BUCKET_ALERTING = 4;
691 public static final int BUCKET_SILENT = 5;
Ned Burns9eb06332019-04-23 16:02:12 -0400692}