• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.Arrays;
25 import java.util.Collections;
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 
74     /**
75      * Adds a log to be printed at log-dump-time and track the associated event for error detection.
76      *
77      * @param gestureEvent GestureEvent representing the event being logged.
78      */
addLog( @onNull String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent)79     public void addLog(
80             @NonNull String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
81         addLog(new CompoundString(event), gestureEvent);
82     }
83 
addLog(@onNull CompoundString compoundString)84     public void addLog(@NonNull CompoundString compoundString) {
85         addLog(compoundString, null);
86     }
87 
addLog( @onNull CompoundString compoundString, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent)88     public void addLog(
89             @NonNull CompoundString compoundString,
90             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
91         EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length];
92         if (lastEventLog == null || mCurrentLogId != lastEventLog.logId) {
93             EventLog eventLog = new EventLog(mCurrentLogId, mIsFullyGesturalNavMode);
94             EventEntry eventEntry = new EventEntry();
95 
96             eventEntry.update(compoundString, gestureEvent);
97             eventLog.eventEntries.add(eventEntry);
98             logs[nextIndex] = eventLog;
99             nextIndex = (nextIndex + 1) % logs.length;
100             return;
101         }
102 
103         // Update the last EventLog
104         List<EventEntry> lastEventEntries = lastEventLog.eventEntries;
105         EventEntry lastEntry = !lastEventEntries.isEmpty()
106                 ? lastEventEntries.get(lastEventEntries.size() - 1) : null;
107 
108         // Update the last EventEntry if it's a duplicate
109         if (isEntrySame(lastEntry, compoundString, gestureEvent)) {
110             lastEntry.duplicateCount++;
111             return;
112         }
113         EventEntry eventEntry = new EventEntry();
114 
115         eventEntry.update(compoundString, gestureEvent);
116         lastEventEntries.add(eventEntry);
117     }
118 
dump(String prefix, PrintWriter writer)119     public void dump(String prefix, PrintWriter writer) {
120         writer.println(prefix + "ActiveGestureErrorDetector:");
121         for (int i = 0; i < logs.length; i++) {
122             EventLog eventLog = logs[(nextIndex + i) % logs.length];
123             if (eventLog == null) {
124                 continue;
125             }
126             ActiveGestureErrorDetector.analyseAndDump(prefix + '\t', writer, eventLog);
127         }
128 
129         writer.println(prefix + "ActiveGestureLog history:");
130         SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSZ  ", Locale.US);
131         Date date = new Date();
132         for (int i = 0; i < logs.length; i++) {
133             EventLog eventLog = logs[(nextIndex + i) % logs.length];
134             if (eventLog == null) {
135                 continue;
136             }
137 
138             writer.println(prefix + "\tLogs for logId: " + eventLog.logId);
139             for (EventEntry eventEntry : eventLog.eventEntries) {
140                 if (eventEntry.mCompoundString.mIsNoOp) {
141                     continue;
142                 }
143                 date.setTime(eventEntry.time);
144 
145                 StringBuilder msg = new StringBuilder(prefix + "\t\t")
146                         .append(sdf.format(date))
147                         .append(eventEntry.mCompoundString);
148                 if (eventEntry.duplicateCount > 0) {
149                     msg.append(" & ").append(eventEntry.duplicateCount).append(" similar events");
150                 }
151                 writer.println(msg);
152             }
153         }
154     }
155 
156     /**
157      * Increments and returns the current log ID. This should be used every time a new log trace
158      * is started.
159      */
incrementLogId()160     public int incrementLogId() {
161         return mCurrentLogId++;
162     }
163 
setIsFullyGesturalNavMode(boolean isFullyGesturalNavMode)164     public void setIsFullyGesturalNavMode(boolean isFullyGesturalNavMode) {
165         mIsFullyGesturalNavMode = isFullyGesturalNavMode;
166     }
167 
168     /** Returns the current log ID. This should be used when a log trace is being reused. */
getLogId()169     public int getLogId() {
170         return mCurrentLogId;
171     }
172 
isEntrySame( EventEntry entry, CompoundString compoundString, ActiveGestureErrorDetector.GestureEvent gestureEvent)173     private boolean isEntrySame(
174             EventEntry entry,
175             CompoundString compoundString,
176             ActiveGestureErrorDetector.GestureEvent gestureEvent) {
177         return entry != null
178                 && entry.mCompoundString.equals(compoundString)
179                 && entry.gestureEvent == gestureEvent;
180     }
181 
182     /** A single event entry. */
183     protected static class EventEntry {
184 
185         @NonNull private CompoundString mCompoundString;
186         private ActiveGestureErrorDetector.GestureEvent gestureEvent;
187         private long time;
188         private int duplicateCount;
189 
EventEntry()190         private EventEntry() {}
191 
192         @Nullable
getGestureEvent()193         protected ActiveGestureErrorDetector.GestureEvent getGestureEvent() {
194             return gestureEvent;
195         }
196 
getDuplicateCount()197         public int getDuplicateCount() {
198             return duplicateCount;
199         }
200 
update( @onNull CompoundString compoundString, ActiveGestureErrorDetector.GestureEvent gestureEvent)201         private void update(
202                 @NonNull CompoundString compoundString,
203                 ActiveGestureErrorDetector.GestureEvent gestureEvent) {
204             this.mCompoundString = compoundString;
205             this.gestureEvent = gestureEvent;
206             time = System.currentTimeMillis();
207             duplicateCount = 0;
208         }
209 
getTime()210         public long getTime() {
211             return time;
212         }
213     }
214 
215     /** An entire log of entries associated with a single log ID */
216     protected static class EventLog {
217 
218         protected final List<EventEntry> eventEntries =
219                 Collections.synchronizedList(new ArrayList<>());
220         protected final int logId;
221         protected final boolean mIsFullyGesturalNavMode;
222 
EventLog(int logId, boolean isFullyGesturalNavMode)223         private EventLog(int logId, boolean isFullyGesturalNavMode) {
224             this.logId = logId;
225             mIsFullyGesturalNavMode = isFullyGesturalNavMode;
226         }
227     }
228 
229     /** A buildable string stored as an array for memory efficiency. */
230     public static class CompoundString {
231 
232         public static final CompoundString NO_OP = new CompoundString(true);
233 
234         private final List<String> mSubstrings;
235         private final List<Object> mArgs;
236 
237         private final boolean mIsNoOp;
238 
newEmptyString()239         public static CompoundString newEmptyString() {
240             return new CompoundString(false);
241         }
242 
CompoundString(boolean isNoOp)243         private CompoundString(boolean isNoOp) {
244             mIsNoOp = isNoOp;
245             mSubstrings = mIsNoOp ? null : new ArrayList<>();
246             mArgs = mIsNoOp ? null : new ArrayList<>();
247         }
248 
CompoundString(String substring, Object... args)249         public CompoundString(String substring, Object... args) {
250             this(substring == null);
251 
252             append(substring, args);
253         }
254 
append(CompoundString substring)255         public CompoundString append(CompoundString substring) {
256             if (mIsNoOp || substring.mIsNoOp) {
257                 return this;
258             }
259             mSubstrings.addAll(substring.mSubstrings);
260             mArgs.addAll(substring.mArgs);
261 
262             return this;
263         }
264 
append(String substring, Object... args)265         public CompoundString append(String substring, Object... args) {
266             if (mIsNoOp) {
267                 return this;
268             }
269             mSubstrings.add(substring);
270             mArgs.addAll(Arrays.stream(args).toList());
271 
272             return this;
273         }
274 
275         @Override
toString()276         public String toString() {
277             if (mIsNoOp) return null;
278             StringBuilder sb = new StringBuilder();
279             for (String substring : mSubstrings) {
280                 sb.append(substring);
281             }
282             return String.format(sb.toString(), mArgs.toArray());
283         }
284 
285         @Override
hashCode()286         public int hashCode() {
287             return Objects.hash(mIsNoOp, mSubstrings, mArgs);
288         }
289 
290         @Override
equals(Object obj)291         public boolean equals(Object obj) {
292             if (!(obj instanceof CompoundString other)) {
293                 return false;
294             }
295             return (mIsNoOp == other.mIsNoOp)
296                     && Objects.equals(mSubstrings, other.mSubstrings)
297                     && Objects.equals(mArgs, other.mArgs);
298         }
299     }
300 }
301