blob: 2a257f6198d7b1460f9552217a2eaee1ada29ce6 [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;
Selim Cinek5dbef2d2020-05-07 17:44:38 -070032import android.view.ViewGroup;
Ned Burns9eb06332019-04-23 16:02:12 -040033
34import com.android.internal.annotations.VisibleForTesting;
35import com.android.systemui.R;
Selim Cinek5dbef2d2020-05-07 17:44:38 -070036import com.android.systemui.media.KeyguardMediaController;
37import com.android.systemui.media.MediaHierarchyManager;
Ned Burns9eb06332019-04-23 16:02:12 -040038import com.android.systemui.plugins.ActivityStarter;
Ned Burns7eeccdd2019-05-15 14:50:11 -040039import com.android.systemui.plugins.statusbar.StatusBarStateController;
40import com.android.systemui.statusbar.StatusBarState;
Dave Mankoff56fe9e42020-01-08 15:42:06 -050041import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
Steve Elliott58adc212019-10-15 11:07:54 -040042import com.android.systemui.statusbar.notification.people.DataListener;
Steve Elliottb0940382020-02-20 14:24:02 -050043import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter;
44import com.android.systemui.statusbar.notification.people.PeopleHubViewBoundary;
Steve Elliott58adc212019-10-15 11:07:54 -040045import com.android.systemui.statusbar.notification.people.PersonViewModel;
Steve Elliottb0940382020-02-20 14:24:02 -050046import com.android.systemui.statusbar.notification.people.Subscription;
Ned Burns9eb06332019-04-23 16:02:12 -040047import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
48import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Steve Elliottb0940382020-02-20 14:24:02 -050049import com.android.systemui.statusbar.notification.row.ExpandableView;
50import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
Ned Burns2c74c2a2019-06-13 19:06:47 -040051import com.android.systemui.statusbar.policy.ConfigurationController;
52import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
Ned Burns9eb06332019-04-23 16:02:12 -040053
Evan Laird25f02752019-08-14 19:25:06 -040054import java.lang.annotation.Retention;
55import java.util.ArrayList;
56import java.util.List;
Steve Elliottb0940382020-02-20 14:24:02 -050057import java.util.Objects;
Evan Laird25f02752019-08-14 19:25:06 -040058
Dave Mankoff56fe9e42020-01-08 15:42:06 -050059import javax.inject.Inject;
60
Steve Elliott58adc212019-10-15 11:07:54 -040061import kotlin.sequences.Sequence;
62
Ned Burns9eb06332019-04-23 16:02:12 -040063/**
64 * Manages the boundaries of the two notification sections (high priority and low priority). Also
65 * shows/hides the headers for those sections where appropriate.
66 *
67 * TODO: Move remaining sections logic from NSSL into this class.
68 */
Evan Laird25f02752019-08-14 19:25:06 -040069public class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvider {
70
71 private static final String TAG = "NotifSectionsManager";
72 private static final boolean DEBUG = false;
Steve Elliottc4064002020-03-31 11:54:26 -040073 private static final boolean ENABLE_SNOOZED_CONVERSATION_HUB = false;
Evan Laird25f02752019-08-14 19:25:06 -040074
Ned Burns9eb06332019-04-23 16:02:12 -040075 private final ActivityStarter mActivityStarter;
Ned Burns7eeccdd2019-05-15 14:50:11 -040076 private final StatusBarStateController mStatusBarStateController;
Ned Burns2c74c2a2019-06-13 19:06:47 -040077 private final ConfigurationController mConfigurationController;
Steve Elliottb0940382020-02-20 14:24:02 -050078 private final PeopleHubViewAdapter mPeopleHubViewAdapter;
Dave Mankoff56fe9e42020-01-08 15:42:06 -050079 private final NotificationSectionsFeatureManager mSectionsFeatureManager;
Selim Cinek5dbef2d2020-05-07 17:44:38 -070080 private final KeyguardMediaController mKeyguardMediaController;
Steve Elliottb0940382020-02-20 14:24:02 -050081 private final int mNumberOfSections;
82
83 private final PeopleHubViewBoundary mPeopleHubViewBoundary = new PeopleHubViewBoundary() {
84 @Override
85 public void setVisible(boolean isVisible) {
86 if (mPeopleHubVisible != isVisible) {
87 mPeopleHubVisible = isVisible;
88 if (mInitialized) {
89 updateSectionBoundaries();
90 }
91 }
92 }
93
94 @NonNull
95 @Override
96 public View getAssociatedViewForClickAnimation() {
97 return mPeopleHubView;
98 }
99
100 @NonNull
101 @Override
102 public Sequence<DataListener<PersonViewModel>> getPersonViewAdapters() {
103 return mPeopleHubView.getPersonViewAdapters();
104 }
105 };
106
107 private NotificationStackScrollLayout mParent;
Ned Burns2c74c2a2019-06-13 19:06:47 -0400108 private boolean mInitialized = false;
Steve Elliott58adc212019-10-15 11:07:54 -0400109
Ned Burns9eb06332019-04-23 16:02:12 -0400110 private SectionHeaderView mGentleHeader;
Ned Burns61269442019-05-02 18:27:23 -0400111 @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener;
Ned Burns9eb06332019-04-23 16:02:12 -0400112
Steve Elliottb0940382020-02-20 14:24:02 -0500113 private SectionHeaderView mAlertingHeader;
Steve Elliott19b2c862020-05-05 14:15:14 -0400114 private SectionHeaderView mIncomingHeader;
Steve Elliottb0940382020-02-20 14:24:02 -0500115
116 private PeopleHubView mPeopleHubView;
Steve Elliottb0940382020-02-20 14:24:02 -0500117 private boolean mPeopleHubVisible = false;
118 @Nullable private Subscription mPeopleHubSubscription;
119
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400120 private MediaHeaderView mMediaControlsView;
121
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500122 @Inject
Ned Burns9eb06332019-04-23 16:02:12 -0400123 NotificationSectionsManager(
Ned Burns9eb06332019-04-23 16:02:12 -0400124 ActivityStarter activityStarter,
Ned Burns7eeccdd2019-05-15 14:50:11 -0400125 StatusBarStateController statusBarStateController,
Ned Burns2c74c2a2019-06-13 19:06:47 -0400126 ConfigurationController configurationController,
Steve Elliottb0940382020-02-20 14:24:02 -0500127 PeopleHubViewAdapter peopleHubViewAdapter,
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700128 KeyguardMediaController keyguardMediaController,
Steve Elliottb0940382020-02-20 14:24:02 -0500129 NotificationSectionsFeatureManager sectionsFeatureManager) {
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;
Evan Laird25f02752019-08-14 19:25:06 -0400137 }
138
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500139 NotificationSection[] createSectionsForBuckets() {
140 int[] buckets = mSectionsFeatureManager.getNotificationBuckets();
Evan Laird25f02752019-08-14 19:25:06 -0400141 NotificationSection[] sections = new NotificationSection[buckets.length];
142 for (int i = 0; i < buckets.length; i++) {
143 sections[i] = new NotificationSection(mParent, buckets[i] /* bucket */);
144 }
145
146 return sections;
Ned Burns9eb06332019-04-23 16:02:12 -0400147 }
148
Ned Burns2c74c2a2019-06-13 19:06:47 -0400149 /** Must be called before use. */
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500150 void initialize(
151 NotificationStackScrollLayout parent, LayoutInflater layoutInflater) {
Ned Burns2c74c2a2019-06-13 19:06:47 -0400152 if (mInitialized) {
153 throw new IllegalStateException("NotificationSectionsManager already initialized");
154 }
155 mInitialized = true;
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500156 mParent = parent;
Ned Burns2c74c2a2019-06-13 19:06:47 -0400157 reinflateViews(layoutInflater);
158 mConfigurationController.addCallback(mConfigurationListener);
159 }
160
Steve Elliottb0940382020-02-20 14:24:02 -0500161 private <T extends ExpandableView> T reinflateView(
162 T view, LayoutInflater layoutInflater, @LayoutRes int layoutResId) {
163 int oldPos = -1;
164 if (view != null) {
165 if (view.getTransientContainer() != null) {
166 view.getTransientContainer().removeView(mGentleHeader);
167 } else if (view.getParent() != null) {
168 oldPos = mParent.indexOfChild(view);
169 mParent.removeView(view);
170 }
171 }
172
173 view = (T) layoutInflater.inflate(layoutResId, mParent, false);
174
175 if (oldPos != -1) {
176 mParent.addView(view, oldPos);
177 }
178
179 return view;
180 }
181
Ned Burns9eb06332019-04-23 16:02:12 -0400182 /**
Ned Burns2c74c2a2019-06-13 19:06:47 -0400183 * Reinflates the entire notification header, including all decoration views.
Ned Burns9eb06332019-04-23 16:02:12 -0400184 */
Ned Burns2c74c2a2019-06-13 19:06:47 -0400185 void reinflateViews(LayoutInflater layoutInflater) {
Steve Elliottb0940382020-02-20 14:24:02 -0500186 mGentleHeader = reinflateView(
187 mGentleHeader, layoutInflater, R.layout.status_bar_notification_section_header);
188 mGentleHeader.setHeaderText(R.string.notification_section_header_gentle);
Ned Burns9eb06332019-04-23 16:02:12 -0400189 mGentleHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
Ned Burns61269442019-05-02 18:27:23 -0400190 mGentleHeader.setOnClearAllClickListener(this::onClearGentleNotifsClick);
Ned Burns9eb06332019-04-23 16:02:12 -0400191
Steve Elliottb0940382020-02-20 14:24:02 -0500192 mAlertingHeader = reinflateView(
193 mAlertingHeader, layoutInflater, R.layout.status_bar_notification_section_header);
194 mAlertingHeader.setHeaderText(R.string.notification_section_header_alerting);
195 mAlertingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
196
197 if (mPeopleHubSubscription != null) {
198 mPeopleHubSubscription.unsubscribe();
Steve Elliott58adc212019-10-15 11:07:54 -0400199 }
Steve Elliottb0940382020-02-20 14:24:02 -0500200 mPeopleHubView = reinflateView(mPeopleHubView, layoutInflater, R.layout.people_strip);
Steve Elliottc4064002020-03-31 11:54:26 -0400201 if (ENABLE_SNOOZED_CONVERSATION_HUB) {
202 mPeopleHubSubscription = mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
203 }
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400204
Steve Elliott19b2c862020-05-05 14:15:14 -0400205 mIncomingHeader = reinflateView(
206 mIncomingHeader, layoutInflater, R.layout.status_bar_notification_section_header);
207 mIncomingHeader.setHeaderText(R.string.notification_section_header_incoming);
208 mIncomingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
209
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400210 mMediaControlsView = reinflateView(mMediaControlsView, layoutInflater,
211 R.layout.keyguard_media_header);
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700212 mKeyguardMediaController.attach(mMediaControlsView);
Ned Burns9eb06332019-04-23 16:02:12 -0400213 }
214
Steve Elliottb0940382020-02-20 14:24:02 -0500215 /** Listener for when the "clear all" button is clicked on the gentle notification header. */
Ned Burns61269442019-05-02 18:27:23 -0400216 void setOnClearGentleNotifsClickListener(View.OnClickListener listener) {
217 mOnClearGentleNotifsClickListener = listener;
218 }
219
Ned Burns9eb06332019-04-23 16:02:12 -0400220 @Override
Evan Laird25f02752019-08-14 19:25:06 -0400221 public boolean beginsSection(@NonNull View view, @Nullable View previous) {
Steve Elliottb0940382020-02-20 14:24:02 -0500222 return view == mGentleHeader
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400223 || view == mMediaControlsView
Steve Elliottb0940382020-02-20 14:24:02 -0500224 || view == mPeopleHubView
225 || view == mAlertingHeader
Steve Elliott19b2c862020-05-05 14:15:14 -0400226 || view == mIncomingHeader
Steve Elliottb0940382020-02-20 14:24:02 -0500227 || !Objects.equals(getBucket(view), getBucket(previous));
Evan Laird25f02752019-08-14 19:25:06 -0400228 }
229
230 private boolean isUsingMultipleSections() {
231 return mNumberOfSections > 1;
232 }
233
Steve Elliottb0940382020-02-20 14:24:02 -0500234 @Nullable
235 private Integer getBucket(View view) {
236 if (view == mGentleHeader) {
Evan Laird25f02752019-08-14 19:25:06 -0400237 return BUCKET_SILENT;
Steve Elliott19b2c862020-05-05 14:15:14 -0400238 } else if (view == mIncomingHeader) {
239 return BUCKET_HEADS_UP;
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400240 } else if (view == mMediaControlsView) {
241 return BUCKET_MEDIA_CONTROLS;
Steve Elliott58adc212019-10-15 11:07:54 -0400242 } else if (view == mPeopleHubView) {
243 return BUCKET_PEOPLE;
Steve Elliottb0940382020-02-20 14:24:02 -0500244 } else if (view == mAlertingHeader) {
245 return BUCKET_ALERTING;
246 } else if (view instanceof ExpandableNotificationRow) {
247 return ((ExpandableNotificationRow) view).getEntry().getBucket();
Evan Laird25f02752019-08-14 19:25:06 -0400248 }
Steve Elliottb0940382020-02-20 14:24:02 -0500249 return null;
Ned Burns9eb06332019-04-23 16:02:12 -0400250 }
251
252 /**
253 * Should be called whenever notifs are added, removed, or updated. Updates section boundary
254 * bookkeeping and adds/moves/removes section headers if appropriate.
255 */
256 void updateSectionBoundaries() {
Evan Laird25f02752019-08-14 19:25:06 -0400257 if (!isUsingMultipleSections()) {
Ned Burns9eb06332019-04-23 16:02:12 -0400258 return;
259 }
260
Steve Elliott35956b42020-02-25 17:12:09 -0500261 // The overall strategy here is to iterate over the current children of mParent, looking
262 // for where the sections headers are currently positioned, and where each section begins.
263 // Then, once we find the start of a new section, we track that position as the "target" for
264 // the section header, adjusted for the case where existing headers are in front of that
265 // target, but won't be once they are moved / removed after the pass has completed.
266
Steve Elliottb0940382020-02-20 14:24:02 -0500267 final boolean showHeaders = mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
268 final boolean usingPeopleFiltering = mSectionsFeatureManager.isFilteringEnabled();
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400269 final boolean usingMediaControls = mSectionsFeatureManager.isMediaControlsEnabled();
Ned Burns9eb06332019-04-23 16:02:12 -0400270
Steve Elliottb0940382020-02-20 14:24:02 -0500271 boolean peopleNotifsPresent = false;
Steve Elliott35956b42020-02-25 17:12:09 -0500272
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400273 int currentMediaControlsIdx = -1;
274 // Currently, just putting media controls in the front and incrementing the position based
275 // on the number of heads-up notifs.
Selim Cinek2d7be5f2020-05-01 13:16:01 -0700276 int mediaControlsTarget = usingMediaControls ? 0 : -1;
Steve Elliott19b2c862020-05-05 14:15:14 -0400277 int currentIncomingHeaderIdx = -1;
278 int incomingHeaderTarget = -1;
Steve Elliott35956b42020-02-25 17:12:09 -0500279 int currentPeopleHeaderIdx = -1;
Steve Elliottb0940382020-02-20 14:24:02 -0500280 int peopleHeaderTarget = -1;
Steve Elliott35956b42020-02-25 17:12:09 -0500281 int currentAlertingHeaderIdx = -1;
Steve Elliottb0940382020-02-20 14:24:02 -0500282 int alertingHeaderTarget = -1;
Steve Elliott35956b42020-02-25 17:12:09 -0500283 int currentGentleHeaderIdx = -1;
Steve Elliottb0940382020-02-20 14:24:02 -0500284 int gentleHeaderTarget = -1;
285
Steve Elliott35956b42020-02-25 17:12:09 -0500286 int lastNotifIndex = 0;
Steve Elliottb0940382020-02-20 14:24:02 -0500287
Steve Elliott35956b42020-02-25 17:12:09 -0500288 final int childCount = mParent.getChildCount();
289 for (int i = 0; i < childCount; i++) {
290 View child = mParent.getChildAt(i);
291
292 // Track the existing positions of the headers
Steve Elliott19b2c862020-05-05 14:15:14 -0400293 if (child == mIncomingHeader) {
294 currentIncomingHeaderIdx = i;
295 continue;
296 }
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400297 if (child == mMediaControlsView) {
298 currentMediaControlsIdx = i;
299 continue;
300 }
Steve Elliott35956b42020-02-25 17:12:09 -0500301 if (child == mPeopleHubView) {
302 currentPeopleHeaderIdx = i;
303 continue;
Steve Elliottb0940382020-02-20 14:24:02 -0500304 }
Steve Elliott35956b42020-02-25 17:12:09 -0500305 if (child == mAlertingHeader) {
306 currentAlertingHeaderIdx = i;
307 continue;
308 }
309 if (child == mGentleHeader) {
310 currentGentleHeaderIdx = i;
311 continue;
312 }
313
314 if (!(child instanceof ExpandableNotificationRow)) {
315 continue;
316 }
317 lastNotifIndex = i;
318 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
319 // Once we enter a new section, calculate the target position for the header.
320 switch (row.getEntry().getBucket()) {
321 case BUCKET_HEADS_UP:
Steve Elliott19b2c862020-05-05 14:15:14 -0400322 if (showHeaders && incomingHeaderTarget == -1) {
323 incomingHeaderTarget = i;
324 // Offset the target if there are other headers before this that will be
325 // moved.
326 if (currentIncomingHeaderIdx != -1) {
327 incomingHeaderTarget--;
328 }
329 if (currentMediaControlsIdx != -1) {
330 incomingHeaderTarget--;
331 }
332 if (currentPeopleHeaderIdx != -1) {
333 incomingHeaderTarget--;
334 }
335 if (currentAlertingHeaderIdx != -1) {
336 incomingHeaderTarget--;
337 }
338 if (currentGentleHeaderIdx != -1) {
339 incomingHeaderTarget--;
340 }
341 }
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400342 if (mediaControlsTarget != -1) {
343 mediaControlsTarget++;
344 }
Steve Elliott35956b42020-02-25 17:12:09 -0500345 break;
346 case BUCKET_PEOPLE:
347 peopleNotifsPresent = true;
348 if (showHeaders && peopleHeaderTarget == -1) {
349 peopleHeaderTarget = i;
350 // Offset the target if there are other headers before this that will be
351 // moved.
352 if (currentPeopleHeaderIdx != -1) {
353 peopleHeaderTarget--;
354 }
355 if (currentAlertingHeaderIdx != -1) {
356 peopleHeaderTarget--;
357 }
358 if (currentGentleHeaderIdx != -1) {
359 peopleHeaderTarget--;
360 }
361 }
362 break;
363 case BUCKET_ALERTING:
364 if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
365 alertingHeaderTarget = i;
366 // Offset the target if there are other headers before this that will be
367 // moved.
368 if (currentAlertingHeaderIdx != -1) {
369 alertingHeaderTarget--;
370 }
371 if (currentGentleHeaderIdx != -1) {
372 alertingHeaderTarget--;
373 }
374 }
375 break;
376 case BUCKET_SILENT:
377 if (showHeaders && gentleHeaderTarget == -1) {
378 gentleHeaderTarget = i;
379 // Offset the target if there are other headers before this that will be
380 // moved.
381 if (currentGentleHeaderIdx != -1) {
382 gentleHeaderTarget--;
383 }
384 }
385 break;
386 default:
387 throw new IllegalStateException("Cannot find section bucket for view");
388 }
389 }
390 if (showHeaders && usingPeopleFiltering && mPeopleHubVisible && peopleHeaderTarget == -1) {
391 // Insert the people header even if there are no people visible, in order to show
392 // the hub. Put it directly above the next header.
393 if (alertingHeaderTarget != -1) {
394 peopleHeaderTarget = alertingHeaderTarget;
395 } else if (gentleHeaderTarget != -1) {
396 peopleHeaderTarget = gentleHeaderTarget;
397 } else {
398 // Put it at the end of the list.
399 peopleHeaderTarget = lastNotifIndex;
Ned Burns9eb06332019-04-23 16:02:12 -0400400 }
Steve Elliott6de51a72020-03-05 11:00:47 -0500401 // Offset the target to account for the current position of the people header.
402 if (currentPeopleHeaderIdx != -1 && currentPeopleHeaderIdx < peopleHeaderTarget) {
403 peopleHeaderTarget--;
404 }
Ned Burns9eb06332019-04-23 16:02:12 -0400405 }
406
Steve Elliott35956b42020-02-25 17:12:09 -0500407 // Add headers in reverse order to preserve indices
408 adjustHeaderVisibilityAndPosition(
409 gentleHeaderTarget, mGentleHeader, currentGentleHeaderIdx);
410 adjustHeaderVisibilityAndPosition(
411 alertingHeaderTarget, mAlertingHeader, currentAlertingHeaderIdx);
412 adjustHeaderVisibilityAndPosition(
413 peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx);
Steve Elliott19b2c862020-05-05 14:15:14 -0400414 adjustViewPosition(mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx);
415 adjustViewPosition(incomingHeaderTarget, mIncomingHeader, currentIncomingHeaderIdx);
Steve Elliott58adc212019-10-15 11:07:54 -0400416
Steve Elliott35956b42020-02-25 17:12:09 -0500417 // Update headers to reflect state of section contents
418 mGentleHeader.setAreThereDismissableGentleNotifs(
419 mParent.hasActiveClearableNotifications(ROWS_GENTLE));
420 mPeopleHubView.setCanSwipe(showHeaders && mPeopleHubVisible && !peopleNotifsPresent);
421 if (peopleHeaderTarget != currentPeopleHeaderIdx) {
422 mPeopleHubView.resetTranslation();
423 }
Ned Burns9eb06332019-04-23 16:02:12 -0400424 }
425
Steve Elliott35956b42020-02-25 17:12:09 -0500426 private void adjustHeaderVisibilityAndPosition(
427 int targetPosition, StackScrollerDecorView header, int currentPosition) {
428 if (targetPosition == -1) {
429 if (currentPosition != -1) {
Steve Elliottb0940382020-02-20 14:24:02 -0500430 mParent.removeView(header);
Ned Burns9eb06332019-04-23 16:02:12 -0400431 }
432 } else {
Steve Elliott35956b42020-02-25 17:12:09 -0500433 if (currentPosition == -1) {
Ned Burns9eb06332019-04-23 16:02:12 -0400434 // If the header is animating away, it will still have a parent, so detach it first
435 // TODO: We should really cancel the active animations here. This will happen
436 // automatically when the view's intro animation starts, but it's a fragile link.
Steve Elliottb0940382020-02-20 14:24:02 -0500437 if (header.getTransientContainer() != null) {
438 header.getTransientContainer().removeTransientView(header);
439 header.setTransientContainer(null);
Ned Burns9eb06332019-04-23 16:02:12 -0400440 }
Steve Elliottb0940382020-02-20 14:24:02 -0500441 header.setContentVisible(true);
Steve Elliott35956b42020-02-25 17:12:09 -0500442 mParent.addView(header, targetPosition);
443 } else {
444 mParent.changeViewPosition(header, targetPosition);
Ned Burns9eb06332019-04-23 16:02:12 -0400445 }
446 }
447 }
448
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400449 private void adjustViewPosition(int targetPosition, ExpandableView header,
450 int currentPosition) {
451 if (targetPosition == -1) {
452 if (currentPosition != -1) {
453 mParent.removeView(header);
454 }
455 } else {
456 if (currentPosition == -1) {
457 // If the header is animating away, it will still have a parent, so detach it first
458 // TODO: We should really cancel the active animations here. This will happen
459 // automatically when the view's intro animation starts, but it's a fragile link.
460 if (header.getTransientContainer() != null) {
461 header.getTransientContainer().removeTransientView(header);
462 header.setTransientContainer(null);
463 }
464 mParent.addView(header, targetPosition);
465 } else {
466 mParent.changeViewPosition(header, targetPosition);
467 }
468 }
469 }
470
Ned Burns9eb06332019-04-23 16:02:12 -0400471 /**
Evan Laird25f02752019-08-14 19:25:06 -0400472 * Updates the boundaries (as tracked by their first and last views) of the priority sections.
Ned Burns9eb06332019-04-23 16:02:12 -0400473 *
474 * @return {@code true} If the last view in the top section changed (so we need to animate).
475 */
Evan Laird25f02752019-08-14 19:25:06 -0400476 boolean updateFirstAndLastViewsForAllSections(
477 NotificationSection[] sections,
478 List<ActivatableNotificationView> children) {
479
480 if (sections.length <= 0 || children.size() <= 0) {
481 for (NotificationSection s : sections) {
482 s.setFirstVisibleChild(null);
483 s.setLastVisibleChild(null);
Ned Burns9eb06332019-04-23 16:02:12 -0400484 }
Ned Burns9eb06332019-04-23 16:02:12 -0400485 return false;
486 }
Evan Laird25f02752019-08-14 19:25:06 -0400487
488 boolean changed = false;
489 ArrayList<ActivatableNotificationView> viewsInBucket = new ArrayList<>();
490 for (NotificationSection s : sections) {
491 int filter = s.getBucket();
492 viewsInBucket.clear();
493
494 //TODO: do this in a single pass, and more better
495 for (ActivatableNotificationView v : children) {
Steve Elliottb0940382020-02-20 14:24:02 -0500496 Integer bucket = getBucket(v);
497 if (bucket == null) {
498 throw new IllegalArgumentException("Cannot find section bucket for view");
499 }
500
501 if (bucket == filter) {
Evan Laird25f02752019-08-14 19:25:06 -0400502 viewsInBucket.add(v);
503 }
504
505 if (viewsInBucket.size() >= 1) {
506 changed |= s.setFirstVisibleChild(viewsInBucket.get(0));
507 changed |= s.setLastVisibleChild(viewsInBucket.get(viewsInBucket.size() - 1));
508 } else {
509 changed |= s.setFirstVisibleChild(null);
510 changed |= s.setLastVisibleChild(null);
511 }
512 }
513 }
514
515 if (DEBUG) {
516 logSections(sections);
517 }
518
519 return changed;
Ned Burns9eb06332019-04-23 16:02:12 -0400520 }
521
Evan Laird25f02752019-08-14 19:25:06 -0400522 private void logSections(NotificationSection[] sections) {
523 for (int i = 0; i < sections.length; i++) {
524 NotificationSection s = sections[i];
525 ActivatableNotificationView first = s.getFirstVisibleChild();
526 String fs = first == null ? "(null)"
527 : (first instanceof ExpandableNotificationRow)
Ned Burns00b4b2d2019-10-17 22:09:27 -0400528 ? ((ExpandableNotificationRow) first).getEntry().getKey()
Evan Laird25f02752019-08-14 19:25:06 -0400529 : Integer.toHexString(System.identityHashCode(first));
530 ActivatableNotificationView last = s.getLastVisibleChild();
531 String ls = last == null ? "(null)"
532 : (last instanceof ExpandableNotificationRow)
Ned Burns00b4b2d2019-10-17 22:09:27 -0400533 ? ((ExpandableNotificationRow) last).getEntry().getKey()
Evan Laird25f02752019-08-14 19:25:06 -0400534 : Integer.toHexString(System.identityHashCode(last));
535 android.util.Log.d(TAG, "updateSections: f=" + fs + " s=" + i);
536 android.util.Log.d(TAG, "updateSections: l=" + ls + " s=" + i);
537 }
538 }
539
Ned Burns9eb06332019-04-23 16:02:12 -0400540 @VisibleForTesting
Steve Elliott35956b42020-02-25 17:12:09 -0500541 ExpandableView getGentleHeaderView() {
Ned Burns9eb06332019-04-23 16:02:12 -0400542 return mGentleHeader;
543 }
544
Steve Elliott35956b42020-02-25 17:12:09 -0500545 @VisibleForTesting
546 ExpandableView getAlertingHeaderView() {
547 return mAlertingHeader;
548 }
549
550 @VisibleForTesting
551 ExpandableView getPeopleHeaderView() {
552 return mPeopleHubView;
553 }
554
Steve Elliott6de51a72020-03-05 11:00:47 -0500555 @VisibleForTesting
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400556 ExpandableView getMediaControlsView() {
557 return mMediaControlsView;
558 }
559
560 @VisibleForTesting
Steve Elliott6de51a72020-03-05 11:00:47 -0500561 void setPeopleHubVisible(boolean visible) {
562 mPeopleHubVisible = visible;
563 }
564
Ned Burns2c74c2a2019-06-13 19:06:47 -0400565 private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
566 @Override
567 public void onLocaleListChanged() {
Steve Elliott59e23eb2020-03-18 16:13:19 -0400568 reinflateViews(LayoutInflater.from(mParent.getContext()));
Ned Burns2c74c2a2019-06-13 19:06:47 -0400569 }
570 };
571
Ned Burns9eb06332019-04-23 16:02:12 -0400572 private void onGentleHeaderClick(View v) {
573 Intent intent = new Intent(Settings.ACTION_NOTIFICATION_SETTINGS);
574 mActivityStarter.startActivity(
575 intent,
576 true,
577 true,
578 Intent.FLAG_ACTIVITY_SINGLE_TOP);
579 }
Ned Burns61269442019-05-02 18:27:23 -0400580
581 private void onClearGentleNotifsClick(View v) {
582 if (mOnClearGentleNotifsClickListener != null) {
583 mOnClearGentleNotifsClickListener.onClick(v);
584 }
585 }
Evan Laird25f02752019-08-14 19:25:06 -0400586
Steve Elliott946b00c2019-10-28 11:20:24 -0400587 void hidePeopleRow() {
588 mPeopleHubVisible = false;
589 updateSectionBoundaries();
Steve Elliott58adc212019-10-15 11:07:54 -0400590 }
591
Steve Elliott577ef792020-04-02 13:49:36 -0400592 void setHeaderForegroundColor(@ColorInt int color) {
593 mPeopleHubView.setTextColor(color);
594 mGentleHeader.setForegroundColor(color);
595 mAlertingHeader.setForegroundColor(color);
596 }
597
Evan Laird25f02752019-08-14 19:25:06 -0400598 /**
599 * For now, declare the available notification buckets (sections) here so that other
600 * presentation code can decide what to do based on an entry's buckets
Evan Laird25f02752019-08-14 19:25:06 -0400601 */
602 @Retention(SOURCE)
603 @IntDef(prefix = { "BUCKET_" }, value = {
Steve Elliottb0940382020-02-20 14:24:02 -0500604 BUCKET_HEADS_UP,
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400605 BUCKET_MEDIA_CONTROLS,
Evan Laird25f02752019-08-14 19:25:06 -0400606 BUCKET_PEOPLE,
607 BUCKET_ALERTING,
608 BUCKET_SILENT
609 })
610 public @interface PriorityBucket {}
Steve Elliottb0940382020-02-20 14:24:02 -0500611 public static final int BUCKET_HEADS_UP = 0;
Robert Snoebergerb6464ea2020-03-20 11:56:22 -0400612 public static final int BUCKET_MEDIA_CONTROLS = 1;
613 public static final int BUCKET_PEOPLE = 2;
614 public static final int BUCKET_ALERTING = 3;
615 public static final int BUCKET_SILENT = 4;
Ned Burns9eb06332019-04-23 16:02:12 -0400616}