Merge "Adds additional folder label states (UNLABELED & EMPTY_LABEL)" into ub-launcher3-rvc-dev
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index b84c7aa..ad21106 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -28,6 +28,8 @@
 
 import androidx.annotation.Keep;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.LinkedList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -37,6 +39,7 @@
  */
 public class DebugTestInformationHandler extends TestInformationHandler {
     private static LinkedList sLeaks;
+    private static Collection<String> sEvents;
 
     public DebugTestInformationHandler(Context context) {
         init(context);
@@ -134,6 +137,34 @@
                 return response;
             }
 
+            case TestProtocol.REQUEST_START_EVENT_LOGGING: {
+                sEvents = new ArrayList<>();
+                TestLogging.setEventConsumer(
+                        (sequence, event) -> {
+                            final Collection<String> events = sEvents;
+                            if (events != null) {
+                                synchronized (events) {
+                                    events.add(sequence + '/' + event);
+                                }
+                            }
+                        });
+                return response;
+            }
+
+            case TestProtocol.REQUEST_STOP_EVENT_LOGGING: {
+                TestLogging.setEventConsumer(null);
+                sEvents = null;
+                return response;
+            }
+
+            case TestProtocol.REQUEST_GET_TEST_EVENTS: {
+                synchronized (sEvents) {
+                    response.putStringArrayList(
+                            TestProtocol.TEST_INFO_RESPONSE_FIELD, new ArrayList<>(sEvents));
+                }
+                return response;
+            }
+
             default:
                 return super.call(method);
         }
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index b9953cb..b359f0f 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -348,8 +348,10 @@
     }
 
     public boolean isRecentsActivityRotationAllowed() {
+        // Activity rotation is allowed if the multi-simulated-rotation is not supported
+        // (fallback recents or tablets) or activity rotation is enabled by various settings.
         return ((mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
-                == MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
+                != MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
                 || (mFlags & (FLAG_HOME_ROTATION_ALLOWED_IN_PREFS
                         | FLAG_MULTIWINDOW_ROTATION_ALLOWED
                         | FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING)) != 0;
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index d522d81..51e0819 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -21,9 +21,17 @@
 
 import com.android.launcher3.Utilities;
 
+import java.util.function.BiConsumer;
+
 public final class TestLogging {
+    private static BiConsumer<String, String> sEventConsumer;
+
     private static void recordEventSlow(String sequence, String event) {
         Log.d(TestProtocol.TAPL_EVENTS_TAG, sequence + " / " + event);
+        final BiConsumer<String, String> eventConsumer = sEventConsumer;
+        if (eventConsumer != null) {
+            eventConsumer.accept(sequence, event);
+        }
     }
 
     public static void recordEvent(String sequence, String event) {
@@ -43,4 +51,8 @@
             recordEventSlow(sequence, message + ": " + event);
         }
     }
+
+    static void setEventConsumer(BiConsumer<String, String> consumer) {
+        sEventConsumer = consumer;
+    }
 }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 519b1b9..49da7b3 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -88,6 +88,9 @@
     public static final String REQUEST_NATIVE_LEAK = "native-leak";
     public static final String REQUEST_VIEW_LEAK = "view-leak";
     public static final String REQUEST_RECENT_TASKS_LIST = "recent-tasks-list";
+    public static final String REQUEST_START_EVENT_LOGGING = "start-event-logging";
+    public static final String REQUEST_GET_TEST_EVENTS = "get-test-events";
+    public static final String REQUEST_STOP_EVENT_LOGGING = "stop-event-logging";
 
     public static boolean sDebugTracing = false;
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 9f4d9ce..f634ce1 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -165,9 +165,7 @@
 
     private Consumer<ContainerType> mOnSettledStateAction;
 
-    private static LogEventChecker sEventChecker;
-    // True if there is an gesture in progress that needs event verification.
-    private static boolean sCheckingEvents;
+    private LogEventChecker mEventChecker;
 
     private boolean mCheckEventsForSuccessfulGestures = false;
     private Runnable mOnLauncherCrashed;
@@ -437,15 +435,16 @@
     }
 
     private String formatErrorWithEvents(String message, boolean checkEvents) {
-        if (sCheckingEvents) {
-            sCheckingEvents = false;
+        if (mEventChecker != null) {
+            final LogEventChecker eventChecker = mEventChecker;
+            mEventChecker = null;
             if (checkEvents) {
-                final String eventMismatch = sEventChecker.verify(0, false);
+                final String eventMismatch = eventChecker.verify(0, false);
                 if (eventMismatch != null) {
                     message = message + ", having produced " + eventMismatch;
                 }
             } else {
-                sEventChecker.finishNoWait();
+                eventChecker.finishNoWait();
             }
         }
 
@@ -1337,12 +1336,11 @@
     }
 
     public Closable eventsCheck() {
-        Assert.assertTrue("Nested event checking", !sCheckingEvents);
+        Assert.assertTrue("Nested event checking", mEventChecker == null);
         disableSensorRotation();
         final Integer initialPid = getPid();
-        if (sEventChecker == null) sEventChecker = new LogEventChecker();
-        sEventChecker.start();
-        sCheckingEvents = true;
+        final LogEventChecker eventChecker = new LogEventChecker(this);
+        if (eventChecker.start()) mEventChecker = eventChecker;
 
         return () -> {
             if (initialPid != null && initialPid.intValue() != getPid()) {
@@ -1353,10 +1351,10 @@
                                 formatErrorWithEvents("Launcher crashed", false)));
             }
 
-            if (sCheckingEvents) {
-                sCheckingEvents = false;
+            if (mEventChecker != null) {
+                mEventChecker = null;
                 if (mCheckEventsForSuccessfulGestures) {
-                    final String message = sEventChecker.verify(WAIT_TIME_MS, true);
+                    final String message = eventChecker.verify(WAIT_TIME_MS, true);
                     if (message != null) {
                         dumpDiagnostics();
                         checkForAnomaly();
@@ -1364,7 +1362,7 @@
                                 "http://go/tapl : successful gesture produced " + message));
                     }
                 } else {
-                    sEventChecker.finishNoWait();
+                    eventChecker.finishNoWait();
                 }
             }
         };
@@ -1375,7 +1373,11 @@
     }
 
     void expectEvent(String sequence, Pattern expected) {
-        if (sCheckingEvents) sEventChecker.expectPattern(sequence, expected);
+        if (mEventChecker != null) {
+            mEventChecker.expectPattern(sequence, expected);
+        } else {
+            Log.d(TAG, "Expecting: " + sequence + " / " + expected);
+        }
     }
 
     Rect getVisibleBounds(UiObject2 object) {
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index 79d20ac..4440b82 100644
--- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -15,169 +15,81 @@
  */
 package com.android.launcher3.tapl;
 
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-import android.util.Log;
+import android.os.SystemClock;
 
 import com.android.launcher3.testing.TestProtocol;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Semaphore;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
- * Utility class to read log on a background thread.
+ * Utility class to verify expected events.
  */
 public class LogEventChecker {
 
-    private static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
-            ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<sequence>[a-zA-Z]+) / (?<event>.*)");
-
-    private static final String START_PREFIX = "START_READER ";
-    private static final String FINISH_PREFIX = "FINISH_READER ";
-    private static final String SKIP_EVENTS_TAG = "b/153670015";
-
-    private volatile CountDownLatch mFinished;
+    private final LauncherInstrumentation mLauncher;
 
     // Map from an event sequence name to an ordered list of expected events in that sequence.
     private final ListMap<Pattern> mExpectedEvents = new ListMap<>();
 
-    private final ListMap<String> mEvents = new ListMap<>();
-    private final Semaphore mEventsCounter = new Semaphore(0);
-
-    private volatile String mStartCommand;
-    private volatile String mFinishCommand;
-
-    LogEventChecker() {
-        final Thread thread = new Thread(this::onRun, "log-reader-thread");
-        thread.setPriority(Thread.NORM_PRIORITY);
-        thread.start();
+    LogEventChecker(LauncherInstrumentation launcher) {
+        mLauncher = launcher;
     }
 
-    void start() {
-        if (mFinished != null) {
-            try {
-                mFinished.await();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            } finally {
-                mFinished = null;
-            }
-        }
-        mEvents.clear();
-        Log.d(SKIP_EVENTS_TAG, "Cleared events");
+    boolean start() {
         mExpectedEvents.clear();
-        mEventsCounter.drainPermits();
-        final String id = UUID.randomUUID().toString();
-        mStartCommand = START_PREFIX + id;
-        mFinishCommand = FINISH_PREFIX + id;
-        Log.d(SKIP_EVENTS_TAG, "Expected finish command: " + mFinishCommand);
-        Log.d(TestProtocol.TAPL_EVENTS_TAG, mStartCommand);
-    }
-
-    private void onRun() {
-        while (true) readEvents();
-    }
-
-    private void readEvents() {
-        try {
-            // Note that we use Runtime.exec to start the log reading process instead of running
-            // it via UIAutomation, so that we can directly access the "Process" object and
-            // ensure that the instrumentation is not stuck forever.
-            final String cmd = "logcat -s " + TestProtocol.TAPL_EVENTS_TAG;
-
-            final Process logcatProcess = Runtime.getRuntime().exec(cmd);
-            try (BufferedReader reader = new BufferedReader(new InputStreamReader(
-                    logcatProcess.getInputStream()))) {
-                while (true) {
-                    // Skip everything before the next start command.
-                    for (; ; ) {
-                        final String event = reader.readLine();
-                        if (event == null) {
-                            Log.d(SKIP_EVENTS_TAG, "Read a null line while waiting for start");
-                            return;
-                        }
-                        if (event.contains(mStartCommand)) {
-                            Log.d(SKIP_EVENTS_TAG, "Read start: " + event);
-                            break;
-                        }
-                    }
-
-                    // Store all actual events until the finish command.
-                    for (; ; ) {
-                        final String event = reader.readLine();
-                        if (event == null) {
-                            Log.d(SKIP_EVENTS_TAG, "Read a null line after waiting for start");
-                            mEventsCounter.drainPermits();
-                            mEvents.clear();
-                            return;
-                        }
-                        if (event.contains(mFinishCommand)) {
-                            mFinished.countDown();
-                            Log.d(SKIP_EVENTS_TAG, "Read finish: " + event);
-                            break;
-                        } else {
-                            final Matcher matcher = EVENT_LOG_ENTRY.matcher(event);
-                            if (matcher.find()) {
-                                mEvents.add(matcher.group("sequence"), matcher.group("event"));
-                                Log.d(SKIP_EVENTS_TAG, "Read event: " + event);
-                                mEventsCounter.release();
-                            } else {
-                                Log.d(SKIP_EVENTS_TAG, "Read something unexpected: " + event);
-                            }
-                        }
-                    }
-                }
-            } finally {
-                logcatProcess.destroyForcibly();
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
+        return mLauncher.getTestInfo(TestProtocol.REQUEST_START_EVENT_LOGGING) != null;
     }
 
     void expectPattern(String sequence, Pattern pattern) {
         mExpectedEvents.add(sequence, pattern);
     }
 
-    private void finishSync(long waitForExpectedCountMs) {
-        try {
-            // Wait until Launcher generates the expected number of events.
-            int expectedCount = mExpectedEvents.entrySet()
+    // Waits for the expected number of events and returns them.
+    private ListMap<String> finishSync(long waitForExpectedCountMs) {
+        final long startTime = SystemClock.uptimeMillis();
+        // Event strings with '/' separating the sequence and the event.
+        ArrayList<String> rawEvents;
+
+        while (true) {
+            rawEvents = mLauncher.getTestInfo(TestProtocol.REQUEST_GET_TEST_EVENTS)
+                    .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+            final int expectedCount = mExpectedEvents.entrySet()
                     .stream().mapToInt(e -> e.getValue().size()).sum();
-            mEventsCounter.tryAcquire(expectedCount, waitForExpectedCountMs, MILLISECONDS);
-            finishNoWait();
-            mFinished.await();
-            mFinished = null;
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
+            if (rawEvents.size() >= expectedCount
+                    || SystemClock.uptimeMillis() > startTime + waitForExpectedCountMs) {
+                break;
+            }
+            SystemClock.sleep(100);
         }
+
+        finishNoWait();
+
+        // Parse raw events into a map.
+        final ListMap<String> eventSequences = new ListMap<>();
+        for (String rawEvent : rawEvents) {
+            final String[] split = rawEvent.split("/");
+            eventSequences.add(split[0], split[1]);
+        }
+        return eventSequences;
     }
 
     void finishNoWait() {
-        mFinished = new CountDownLatch(1);
-        Log.d(TestProtocol.TAPL_EVENTS_TAG, mFinishCommand);
+        mLauncher.getTestInfo(TestProtocol.REQUEST_STOP_EVENT_LOGGING);
     }
 
     String verify(long waitForExpectedCountMs, boolean successfulGesture) {
-        finishSync(waitForExpectedCountMs);
+        final ListMap<String> actualEvents = finishSync(waitForExpectedCountMs);
 
         final StringBuilder sb = new StringBuilder();
         boolean hasMismatches = false;
         for (Map.Entry<String, List<Pattern>> expectedEvents : mExpectedEvents.entrySet()) {
             String sequence = expectedEvents.getKey();
 
-            List<String> actual = new ArrayList<>(mEvents.getNonNull(sequence));
-            Log.d(SKIP_EVENTS_TAG, "Verifying events");
+            List<String> actual = new ArrayList<>(actualEvents.getNonNull(sequence));
             final int mismatchPosition = getMismatchPosition(expectedEvents.getValue(), actual);
             hasMismatches = hasMismatches
                     || mismatchPosition != -1 && !ignoreMistatch(successfulGesture, sequence);
@@ -189,7 +101,7 @@
                     mismatchPosition);
         }
         // Check for unexpected event sequences in the actual data.
-        for (String actualNamedSequence : mEvents.keySet()) {
+        for (String actualNamedSequence : actualEvents.keySet()) {
             if (!mExpectedEvents.containsKey(actualNamedSequence)) {
                 hasMismatches = hasMismatches
                         || !ignoreMistatch(successfulGesture, actualNamedSequence);
@@ -197,7 +109,7 @@
                         sb,
                         actualNamedSequence,
                         new ArrayList<>(),
-                        mEvents.get(actualNamedSequence),
+                        actualEvents.get(actualNamedSequence),
                         0);
             }
         }