blob: 5b3082b04d5885b2d3b42101530c143c8be12581 [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
Eliot Courtney6c313d32017-12-14 19:57:51 +090026import com.android.systemui.Dependency;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090027import com.android.systemui.R;
Rohan Shah20790b82018-07-02 17:21:04 -070028import com.android.systemui.statusbar.notification.NotificationData;
29import com.android.systemui.statusbar.notification.NotificationEntryManager;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090030import com.android.systemui.statusbar.notification.VisualStabilityManager;
Rohan Shah20790b82018-07-02 17:21:04 -070031import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
32import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090033import com.android.systemui.statusbar.phone.NotificationGroupManager;
34
35import java.util.ArrayList;
36import java.util.HashMap;
37import java.util.List;
38import java.util.Stack;
39
40/**
41 * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
42 * on their group structure. For example, if a notification becomes bundled with another,
43 * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
44 * tell NotificationListContainer which notifications to display, and inform it of changes to those
45 * notifications that might affect their display.
46 */
47public class NotificationViewHierarchyManager {
48 private static final String TAG = "NotificationViewHierarchyManager";
49
50 private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
51 mTmpChildOrderMap = new HashMap<>();
Eliot Courtney6c313d32017-12-14 19:57:51 +090052
53 // Dependencies:
54 protected final NotificationLockscreenUserManager mLockscreenUserManager =
55 Dependency.get(NotificationLockscreenUserManager.class);
56 protected final NotificationGroupManager mGroupManager =
57 Dependency.get(NotificationGroupManager.class);
58 protected final VisualStabilityManager mVisualStabilityManager =
59 Dependency.get(VisualStabilityManager.class);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090060
61 /**
62 * {@code true} if notifications not part of a group should by default be rendered in their
63 * expanded state. If {@code false}, then only the first notification will be expanded if
64 * possible.
65 */
66 private final boolean mAlwaysExpandNonGroupedNotification;
67
68 private NotificationPresenter mPresenter;
69 private NotificationEntryManager mEntryManager;
70 private NotificationListContainer mListContainer;
71
Eliot Courtney6c313d32017-12-14 19:57:51 +090072 public NotificationViewHierarchyManager(Context context) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090073 Resources res = context.getResources();
74 mAlwaysExpandNonGroupedNotification =
75 res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
76 }
77
78 public void setUpWithPresenter(NotificationPresenter presenter,
79 NotificationEntryManager entryManager, NotificationListContainer listContainer) {
80 mPresenter = presenter;
81 mEntryManager = entryManager;
82 mListContainer = listContainer;
83 }
84
85 /**
86 * Updates the visual representation of the notifications.
87 */
88 public void updateNotificationViews() {
89 ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData()
90 .getActiveNotifications();
91 ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
92 final int N = activeNotifications.size();
93 for (int i = 0; i < N; i++) {
94 NotificationData.Entry ent = activeNotifications.get(i);
95 if (ent.row.isDismissed() || ent.row.isRemoved()) {
96 // we don't want to update removed notifications because they could
97 // temporarily become children if they were isolated before.
98 continue;
99 }
100 int userId = ent.notification.getUserId();
101
102 // Display public version of the notification if we need to redact.
103 // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
104 // We can probably move some of this code there.
105 boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(
106 mLockscreenUserManager.getCurrentUserId());
107 boolean userPublic = devicePublic
108 || mLockscreenUserManager.isLockscreenPublicMode(userId);
109 boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
110 boolean sensitive = userPublic && needsRedaction;
111 boolean deviceSensitive = devicePublic
112 && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
113 mLockscreenUserManager.getCurrentUserId());
114 ent.row.setSensitive(sensitive, deviceSensitive);
115 ent.row.setNeedsRedaction(needsRedaction);
116 if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) {
117 ExpandableNotificationRow summary = mGroupManager.getGroupSummary(
118 ent.row.getStatusBarNotification());
119 List<ExpandableNotificationRow> orderedChildren =
120 mTmpChildOrderMap.get(summary);
121 if (orderedChildren == null) {
122 orderedChildren = new ArrayList<>();
123 mTmpChildOrderMap.put(summary, orderedChildren);
124 }
125 orderedChildren.add(ent.row);
126 } else {
127 toShow.add(ent.row);
128 }
129
130 }
131
Rohan Shah524cf7b2018-03-15 14:40:02 -0700132 ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900133 for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
134 View child = mListContainer.getContainerChildAt(i);
135 if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700136 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
137
138 // Blocking helper is effectively a detached view. Don't bother removing it from the
139 // layout.
140 if (!row.isBlockingHelperShowing()) {
141 viewsToRemove.add((ExpandableNotificationRow) child);
142 }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900143 }
144 }
145
Rohan Shah524cf7b2018-03-15 14:40:02 -0700146 for (ExpandableNotificationRow viewToRemove : viewsToRemove) {
147 if (mGroupManager.isChildInGroupWithSummary(viewToRemove.getStatusBarNotification())) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900148 // we are only transferring this notification to its parent, don't generate an
149 // animation
150 mListContainer.setChildTransferInProgress(true);
151 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700152 if (viewToRemove.isSummaryWithChildren()) {
153 viewToRemove.removeAllChildren();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900154 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700155 mListContainer.removeContainerView(viewToRemove);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900156 mListContainer.setChildTransferInProgress(false);
157 }
158
159 removeNotificationChildren();
160
161 for (int i = 0; i < toShow.size(); i++) {
162 View v = toShow.get(i);
163 if (v.getParent() == null) {
164 mVisualStabilityManager.notifyViewAddition(v);
165 mListContainer.addContainerView(v);
166 }
167 }
168
169 addNotificationChildrenAndSort();
170
171 // So after all this work notifications still aren't sorted correctly.
172 // Let's do that now by advancing through toShow and mListContainer in
173 // lock-step, making sure mListContainer matches what we see in toShow.
174 int j = 0;
175 for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
176 View child = mListContainer.getContainerChildAt(i);
177 if (!(child instanceof ExpandableNotificationRow)) {
178 // We don't care about non-notification views.
179 continue;
180 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700181 if (((ExpandableNotificationRow) child).isBlockingHelperShowing()) {
182 // Don't count/reorder notifications that are showing the blocking helper!
183 continue;
184 }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900185
186 ExpandableNotificationRow targetChild = toShow.get(j);
187 if (child != targetChild) {
188 // Oops, wrong notification at this position. Put the right one
189 // here and advance both lists.
190 if (mVisualStabilityManager.canReorderNotification(targetChild)) {
191 mListContainer.changeViewPosition(targetChild, i);
192 } else {
193 mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager);
194 }
195 }
196 j++;
197
198 }
199
200 mVisualStabilityManager.onReorderingFinished();
201 // clear the map again for the next usage
202 mTmpChildOrderMap.clear();
203
204 updateRowStates();
205
206 mListContainer.onNotificationViewUpdateFinished();
207 }
208
209 private void addNotificationChildrenAndSort() {
210 // Let's now add all notification children which are missing
211 boolean orderChanged = false;
212 for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
213 View view = mListContainer.getContainerChildAt(i);
214 if (!(view instanceof ExpandableNotificationRow)) {
215 // We don't care about non-notification views.
216 continue;
217 }
218
219 ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
220 List<ExpandableNotificationRow> children = parent.getNotificationChildren();
221 List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
222
223 for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
224 childIndex++) {
225 ExpandableNotificationRow childView = orderedChildren.get(childIndex);
226 if (children == null || !children.contains(childView)) {
227 if (childView.getParent() != null) {
228 Log.wtf(TAG, "trying to add a notification child that already has " +
229 "a parent. class:" + childView.getParent().getClass() +
230 "\n child: " + childView);
231 // This shouldn't happen. We can recover by removing it though.
232 ((ViewGroup) childView.getParent()).removeView(childView);
233 }
234 mVisualStabilityManager.notifyViewAddition(childView);
235 parent.addChildNotification(childView, childIndex);
236 mListContainer.notifyGroupChildAdded(childView);
237 }
238 }
239
240 // Finally after removing and adding has been performed we can apply the order.
241 orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager,
242 mEntryManager);
243 }
244 if (orderChanged) {
245 mListContainer.generateChildOrderChangedEvent();
246 }
247 }
248
249 private void removeNotificationChildren() {
250 // First let's remove all children which don't belong in the parents
251 ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
252 for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
253 View view = mListContainer.getContainerChildAt(i);
254 if (!(view instanceof ExpandableNotificationRow)) {
255 // We don't care about non-notification views.
256 continue;
257 }
258
259 ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
260 List<ExpandableNotificationRow> children = parent.getNotificationChildren();
261 List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
262
263 if (children != null) {
264 toRemove.clear();
265 for (ExpandableNotificationRow childRow : children) {
266 if ((orderedChildren == null
267 || !orderedChildren.contains(childRow))
268 && !childRow.keepInParent()) {
269 toRemove.add(childRow);
270 }
271 }
272 for (ExpandableNotificationRow remove : toRemove) {
273 parent.removeChildNotification(remove);
274 if (mEntryManager.getNotificationData().get(
275 remove.getStatusBarNotification().getKey()) == null) {
276 // We only want to add an animation if the view is completely removed
277 // otherwise it's just a transfer
278 mListContainer.notifyGroupChildRemoved(remove,
279 parent.getChildrenContainer());
280 }
281 }
282 }
283 }
284 }
285
286 /**
287 * Updates expanded, dimmed and locked states of notification rows.
288 */
289 public void updateRowStates() {
Lucas Dupin16013822018-05-17 18:00:16 -0700290 Trace.beginSection("NotificationViewHierarchyManager#updateRowStates");
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900291 final int N = mListContainer.getContainerChildCount();
292
293 int visibleNotifications = 0;
294 boolean isLocked = mPresenter.isPresenterLocked();
295 int maxNotifications = -1;
296 if (isLocked) {
297 maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */);
298 }
299 mListContainer.setMaxDisplayedNotifications(maxNotifications);
300 Stack<ExpandableNotificationRow> stack = new Stack<>();
301 for (int i = N - 1; i >= 0; i--) {
302 View child = mListContainer.getContainerChildAt(i);
303 if (!(child instanceof ExpandableNotificationRow)) {
304 continue;
305 }
306 stack.push((ExpandableNotificationRow) child);
307 }
308 while(!stack.isEmpty()) {
309 ExpandableNotificationRow row = stack.pop();
310 NotificationData.Entry entry = row.getEntry();
311 boolean isChildNotification =
312 mGroupManager.isChildInGroupWithSummary(entry.notification);
313
314 row.setOnKeyguard(isLocked);
315
316 if (!isLocked) {
317 // If mAlwaysExpandNonGroupedNotification is false, then only expand the
318 // very first notification and if it's not a child of grouped notifications.
319 row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
320 || (visibleNotifications == 0 && !isChildNotification
321 && !row.isLowPriority()));
322 }
323
Kevina97ea052018-09-11 13:53:18 -0700324 entry.row.setOnAmbient(mPresenter.isDozing());
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900325 int userId = entry.notification.getUserId();
326 boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
327 entry.notification) && !entry.row.isRemoved();
328 boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry
329 .notification);
Selim Cinek3bf2d202018-10-16 17:30:05 -0700330 if (!showOnKeyguard) {
331 // min priority notifications should show if their summary is showing
332 if (mGroupManager.isChildInGroupWithSummary(entry.notification)) {
333 ExpandableNotificationRow summary = mGroupManager.getLogicalGroupSummary(
334 entry.notification);
335 if (summary != null && mLockscreenUserManager.shouldShowOnKeyguard(
336 summary.getStatusBarNotification())) {
337 showOnKeyguard = true;
338 }
339 }
340 }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900341 if (suppressedSummary
Pavel Grafov65152632018-03-26 15:14:45 +0100342 || mLockscreenUserManager.shouldHideNotifications(userId)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900343 || (isLocked && !showOnKeyguard)) {
344 entry.row.setVisibility(View.GONE);
345 } else {
346 boolean wasGone = entry.row.getVisibility() == View.GONE;
347 if (wasGone) {
348 entry.row.setVisibility(View.VISIBLE);
349 }
350 if (!isChildNotification && !entry.row.isRemoved()) {
351 if (wasGone) {
352 // notify the scroller of a child addition
353 mListContainer.generateAddAnimation(entry.row,
354 !showOnKeyguard /* fromMoreCard */);
355 }
356 visibleNotifications++;
357 }
358 }
359 if (row.isSummaryWithChildren()) {
360 List<ExpandableNotificationRow> notificationChildren =
361 row.getNotificationChildren();
362 int size = notificationChildren.size();
363 for (int i = size - 1; i >= 0; i--) {
364 stack.push(notificationChildren.get(i));
365 }
366 }
Dan Sandler83b70a02018-01-24 23:20:18 -0500367
Julia Reynoldsfc640012018-02-21 12:25:27 -0500368 row.showAppOpsIcons(entry.mActiveAppOps);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900369 }
370
Lucas Dupin16013822018-05-17 18:00:16 -0700371 Trace.beginSection("NotificationPresenter#onUpdateRowStates");
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900372 mPresenter.onUpdateRowStates();
Lucas Dupin16013822018-05-17 18:00:16 -0700373 Trace.endSection();
374 Trace.endSection();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900375 }
376}