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