blob: 662cf514b977a43b7e6b9f9466dea839d24e1cfe [file] [log] [blame]
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09001/*
2 * Copyright (C) 2017 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;
18
19import android.content.Context;
20import android.content.res.Resources;
Lucas Dupin16013822018-05-17 18:00:16 -070021import android.os.Trace;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090022import android.util.Log;
23import android.view.View;
24import android.view.ViewGroup;
25
26import com.android.systemui.R;
Rohan Shah20790b82018-07-02 17:21:04 -070027import com.android.systemui.statusbar.notification.NotificationEntryManager;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090028import com.android.systemui.statusbar.notification.VisualStabilityManager;
Ned Burnsf81c4c42019-01-07 14:10:43 -050029import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Rohan Shah20790b82018-07-02 17:21:04 -070030import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
31import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090032import com.android.systemui.statusbar.phone.NotificationGroupManager;
Jason Monk297c04e2018-08-23 17:16:59 -040033import com.android.systemui.statusbar.phone.ShadeController;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090034
35import java.util.ArrayList;
36import java.util.HashMap;
37import java.util.List;
38import java.util.Stack;
39
Jason Monk27d01a622018-12-10 15:57:09 -050040import javax.inject.Inject;
41import javax.inject.Singleton;
42
Jason Monk09f4d372018-12-20 15:30:54 -050043import dagger.Lazy;
44
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090045/**
46 * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
47 * on their group structure. For example, if a notification becomes bundled with another,
48 * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
49 * tell NotificationListContainer which notifications to display, and inform it of changes to those
50 * notifications that might affect their display.
51 */
Jason Monk27d01a622018-12-10 15:57:09 -050052@Singleton
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090053public class NotificationViewHierarchyManager {
54 private static final String TAG = "NotificationViewHierarchyManager";
55
Evan Laird94492852018-10-25 13:43:01 -040056 //TODO: change this top <Entry, List<Entry>>?
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090057 private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
58 mTmpChildOrderMap = new HashMap<>();
Eliot Courtney6c313d32017-12-14 19:57:51 +090059
60 // Dependencies:
Jason Monk09f4d372018-12-20 15:30:54 -050061 protected final NotificationLockscreenUserManager mLockscreenUserManager;
62 protected final NotificationGroupManager mGroupManager;
63 protected final VisualStabilityManager mVisualStabilityManager;
Beverly8fdb5332019-02-04 14:29:49 -050064 private final StatusBarStateControllerImpl mStatusBarStateController;
Jason Monk09f4d372018-12-20 15:30:54 -050065 private final NotificationEntryManager mEntryManager;
Jason Monk297c04e2018-08-23 17:16:59 -040066
67 // Lazy
Jason Monk09f4d372018-12-20 15:30:54 -050068 private final Lazy<ShadeController> mShadeController;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090069
70 /**
71 * {@code true} if notifications not part of a group should by default be rendered in their
72 * expanded state. If {@code false}, then only the first notification will be expanded if
73 * possible.
74 */
75 private final boolean mAlwaysExpandNonGroupedNotification;
76
77 private NotificationPresenter mPresenter;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090078 private NotificationListContainer mListContainer;
79
Jason Monk27d01a622018-12-10 15:57:09 -050080 @Inject
Jason Monk09f4d372018-12-20 15:30:54 -050081 public NotificationViewHierarchyManager(Context context,
82 NotificationLockscreenUserManager notificationLockscreenUserManager,
83 NotificationGroupManager groupManager,
84 VisualStabilityManager visualStabilityManager,
Beverly8fdb5332019-02-04 14:29:49 -050085 StatusBarStateControllerImpl statusBarStateController,
Jason Monk09f4d372018-12-20 15:30:54 -050086 NotificationEntryManager notificationEntryManager,
Jason Monk09f4d372018-12-20 15:30:54 -050087 Lazy<ShadeController> shadeController) {
88 mLockscreenUserManager = notificationLockscreenUserManager;
89 mGroupManager = groupManager;
90 mVisualStabilityManager = visualStabilityManager;
91 mStatusBarStateController = statusBarStateController;
92 mEntryManager = notificationEntryManager;
Jason Monk09f4d372018-12-20 15:30:54 -050093 mShadeController = shadeController;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090094 Resources res = context.getResources();
95 mAlwaysExpandNonGroupedNotification =
96 res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
97 }
98
99 public void setUpWithPresenter(NotificationPresenter presenter,
Jason Monk297c04e2018-08-23 17:16:59 -0400100 NotificationListContainer listContainer) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900101 mPresenter = presenter;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900102 mListContainer = listContainer;
103 }
104
105 /**
106 * Updates the visual representation of the notifications.
107 */
Evan Laird94492852018-10-25 13:43:01 -0400108 //TODO: Rewrite this to focus on Entries, or some other data object instead of views
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900109 public void updateNotificationViews() {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500110 ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData()
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900111 .getActiveNotifications();
112 ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
113 final int N = activeNotifications.size();
114 for (int i = 0; i < N; i++) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500115 NotificationEntry ent = activeNotifications.get(i);
Evan Laird94492852018-10-25 13:43:01 -0400116 if (ent.isRowDismissed() || ent.isRowRemoved()) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900117 // we don't want to update removed notifications because they could
118 // temporarily become children if they were isolated before.
119 continue;
120 }
Mady Mellor5549dd22018-11-06 18:07:34 -0800121
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900122 int userId = ent.notification.getUserId();
123
124 // Display public version of the notification if we need to redact.
125 // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
126 // We can probably move some of this code there.
127 boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
128 mLockscreenUserManager.getCurrentUserId());
129 boolean userPublic = devicePublic
130 || mLockscreenUserManager.isLockscreenPublicMode(userId);
131 boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
132 boolean sensitive = userPublic && needsRedaction;
133 boolean deviceSensitive = devicePublic
134 && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
135 mLockscreenUserManager.getCurrentUserId());
Evan Laird94492852018-10-25 13:43:01 -0400136 ent.getRow().setSensitive(sensitive, deviceSensitive);
137 ent.getRow().setNeedsRedaction(needsRedaction);
138 if (mGroupManager.isChildInGroupWithSummary(ent.notification)) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500139 NotificationEntry summary = mGroupManager.getGroupSummary(ent.notification);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900140 List<ExpandableNotificationRow> orderedChildren =
Evan Laird94492852018-10-25 13:43:01 -0400141 mTmpChildOrderMap.get(summary.getRow());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900142 if (orderedChildren == null) {
143 orderedChildren = new ArrayList<>();
Evan Laird94492852018-10-25 13:43:01 -0400144 mTmpChildOrderMap.put(summary.getRow(), orderedChildren);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900145 }
Evan Laird94492852018-10-25 13:43:01 -0400146 orderedChildren.add(ent.getRow());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900147 } else {
Evan Laird94492852018-10-25 13:43:01 -0400148 toShow.add(ent.getRow());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900149 }
150
151 }
152
Rohan Shah524cf7b2018-03-15 14:40:02 -0700153 ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900154 for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
155 View child = mListContainer.getContainerChildAt(i);
156 if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700157 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
158
159 // Blocking helper is effectively a detached view. Don't bother removing it from the
160 // layout.
161 if (!row.isBlockingHelperShowing()) {
162 viewsToRemove.add((ExpandableNotificationRow) child);
163 }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900164 }
165 }
166
Rohan Shah524cf7b2018-03-15 14:40:02 -0700167 for (ExpandableNotificationRow viewToRemove : viewsToRemove) {
168 if (mGroupManager.isChildInGroupWithSummary(viewToRemove.getStatusBarNotification())) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900169 // we are only transferring this notification to its parent, don't generate an
170 // animation
171 mListContainer.setChildTransferInProgress(true);
172 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700173 if (viewToRemove.isSummaryWithChildren()) {
174 viewToRemove.removeAllChildren();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900175 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700176 mListContainer.removeContainerView(viewToRemove);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900177 mListContainer.setChildTransferInProgress(false);
178 }
179
180 removeNotificationChildren();
181
182 for (int i = 0; i < toShow.size(); i++) {
183 View v = toShow.get(i);
184 if (v.getParent() == null) {
185 mVisualStabilityManager.notifyViewAddition(v);
186 mListContainer.addContainerView(v);
187 }
188 }
189
190 addNotificationChildrenAndSort();
191
192 // So after all this work notifications still aren't sorted correctly.
193 // Let's do that now by advancing through toShow and mListContainer in
194 // lock-step, making sure mListContainer matches what we see in toShow.
195 int j = 0;
196 for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
197 View child = mListContainer.getContainerChildAt(i);
198 if (!(child instanceof ExpandableNotificationRow)) {
199 // We don't care about non-notification views.
200 continue;
201 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700202 if (((ExpandableNotificationRow) child).isBlockingHelperShowing()) {
203 // Don't count/reorder notifications that are showing the blocking helper!
204 continue;
205 }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900206
207 ExpandableNotificationRow targetChild = toShow.get(j);
208 if (child != targetChild) {
209 // Oops, wrong notification at this position. Put the right one
210 // here and advance both lists.
211 if (mVisualStabilityManager.canReorderNotification(targetChild)) {
212 mListContainer.changeViewPosition(targetChild, i);
213 } else {
214 mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager);
215 }
216 }
217 j++;
218
219 }
220
221 mVisualStabilityManager.onReorderingFinished();
222 // clear the map again for the next usage
223 mTmpChildOrderMap.clear();
224
225 updateRowStates();
226
227 mListContainer.onNotificationViewUpdateFinished();
228 }
229
230 private void addNotificationChildrenAndSort() {
231 // Let's now add all notification children which are missing
232 boolean orderChanged = false;
233 for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
234 View view = mListContainer.getContainerChildAt(i);
235 if (!(view instanceof ExpandableNotificationRow)) {
236 // We don't care about non-notification views.
237 continue;
238 }
239
240 ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
241 List<ExpandableNotificationRow> children = parent.getNotificationChildren();
242 List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
243
244 for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
245 childIndex++) {
246 ExpandableNotificationRow childView = orderedChildren.get(childIndex);
247 if (children == null || !children.contains(childView)) {
248 if (childView.getParent() != null) {
249 Log.wtf(TAG, "trying to add a notification child that already has " +
250 "a parent. class:" + childView.getParent().getClass() +
251 "\n child: " + childView);
252 // This shouldn't happen. We can recover by removing it though.
253 ((ViewGroup) childView.getParent()).removeView(childView);
254 }
255 mVisualStabilityManager.notifyViewAddition(childView);
256 parent.addChildNotification(childView, childIndex);
257 mListContainer.notifyGroupChildAdded(childView);
258 }
259 }
260
261 // Finally after removing and adding has been performed we can apply the order.
262 orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager,
263 mEntryManager);
264 }
265 if (orderChanged) {
266 mListContainer.generateChildOrderChangedEvent();
267 }
268 }
269
270 private void removeNotificationChildren() {
271 // First let's remove all children which don't belong in the parents
272 ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
273 for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
274 View view = mListContainer.getContainerChildAt(i);
275 if (!(view instanceof ExpandableNotificationRow)) {
276 // We don't care about non-notification views.
277 continue;
278 }
279
280 ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
281 List<ExpandableNotificationRow> children = parent.getNotificationChildren();
282 List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
283
284 if (children != null) {
285 toRemove.clear();
286 for (ExpandableNotificationRow childRow : children) {
287 if ((orderedChildren == null
288 || !orderedChildren.contains(childRow))
289 && !childRow.keepInParent()) {
290 toRemove.add(childRow);
291 }
292 }
293 for (ExpandableNotificationRow remove : toRemove) {
294 parent.removeChildNotification(remove);
295 if (mEntryManager.getNotificationData().get(
296 remove.getStatusBarNotification().getKey()) == null) {
297 // We only want to add an animation if the view is completely removed
298 // otherwise it's just a transfer
299 mListContainer.notifyGroupChildRemoved(remove,
300 parent.getChildrenContainer());
301 }
302 }
303 }
304 }
305 }
306
307 /**
308 * Updates expanded, dimmed and locked states of notification rows.
309 */
310 public void updateRowStates() {
Lucas Dupin16013822018-05-17 18:00:16 -0700311 Trace.beginSection("NotificationViewHierarchyManager#updateRowStates");
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900312 final int N = mListContainer.getContainerChildCount();
313
314 int visibleNotifications = 0;
Jason Monk297c04e2018-08-23 17:16:59 -0400315 boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900316 int maxNotifications = -1;
Jason Monk297c04e2018-08-23 17:16:59 -0400317 if (onKeyguard) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900318 maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
319 }
320 mListContainer.setMaxDisplayedNotifications(maxNotifications);
321 Stack<ExpandableNotificationRow> stack = new Stack<>();
322 for (int i = N - 1; i >= 0; i--) {
323 View child = mListContainer.getContainerChildAt(i);
324 if (!(child instanceof ExpandableNotificationRow)) {
325 continue;
326 }
327 stack.push((ExpandableNotificationRow) child);
328 }
329 while(!stack.isEmpty()) {
330 ExpandableNotificationRow row = stack.pop();
Ned Burnsf81c4c42019-01-07 14:10:43 -0500331 NotificationEntry entry = row.getEntry();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900332 boolean isChildNotification =
333 mGroupManager.isChildInGroupWithSummary(entry.notification);
334
Jason Monk297c04e2018-08-23 17:16:59 -0400335 row.setOnKeyguard(onKeyguard);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900336
Jason Monk297c04e2018-08-23 17:16:59 -0400337 if (!onKeyguard) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900338 // If mAlwaysExpandNonGroupedNotification is false, then only expand the
339 // very first notification and if it's not a child of grouped notifications.
340 row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
341 || (visibleNotifications == 0 && !isChildNotification
342 && !row.isLowPriority()));
343 }
344
Jason Monk09f4d372018-12-20 15:30:54 -0500345 entry.getRow().setOnAmbient(mShadeController.get().isDozing());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900346 int userId = entry.notification.getUserId();
347 boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
Evan Laird94492852018-10-25 13:43:01 -0400348 entry.notification) && !entry.isRowRemoved();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900349 boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
350 .notification);
Selim Cinek3bf2d202018-10-16 17:30:05 -0700351 if (!showOnKeyguard) {
352 // min priority notifications should show if their summary is showing
353 if (mGroupManager.isChildInGroupWithSummary(entry.notification)) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500354 NotificationEntry summary = mGroupManager.getLogicalGroupSummary(
Selim Cinek3bf2d202018-10-16 17:30:05 -0700355 entry.notification);
356 if (summary != null && mLockscreenUserManager.shouldShowOnKeyguard(
Evan Laird94492852018-10-25 13:43:01 -0400357 summary.notification)) {
Selim Cinek3bf2d202018-10-16 17:30:05 -0700358 showOnKeyguard = true;
359 }
360 }
361 }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900362 if (suppressedSummary
Pavel Grafov65152632018-03-26 15:14:45 +0100363 || mLockscreenUserManager.shouldHideNotifications(userId)
Jason Monk297c04e2018-08-23 17:16:59 -0400364 || (onKeyguard && !showOnKeyguard)) {
Evan Laird94492852018-10-25 13:43:01 -0400365 entry.getRow().setVisibility(View.GONE);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900366 } else {
Evan Laird94492852018-10-25 13:43:01 -0400367 boolean wasGone = entry.getRow().getVisibility() == View.GONE;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900368 if (wasGone) {
Evan Laird94492852018-10-25 13:43:01 -0400369 entry.getRow().setVisibility(View.VISIBLE);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900370 }
Evan Laird94492852018-10-25 13:43:01 -0400371 if (!isChildNotification && !entry.getRow().isRemoved()) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900372 if (wasGone) {
373 // notify the scroller of a child addition
Evan Laird94492852018-10-25 13:43:01 -0400374 mListContainer.generateAddAnimation(entry.getRow(),
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900375 !showOnKeyguard /* fromMoreCard */);
376 }
377 visibleNotifications++;
378 }
379 }
380 if (row.isSummaryWithChildren()) {
381 List<ExpandableNotificationRow> notificationChildren =
382 row.getNotificationChildren();
383 int size = notificationChildren.size();
384 for (int i = size - 1; i >= 0; i--) {
385 stack.push(notificationChildren.get(i));
386 }
387 }
Dan Sandler83b70a02018-01-24 23:20:18 -0500388
Julia Reynoldsfc640012018-02-21 12:25:27 -0500389 row.showAppOpsIcons(entry.mActiveAppOps);
Gus Prevas7306b902018-12-11 10:57:06 -0500390 row.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900391 }
392
Lucas Dupin16013822018-05-17 18:00:16 -0700393 Trace.beginSection("NotificationPresenter#onUpdateRowStates");
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900394 mPresenter.onUpdateRowStates();
Lucas Dupin16013822018-05-17 18:00:16 -0700395 Trace.endSection();
396 Trace.endSection();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900397 }
398}