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