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