• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.quickstep.util;
17 
18 import androidx.annotation.NonNull;
19 import androidx.annotation.Nullable;
20 
21 import java.io.PrintWriter;
22 import java.text.SimpleDateFormat;
23 import java.util.ArrayList;
24 import java.util.Date;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Objects;
28 
29 /**
30  * A log to keep track of the active gesture.
31  */
32 public class ActiveGestureLog {
33 
34     private static final int MAX_GESTURES_TRACKED = 10;
35 
36     public static final ActiveGestureLog INSTANCE = new ActiveGestureLog();
37 
38     /**
39      * NOTE: This value should be kept same as
40      * ActivityTaskManagerService#INTENT_EXTRA_LOG_TRACE_ID in platform
41      */
42     public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID";
43 
44     private static final int TYPE_ONE_OFF = 0;
45     private static final int TYPE_FLOAT = 1;
46     private static final int TYPE_INTEGER = 2;
47     private static final int TYPE_BOOL_TRUE = 3;
48     private static final int TYPE_BOOL_FALSE = 4;
49     private static final int TYPE_INPUT_CONSUMER = 5;
50     private static final int TYPE_GESTURE_EVENT = 6;
51 
52     private final EventLog[] logs;
53     private int nextIndex;
54     private int mCurrentLogId = 100;
55 
ActiveGestureLog()56     private ActiveGestureLog() {
57         this.logs = new EventLog[MAX_GESTURES_TRACKED];
58         this.nextIndex = 0;
59     }
60 
61     /**
62      * Track the given event for error detection.
63      *
64      * @param gestureEvent GestureEvent representing an event during the current gesture's
65      *                   execution.
66      */
trackEvent(@ullable ActiveGestureErrorDetector.GestureEvent gestureEvent)67     public void trackEvent(@Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
68         addLog(TYPE_GESTURE_EVENT, "", 0, CompoundString.NO_OP, gestureEvent);
69     }
70 
addLog(String event)71     public void addLog(String event) {
72         addLog(event, null);
73     }
74 
addLog(String event, int extras)75     public void addLog(String event, int extras) {
76         addLog(event, extras, null);
77     }
78 
addLog(String event, boolean extras)79     public void addLog(String event, boolean extras) {
80         addLog(event, extras, null);
81     }
82 
addLog(CompoundString compoundString)83     public void addLog(CompoundString compoundString) {
84         addLog(TYPE_INPUT_CONSUMER, "", 0, compoundString, null);
85     }
86 
87     /**
88      * Adds a log and track the associated event for error detection.
89      *
90      * @param gestureEvent GestureEvent representing the event being logged.
91      */
addLog( String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent)92     public void addLog(
93             String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
94         addLog(TYPE_ONE_OFF, event, 0, CompoundString.NO_OP, gestureEvent);
95     }
96 
addLog( String event, int extras, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent)97     public void addLog(
98             String event,
99             int extras,
100             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
101         addLog(TYPE_INTEGER, event, extras, CompoundString.NO_OP, gestureEvent);
102     }
103 
addLog( String event, boolean extras, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent)104     public void addLog(
105             String event,
106             boolean extras,
107             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
108         addLog(
109                 extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE,
110                 event,
111                 0,
112                 CompoundString.NO_OP,
113                 gestureEvent);
114     }
115 
addLog( int type, String event, float extras, CompoundString compoundString, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent)116     private void addLog(
117             int type,
118             String event,
119             float extras,
120             CompoundString compoundString,
121             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
122         EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length];
123         if (lastEventLog == null || mCurrentLogId != lastEventLog.logId) {
124             EventLog eventLog = new EventLog(mCurrentLogId);
125             EventEntry eventEntry = new EventEntry();
126 
127             eventEntry.update(type, event, extras, compoundString, gestureEvent);
128             eventLog.eventEntries.add(eventEntry);
129             logs[nextIndex] = eventLog;
130             nextIndex = (nextIndex + 1) % logs.length;
131             return;
132         }
133 
134         // Update the last EventLog
135         List<EventEntry> lastEventEntries = lastEventLog.eventEntries;
136         EventEntry lastEntry = lastEventEntries.size() > 0
137                 ? lastEventEntries.get(lastEventEntries.size() - 1) : null;
138 
139         // Update the last EventEntry if it's a duplicate
140         if (isEntrySame(lastEntry, type, event, extras, compoundString, gestureEvent)) {
141             lastEntry.duplicateCount++;
142             return;
143         }
144         EventEntry eventEntry = new EventEntry();
145 
146         eventEntry.update(type, event, extras, compoundString, gestureEvent);
147         lastEventEntries.add(eventEntry);
148     }
149 
dump(String prefix, PrintWriter writer)150     public void dump(String prefix, PrintWriter writer) {
151         writer.println(prefix + "ActiveGestureErrorDetector:");
152         for (int i = 0; i < logs.length; i++) {
153             EventLog eventLog = logs[(nextIndex + i) % logs.length];
154             if (eventLog == null) {
155                 continue;
156             }
157             ActiveGestureErrorDetector.analyseAndDump(prefix + '\t', writer, eventLog);
158         }
159 
160         writer.println(prefix + "ActiveGestureLog history:");
161         SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSZ  ", Locale.US);
162         Date date = new Date();
163         for (int i = 0; i < logs.length; i++) {
164             EventLog eventLog = logs[(nextIndex + i) % logs.length];
165             if (eventLog == null) {
166                 continue;
167             }
168 
169             writer.println(prefix + "\tLogs for logId: " + eventLog.logId);
170             for (EventEntry eventEntry : eventLog.eventEntries) {
171                 date.setTime(eventEntry.time);
172 
173                 StringBuilder msg = new StringBuilder(prefix + "\t\t").append(sdf.format(date))
174                         .append(eventEntry.event);
175                 switch (eventEntry.type) {
176                     case TYPE_BOOL_FALSE:
177                         msg.append(": false");
178                         break;
179                     case TYPE_BOOL_TRUE:
180                         msg.append(": true");
181                         break;
182                     case TYPE_FLOAT:
183                         msg.append(": ").append(eventEntry.extras);
184                         break;
185                     case TYPE_INTEGER:
186                         msg.append(": ").append((int) eventEntry.extras);
187                         break;
188                     case TYPE_INPUT_CONSUMER:
189                         msg.append(eventEntry.mCompoundString);
190                         break;
191                     case TYPE_GESTURE_EVENT:
192                         continue;
193                     default: // fall out
194                 }
195                 if (eventEntry.duplicateCount > 0) {
196                     msg.append(" & ").append(eventEntry.duplicateCount).append(" similar events");
197                 }
198                 writer.println(msg);
199             }
200         }
201     }
202 
203     /**
204      * Increments and returns the current log ID. This should be used every time a new log trace
205      * is started.
206      */
incrementLogId()207     public int incrementLogId() {
208         return mCurrentLogId++;
209     }
210 
211     /** Returns the current log ID. This should be used when a log trace is being reused. */
getLogId()212     public int getLogId() {
213         return mCurrentLogId;
214     }
215 
isEntrySame( EventEntry entry, int type, String event, float extras, CompoundString compoundString, ActiveGestureErrorDetector.GestureEvent gestureEvent)216     private boolean isEntrySame(
217             EventEntry entry,
218             int type,
219             String event,
220             float extras,
221             CompoundString compoundString,
222             ActiveGestureErrorDetector.GestureEvent gestureEvent) {
223         return entry != null
224                 && entry.type == type
225                 && entry.event.equals(event)
226                 && Float.compare(entry.extras, extras) == 0
227                 && entry.mCompoundString.equals(compoundString)
228                 && entry.gestureEvent == gestureEvent;
229     }
230 
231     /** A single event entry. */
232     protected static class EventEntry {
233 
234         private int type;
235         private String event;
236         private float extras;
237         @NonNull private CompoundString mCompoundString;
238         private ActiveGestureErrorDetector.GestureEvent gestureEvent;
239         private long time;
240         private int duplicateCount;
241 
EventEntry()242         private EventEntry() {}
243 
244         @Nullable
getGestureEvent()245         protected ActiveGestureErrorDetector.GestureEvent getGestureEvent() {
246             return gestureEvent;
247         }
248 
update( int type, String event, float extras, @NonNull CompoundString compoundString, ActiveGestureErrorDetector.GestureEvent gestureEvent)249         private void update(
250                 int type,
251                 String event,
252                 float extras,
253                 @NonNull CompoundString compoundString,
254                 ActiveGestureErrorDetector.GestureEvent gestureEvent) {
255             this.type = type;
256             this.event = event;
257             this.extras = extras;
258             this.mCompoundString = compoundString;
259             this.gestureEvent = gestureEvent;
260             time = System.currentTimeMillis();
261             duplicateCount = 0;
262         }
263     }
264 
265     /** An entire log of entries associated with a single log ID */
266     protected static class EventLog {
267 
268         protected final List<EventEntry> eventEntries = new ArrayList<>();
269         protected final int logId;
270 
EventLog(int logId)271         private EventLog(int logId) {
272             this.logId = logId;
273         }
274     }
275 
276     /** A buildable string stored as an array for memory efficiency. */
277     public static class CompoundString {
278 
279         public static final CompoundString NO_OP = new CompoundString();
280 
281         private final List<String> mSubstrings;
282 
283         private final boolean mIsNoOp;
284 
CompoundString()285         private CompoundString() {
286             this(null);
287         }
288 
CompoundString(String substring)289         public CompoundString(String substring) {
290             mIsNoOp = substring == null;
291             if (mIsNoOp) {
292                 mSubstrings = null;
293                 return;
294             }
295             mSubstrings = new ArrayList<>();
296             mSubstrings.add(substring);
297         }
298 
append(CompoundString substring)299         public CompoundString append(CompoundString substring) {
300             if (mIsNoOp) {
301                 return this;
302             }
303             mSubstrings.addAll(substring.mSubstrings);
304 
305             return this;
306         }
307 
append(String substring)308         public CompoundString append(String substring) {
309             if (mIsNoOp) {
310                 return this;
311             }
312             mSubstrings.add(substring);
313 
314             return this;
315         }
316 
317         @Override
toString()318         public String toString() {
319             if (mIsNoOp) {
320                 return "ERROR: cannot use No-Op compound string";
321             }
322             StringBuilder sb = new StringBuilder();
323             for (String substring : mSubstrings) {
324                 sb.append(substring);
325             }
326 
327             return sb.toString();
328         }
329 
330         @Override
hashCode()331         public int hashCode() {
332             return Objects.hash(mIsNoOp, mSubstrings);
333         }
334 
335         @Override
equals(Object obj)336         public boolean equals(Object obj) {
337             if (!(obj instanceof CompoundString)) {
338                 return false;
339             }
340             CompoundString other = (CompoundString) obj;
341             return (mIsNoOp == other.mIsNoOp) && Objects.equals(mSubstrings, other.mSubstrings);
342         }
343     }
344 }
345