1 /*
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 
17 package com.android.systemui.statusbar.notification.collection;
18 
19 import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
20 import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
21 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
22 
23 import static java.util.Objects.requireNonNull;
24 
25 import com.android.systemui.statusbar.NotificationInteractionTracker;
26 
27 import java.util.Arrays;
28 import java.util.List;
29 
30 /**
31  * Utility class for dumping the results of a {@link ShadeListBuilder} to a debug string.
32  */
33 public class ListDumper {
34 
35     /**
36      * Creates a debug string for a list of grouped notifications that will be printed
37      * in the order given in a tiered/tree structure.
38      * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
39      *                             entry to be in its current state (ie: filter, lifeExtender)
40      */
dumpTree( List<ListEntry> entries, NotificationInteractionTracker interactionTracker, boolean includeRecordKeeping, String indent)41     public static String dumpTree(
42             List<ListEntry> entries,
43             NotificationInteractionTracker interactionTracker,
44             boolean includeRecordKeeping,
45             String indent) {
46         StringBuilder sb = new StringBuilder();
47         final String childEntryIndent = indent + INDENT;
48         for (int topEntryIndex = 0; topEntryIndex < entries.size(); topEntryIndex++) {
49             ListEntry entry = entries.get(topEntryIndex);
50             dumpEntry(entry,
51                     Integer.toString(topEntryIndex),
52                     indent,
53                     sb,
54                     true,
55                     includeRecordKeeping,
56                     interactionTracker.hasUserInteractedWith(logKey(entry)));
57             if (entry instanceof GroupEntry) {
58                 GroupEntry ge = (GroupEntry) entry;
59                 NotificationEntry summary = ge.getSummary();
60                 if (summary != null) {
61                     dumpEntry(summary,
62                             topEntryIndex + ":*",
63                             childEntryIndent,
64                             sb,
65                             true,
66                             includeRecordKeeping,
67                             interactionTracker.hasUserInteractedWith(logKey(summary)));
68                 }
69                 List<NotificationEntry> children = ge.getChildren();
70                 for (int childIndex = 0;  childIndex < children.size(); childIndex++) {
71                     NotificationEntry child = children.get(childIndex);
72                     dumpEntry(child,
73                             topEntryIndex + "." + childIndex,
74                             childEntryIndent,
75                             sb,
76                             true,
77                             includeRecordKeeping,
78                             interactionTracker.hasUserInteractedWith(logKey(child)));
79                 }
80             }
81         }
82         return sb.toString();
83     }
84 
85     /**
86      * Creates a debug string for a flat list of notifications
87      * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
88      *                             entry to be in its current state (ie: filter, lifeExtender)
89      */
dumpList( List<NotificationEntry> entries, boolean includeRecordKeeping, String indent)90     public static String dumpList(
91             List<NotificationEntry> entries,
92             boolean includeRecordKeeping,
93             String indent) {
94         StringBuilder sb = new StringBuilder();
95         for (int j = 0; j < entries.size(); j++) {
96             dumpEntry(
97                     entries.get(j),
98                     Integer.toString(j),
99                     indent,
100                     sb,
101                     false,
102                     includeRecordKeeping,
103                     false);
104         }
105         return sb.toString();
106     }
107 
dumpEntry( ListEntry entry, String index, String indent, StringBuilder sb, boolean includeParent, boolean includeRecordKeeping, boolean hasBeenInteractedWith )108     private static void dumpEntry(
109             ListEntry entry,
110             String index,
111             String indent,
112             StringBuilder sb,
113             boolean includeParent,
114             boolean includeRecordKeeping,
115             boolean hasBeenInteractedWith
116     ) {
117         sb.append(indent)
118                 .append("[").append(index).append("] ")
119                 .append(index.length() == 1 ? " " : "")
120                 .append(logKey(entry));
121 
122         if (includeParent) {
123             sb.append(" (parent=")
124                     .append(logKey(entry.getParent()))
125                     .append(")");
126 
127             NotificationEntry notifEntry = entry.getRepresentativeEntry();
128             if (notifEntry != null) {
129                 sb.append(" rank=")
130                         .append(notifEntry.getRanking().getRank());
131             }
132         }
133 
134         if (entry.getSection() != null) {
135             sb.append(" section=")
136                     .append(entry.getSection().getLabel());
137         }
138 
139         if (includeRecordKeeping) {
140             NotificationEntry notifEntry = requireNonNull(entry.getRepresentativeEntry());
141             StringBuilder rksb = new StringBuilder();
142 
143             if (!notifEntry.mLifetimeExtenders.isEmpty()) {
144                 String[] lifetimeExtenderNames = new String[notifEntry.mLifetimeExtenders.size()];
145                 for (int i = 0; i < lifetimeExtenderNames.length; i++) {
146                     lifetimeExtenderNames[i] = notifEntry.mLifetimeExtenders.get(i).getName();
147                 }
148                 rksb.append("lifetimeExtenders=")
149                         .append(Arrays.toString(lifetimeExtenderNames))
150                         .append(" ");
151             }
152 
153             if (!notifEntry.mDismissInterceptors.isEmpty()) {
154                 String[] interceptorsNames = new String[notifEntry.mDismissInterceptors.size()];
155                 for (int i = 0; i < interceptorsNames.length; i++) {
156                     interceptorsNames[i] = notifEntry.mDismissInterceptors.get(i).getName();
157                 }
158                 rksb.append("dismissInterceptors=")
159                         .append(Arrays.toString(interceptorsNames))
160                         .append(" ");
161             }
162 
163             if (notifEntry.getExcludingFilter() != null) {
164                 rksb.append("filter=")
165                         .append(notifEntry.getExcludingFilter().getName())
166                         .append(" ");
167             }
168 
169             if (notifEntry.getNotifPromoter() != null) {
170                 rksb.append("promoter=")
171                         .append(notifEntry.getNotifPromoter().getName())
172                         .append(" ");
173             }
174 
175             if (notifEntry.mCancellationReason != REASON_NOT_CANCELED) {
176                 rksb.append("cancellationReason=")
177                         .append(notifEntry.mCancellationReason)
178                         .append(" ");
179             }
180 
181             if (notifEntry.getDismissState() != NOT_DISMISSED) {
182                 rksb.append("dismissState=")
183                         .append(notifEntry.getDismissState())
184                         .append(" ");
185             }
186 
187             if (notifEntry.getAttachState().getSuppressedChanges().getParent() != null) {
188                 rksb.append("suppressedParent=")
189                         .append(logKey(notifEntry.getAttachState().getSuppressedChanges()
190                                 .getParent()))
191                         .append(" ");
192             }
193 
194             if (notifEntry.getAttachState().getSuppressedChanges().getSection() != null) {
195                 rksb.append("suppressedSection=")
196                         .append(notifEntry.getAttachState().getSuppressedChanges()
197                                 .getSection().getLabel())
198                         .append(" ");
199             }
200 
201             if (hasBeenInteractedWith) {
202                 rksb.append("interacted=yes ");
203             }
204 
205             String rkString = rksb.toString();
206             if (!rkString.isEmpty()) {
207                 sb.append("\n\t")
208                         .append(indent)
209                         .append(rkString);
210             }
211         }
212 
213         sb.append("\n");
214     }
215 
216     private static final String INDENT = "  ";
217 }
218