Using a single array instead of a list of array for trcking events

In case events come out of order, the list is not properly initialized
which can lead to missing invalid states

Bug: 127282292
Change-Id: I3b4784b8e3eec890e80e62c2ea613e1ba84b6c95
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
index e02c696..109a4c5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
@@ -78,6 +78,11 @@
     }
 
     @Override
+    public int getType() {
+        return TYPE_ASSISTANT;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         // TODO add logging
         switch (ev.getActionMasked()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
index 047f34c..d919a3e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
@@ -31,6 +31,11 @@
     }
 
     @Override
+    public int getType() {
+        return TYPE_DEVICE_LOCKED;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         // For now, just start the home intent so user is prompted to unlock the device.
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
index 8dfb9ab..ad9fe78 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
@@ -23,7 +23,16 @@
 
 @TargetApi(Build.VERSION_CODES.O)
 public interface InputConsumer {
-    InputConsumer NO_OP = new InputConsumer() { };
+
+    int TYPE_NO_OP = 0;
+    int TYPE_OVERVIEW = 1;
+    int TYPE_OTHER_ACTIVITY = 2;
+    int TYPE_ASSISTANT = 3;
+    int TYPE_DEVICE_LOCKED = 4;
+
+    InputConsumer NO_OP = () -> TYPE_NO_OP;
+
+    int getType();
 
     default boolean isActive() {
         return false;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
index 67bfeaa..e3afb92 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
@@ -148,6 +148,11 @@
     }
 
     @Override
+    public int getType() {
+        return TYPE_OTHER_ACTIVITY;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         if (mVelocityTracker == null) {
             return;
@@ -225,7 +230,7 @@
                             mTouchSlop) {
                         mPassedTouchSlop = true;
 
-                        TOUCH_INTERACTION_LOG.startQuickStep();
+                        TOUCH_INTERACTION_LOG.addLog("startQuickstep");
                         if (mIsDeferredDownTarget) {
                             // Deferred gesture, start the animation and gesture tracking once
                             // we pass the actual touch slop
@@ -288,7 +293,7 @@
     }
 
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
-        TOUCH_INTERACTION_LOG.startRecentsAnimation();
+        TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation");
 
         RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
         final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
index 28c4db4..cce5cb3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
@@ -60,6 +60,11 @@
     }
 
     @Override
+    public int getType() {
+        return TYPE_OVERVIEW;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         if (mInvalidated) {
             return;
@@ -120,7 +125,7 @@
             OverviewCallbacks.get(mActivity).closeAllWindows();
             ActivityManagerWrapper.getInstance()
                     .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-            TOUCH_INTERACTION_LOG.startQuickStep();
+            TOUCH_INTERACTION_LOG.addLog("startQuickstep");
         }
 
         mTrackingStarted = true;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionLog.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionLog.java
deleted file mode 100644
index 4b660d4..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionLog.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.view.MotionEvent;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.LinkedList;
-
-/**
- * Keeps track of debugging logs for a particular quickstep gesture.
- */
-public class TouchInteractionLog {
-
-    // The number of gestures to log
-    private static final int MAX_NUM_LOG_GESTURES = 5;
-
-    private final Calendar mCalendar = Calendar.getInstance();
-    private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MMM dd - kk:mm:ss:SSS");
-    private final LinkedList<ArrayList<String>> mGestureLogs = new LinkedList<>();
-
-    public void prepareForNewGesture() {
-        mGestureLogs.add(new ArrayList<>());
-        while (mGestureLogs.size() > MAX_NUM_LOG_GESTURES) {
-            mGestureLogs.pop();
-        }
-        getCurrentLog().add("[" + mDateFormat.format(mCalendar.getTime()) + "]");
-    }
-
-    public void setInputConsumer(InputConsumer consumer) {
-        getCurrentLog().add("tc=" + consumer.getClass().getSimpleName());
-    }
-
-    public void addMotionEvent(MotionEvent event) {
-        getCurrentLog().add("ev=" + event.getActionMasked());
-    }
-
-    public void startQuickStep() {
-        getCurrentLog().add("qstStart");
-    }
-
-    public void startRecentsAnimation() {
-        getCurrentLog().add("raStart");
-    }
-
-    public void startRecentsAnimationCallback(int numTargets) {
-        getCurrentLog().add("raStartCb=" + numTargets);
-    }
-
-    public void cancelRecentsAnimation() {
-        getCurrentLog().add("raCancel");
-    }
-
-    public void finishRecentsAnimation(boolean toHome) {
-        getCurrentLog().add("raFinish=" + toHome);
-    }
-
-    public void dump(PrintWriter pw) {
-        pw.println("TouchInteractionLog {");
-        for (ArrayList<String> gesture : mGestureLogs) {
-            pw.print("    ");
-            for (String log : gesture) {
-                pw.print(log + " ");
-            }
-            pw.println();
-        }
-        pw.println("}");
-    }
-
-    private ArrayList<String> getCurrentLog() {
-        return mGestureLogs.getLast();
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index d1d0e86..a3c3ff9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.EventLogArray;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.IOverviewProxy;
@@ -70,7 +71,8 @@
     public static final LooperExecutor BACKGROUND_EXECUTOR =
             new LooperExecutor(UiThreadHelper.getBackgroundLooper());
 
-    public static final TouchInteractionLog TOUCH_INTERACTION_LOG = new TouchInteractionLog();
+    public static final EventLogArray TOUCH_INTERACTION_LOG =
+            new EventLogArray("touch_interaction_log", 40);
 
     public static final int EDGE_NAV_BAR = 1 << 8;
 
@@ -281,15 +283,13 @@
             return;
         }
         MotionEvent event = (MotionEvent) ev;
+        TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
         if (event.getAction() == ACTION_DOWN) {
-            TOUCH_INTERACTION_LOG.prepareForNewGesture();
             boolean useSharedState = mConsumer.isActive();
             mConsumer.onConsumerAboutToBeSwitched();
             mConsumer = newConsumer(useSharedState, event);
-            TOUCH_INTERACTION_LOG.setInputConsumer(mConsumer);
+            TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
         }
-        TOUCH_INTERACTION_LOG.addMotionEvent(event);
-
         mConsumer.onMotionEvent(event);
     }
 
@@ -342,6 +342,6 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        TOUCH_INTERACTION_LOG.dump(pw);
+        TOUCH_INTERACTION_LOG.dump("", pw);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index fa827e7..7dc58a5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -671,7 +671,7 @@
         initTransitionEndpoints(dp);
 
         mRecentsAnimationWrapper.setController(targetSet);
-        TOUCH_INTERACTION_LOG.startRecentsAnimationCallback(targetSet.apps.length);
+        TOUCH_INTERACTION_LOG.addLog("startRecentsAnimationCallback", targetSet.apps.length);
         setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
 
         mPassedOverviewThreshold = false;
@@ -682,7 +682,7 @@
         mRecentsAnimationWrapper.setController(null);
         mActivityInitListener.unregister();
         setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
-        TOUCH_INTERACTION_LOG.cancelRecentsAnimation();
+        TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation");
     }
 
     @UiThread
@@ -1025,7 +1025,7 @@
     @UiThread
     private void resumeLastTask() {
         mRecentsAnimationWrapper.finish(false /* toRecents */, null);
-        TOUCH_INTERACTION_LOG.finishRecentsAnimation(false);
+        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
     }
 
     @UiThread
@@ -1043,7 +1043,7 @@
                         mMainThreadHandler);
             });
         }
-        TOUCH_INTERACTION_LOG.finishRecentsAnimation(false);
+        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
         doLogGesture(NEW_TASK);
     }
 
@@ -1152,7 +1152,7 @@
                         () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
             }
         }
-        TOUCH_INTERACTION_LOG.finishRecentsAnimation(true);
+        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
     }
 
     private void finishCurrentTransitionToHome() {
@@ -1160,7 +1160,7 @@
             mRecentsAnimationWrapper.finish(true /* toRecents */,
                     () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
-        TOUCH_INTERACTION_LOG.finishRecentsAnimation(true);
+        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
         doLogGesture(HOME);
     }
 
diff --git a/src/com/android/launcher3/logging/EventLogArray.java b/src/com/android/launcher3/logging/EventLogArray.java
new file mode 100644
index 0000000..bfb3792
--- /dev/null
+++ b/src/com/android/launcher3/logging/EventLogArray.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.logging;
+
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * A utility class to record and log events. Events are stored in a fixed size array and old logs
+ * are purged as new events come.
+ */
+public class EventLogArray {
+
+    private static final int TYPE_ONE_OFF = 0;
+    private static final int TYPE_FLOAT = 1;
+    private static final int TYPE_INTEGER = 2;
+    private static final int TYPE_BOOL_TRUE = 3;
+    private static final int TYPE_BOOL_FALSE = 4;
+
+    private final String name;
+    private final EventEntry[] logs;
+    private int nextIndex;
+
+    public EventLogArray(String name, int size) {
+        this.name = name;
+        logs = new EventEntry[size];
+        nextIndex = 0;
+    }
+
+    public void addLog(String event) {
+        addLog(TYPE_ONE_OFF, event, 0);
+    }
+
+    public void addLog(String event, int extras) {
+        addLog(TYPE_INTEGER, event, extras);
+    }
+
+    public void addLog(String event, float extras) {
+        addLog(TYPE_FLOAT, event, extras);
+    }
+
+    public void addLog(String event, boolean extras) {
+        addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0);
+    }
+
+    private void addLog(int type, String event, float extras) {
+        // Merge the logs if its a duplicate
+        int last = (nextIndex + logs.length - 1) % logs.length;
+        int secondLast = (nextIndex + logs.length - 2) % logs.length;
+        if (isEntrySame(logs[last], type, event) && isEntrySame(logs[secondLast], type, event)) {
+            logs[last].update(type, event, extras);
+            logs[secondLast].duplicateCount++;
+            return;
+        }
+
+        if (logs[nextIndex] == null) {
+            logs[nextIndex] = new EventEntry();
+        }
+        logs[nextIndex].update(type, event, extras);
+        nextIndex = (nextIndex + 1) % logs.length;
+    }
+
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + name + " event history:");
+        SimpleDateFormat sdf = new SimpleDateFormat("  HH:mm:ss.SSSZ  ", Locale.US);
+        Date date = new Date();
+
+        for (int i = 0; i < logs.length; i++) {
+            EventEntry log = logs[(nextIndex + logs.length - i - 1) % logs.length];
+            if (log == null) {
+                continue;
+            }
+            date.setTime(log.time);
+
+            StringBuilder msg = new StringBuilder(prefix).append(sdf.format(date))
+                    .append(log.event);
+            switch (log.type) {
+                case TYPE_BOOL_FALSE:
+                    msg.append(": false");
+                    break;
+                case TYPE_BOOL_TRUE:
+                    msg.append(": true");
+                    break;
+                case TYPE_FLOAT:
+                    msg.append(": ").append(log.extras);
+                    break;
+                case TYPE_INTEGER:
+                    msg.append(": ").append((int) log.extras);
+                    break;
+                default: // fall out
+            }
+            if (log.duplicateCount > 0) {
+                msg.append(" & ").append(log.duplicateCount).append(" similar events");
+            }
+            writer.println(msg);
+        }
+    }
+
+    private boolean isEntrySame(EventEntry entry, int type, String event) {
+        return entry != null && entry.type == type && entry.event.equals(event);
+    }
+
+    /** A single event entry. */
+    private static class EventEntry {
+
+        private int type;
+        private String event;
+        private float extras;
+        private long time;
+        private int duplicateCount;
+
+        public void update(int type, String event, float extras) {
+            this.type = type;
+            this.event = event;
+            this.extras = extras;
+            time = System.currentTimeMillis();
+            duplicateCount = 0;
+        }
+    }
+}