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