Merge "Fix flaky: testKeyguardShowHideOverSplitScreen and testLaunchActivityWithFlagClearTop" into qt-dev am: f6f63054af
am: ac06a267dc

Change-Id: If2737b785e628c99124824cce495cfcfd1dab5f1
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
index c3e9c11..3a45a3c 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
@@ -105,18 +105,9 @@
         String propDisablePreloading = device.getProperty(PROPERTY_DISABLE_OPENGL_PRELOADING);
         String propGfxDriver = device.getProperty(PROPERTY_GFX_DRIVER);
 
-        // Make sure ANGLE exists on the device
-        if((angleSupported == null) || (angleSupported.equals("false"))) {
-            return false;
-        }
-
-        // This logic is attempting to mimic ZygoteInit.java::ZygoteInit#preloadOpenGL()
-        if (((propDisablePreloading != null) && propDisablePreloading.equals("false")) &&
-                ((propGfxDriver == null) || propGfxDriver.isEmpty())) {
-            return false;
-        }
-
-        return true;
+        return (angleSupported != null) && (angleSupported.equals("true")) &&
+                (((propDisablePreloading != null) && propDisablePreloading.equals("true")) ||
+                        ((propGfxDriver != null) && !propGfxDriver.isEmpty()));
     }
 
     static void startActivity(ITestDevice device, String action) throws Exception {
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
index 3b76b5d..5373a3b 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
@@ -92,6 +92,9 @@
     public void testEnableAngleForAll() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
+        installPackage(ANGLE_DRIVER_TEST_APP);
+        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.DEFAULT));
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_SEC_PKG,
@@ -99,9 +102,6 @@
 
         setGlobalSetting(getDevice(), SETTINGS_GLOBAL_ALL_USE_ANGLE, "1");
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
-
         waitThenRunDeviceTests(this, ANGLE_DRIVER_TEST_PKG,
                 ANGLE_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
                 ANGLE_DRIVER_TEST_ANGLE_METHOD);
@@ -117,11 +117,11 @@
     public void testUseDefaultDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
+        installPackage(ANGLE_DRIVER_TEST_APP);
+
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.DEFAULT));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-
         waitThenRunDeviceTests(this, ANGLE_DRIVER_TEST_PKG,
                 ANGLE_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
                 ANGLE_DRIVER_TEST_DEFAULT_METHOD);
@@ -134,11 +134,11 @@
     public void testUseAngleDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
+        installPackage(ANGLE_DRIVER_TEST_APP);
+
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-
         waitThenRunDeviceTests(this, ANGLE_DRIVER_TEST_PKG,
                 ANGLE_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
                 ANGLE_DRIVER_TEST_ANGLE_METHOD);
@@ -151,11 +151,11 @@
     public void testUseNativeDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
+        installPackage(ANGLE_DRIVER_TEST_APP);
+
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.NATIVE));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-
         waitThenRunDeviceTests(this, ANGLE_DRIVER_TEST_PKG,
                 ANGLE_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
                 ANGLE_DRIVER_TEST_NATIVE_METHOD);
@@ -168,13 +168,13 @@
     public void testSettingsLengthMismatch() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
+        installPackage(ANGLE_DRIVER_TEST_APP);
+        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," +
                         ANGLE_DRIVER_TEST_SEC_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
-
         waitThenRunDeviceTests(this, ANGLE_DRIVER_TEST_PKG,
                 ANGLE_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
                 ANGLE_DRIVER_TEST_DEFAULT_METHOD);
@@ -191,10 +191,10 @@
     public void testUseInvalidDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG, "timtim");
-
         installPackage(ANGLE_DRIVER_TEST_APP);
 
+        setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG, "timtim");
+
         waitThenRunDeviceTests(this, ANGLE_DRIVER_TEST_PKG,
                 ANGLE_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
                 ANGLE_DRIVER_TEST_DEFAULT_METHOD);
@@ -229,14 +229,14 @@
     public void testMultipleDevOptionsAngleNative() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
+        installPackage(ANGLE_DRIVER_TEST_APP);
+        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," +
                         ANGLE_DRIVER_TEST_SEC_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE) + "," +
                         sDriverGlobalSettingMap.get(OpenGlDriverChoice.NATIVE));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
-
         waitThenRunDeviceTests(this, ANGLE_DRIVER_TEST_PKG,
                 ANGLE_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
                 ANGLE_DRIVER_TEST_ANGLE_METHOD);
@@ -315,12 +315,12 @@
     public void testDefaultNotInSettings() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
-                sDriverGlobalSettingMap.get(OpenGlDriverChoice.DEFAULT));
-
         // Install the package so the setting isn't removed because the package isn't present.
         installPackage(ANGLE_DRIVER_TEST_APP);
 
+        setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
+                sDriverGlobalSettingMap.get(OpenGlDriverChoice.DEFAULT));
+
         // Run the ANGLE activity so it'll clear up any 'default' settings.
         startActivity(getDevice(), ANGLE_MAIN_ACTIVTY);
 
@@ -376,13 +376,13 @@
     public void testMultipleDevOptionsAngleDefault() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
+        installPackage(ANGLE_DRIVER_TEST_APP);
+        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," + ANGLE_DRIVER_TEST_SEC_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE) + "," +
                         sDriverGlobalSettingMap.get(OpenGlDriverChoice.DEFAULT));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
-
         // Run the ANGLE activity so it'll clear up any 'default' settings.
         startActivity(getDevice(), ANGLE_MAIN_ACTIVTY);
 
@@ -408,12 +408,12 @@
     public void testMultipleDevOptionsAngleNativeUninstall() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
+        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," + ANGLE_DRIVER_TEST_SEC_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE) + "," +
                         sDriverGlobalSettingMap.get(OpenGlDriverChoice.NATIVE));
 
-        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
-
         // Run the ANGLE activity so it'll clear up any 'default' settings.
         startActivity(getDevice(), ANGLE_MAIN_ACTIVTY);
 
diff --git a/hostsidetests/devicepolicy/OWNERS b/hostsidetests/devicepolicy/OWNERS
new file mode 100644
index 0000000..12341eb
--- /dev/null
+++ b/hostsidetests/devicepolicy/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 100560
+include /tests/admin/OWNERS
diff --git a/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/Android.bp b/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/Android.bp
index 9456a6a..2e8d9f4 100644
--- a/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/NoLaunchableActivityApp/Android.bp
@@ -32,3 +32,45 @@
     ],
     sdk_version: "current",
 }
+
+// Build for no component app
+android_test_helper_app {
+    name: "CtsNoComponentApp",
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+        "cts_instant",
+    ],
+    manifest: "no_component_AndroidManifest.xml",
+    sdk_version: "current",
+}
+
+// Build for no permission app
+android_test_helper_app {
+    name: "CtsNoPermissionApp",
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+        "cts_instant",
+    ],
+    manifest: "no_permission_AndroidManifest.xml",
+    sdk_version: "current",
+}
diff --git a/hostsidetests/signedconfig/hostside/OWNERS b/hostsidetests/signedconfig/hostside/OWNERS
new file mode 100644
index 0000000..f968305
--- /dev/null
+++ b/hostsidetests/signedconfig/hostside/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 533114
+mathewi@google.com
+*
diff --git a/tests/JobScheduler/OWNERS b/tests/JobScheduler/OWNERS
new file mode 100644
index 0000000..ef7929f
--- /dev/null
+++ b/tests/JobScheduler/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 330738
+ctate@google.com
\ No newline at end of file
diff --git a/tests/JobSchedulerSharedUid/OWNERS b/tests/JobSchedulerSharedUid/OWNERS
new file mode 100644
index 0000000..ef7929f
--- /dev/null
+++ b/tests/JobSchedulerSharedUid/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 330738
+ctate@google.com
\ No newline at end of file
diff --git a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
index 437c35e..acaf0b3 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
@@ -118,19 +118,6 @@
      * @param instrumentation A valid instrumentation
      */
     public static void turnAccessibilityOff(Instrumentation instrumentation) {
-        if (SpeakingAccessibilityService.sConnectedInstance != null) {
-            SpeakingAccessibilityService.sConnectedInstance.disableSelf();
-            SpeakingAccessibilityService.sConnectedInstance = null;
-        }
-        if (VibratingAccessibilityService.sConnectedInstance != null) {
-            VibratingAccessibilityService.sConnectedInstance.disableSelf();
-            VibratingAccessibilityService.sConnectedInstance = null;
-        }
-        if (SpeakingAndVibratingAccessibilityService.sConnectedInstance != null) {
-            SpeakingAndVibratingAccessibilityService.sConnectedInstance.disableSelf();
-            SpeakingAndVibratingAccessibilityService.sConnectedInstance = null;
-        }
-
         final Object waitLockForA11yOff = new Object();
         AccessibilityManager manager = (AccessibilityManager) instrumentation
                 .getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -143,6 +130,19 @@
             }
         };
         manager.addAccessibilityStateChangeListener(listener);
+
+        if (SpeakingAccessibilityService.sConnectedInstance != null) {
+            SpeakingAccessibilityService.sConnectedInstance.disableSelf();
+            SpeakingAccessibilityService.sConnectedInstance = null;
+        }
+        if (VibratingAccessibilityService.sConnectedInstance != null) {
+            VibratingAccessibilityService.sConnectedInstance.disableSelf();
+            VibratingAccessibilityService.sConnectedInstance = null;
+        }
+        if (SpeakingAndVibratingAccessibilityService.sConnectedInstance != null) {
+            SpeakingAndVibratingAccessibilityService.sConnectedInstance.disableSelf();
+            SpeakingAndVibratingAccessibilityService.sConnectedInstance = null;
+        }
         waitOn(waitLockForA11yOff, () -> !accessibilityEnabled.get(), TIMEOUT_FOR_SERVICE_ENABLE,
                 "Accessibility turns off");
     }
diff --git a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
index 19daf4b..c1505ab 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
@@ -19,12 +19,15 @@
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.ComponentName;
+import android.content.Intent;
+import android.util.Log;
 import android.view.accessibility.AccessibilityEvent;
 
 /**
  * Stub accessibility service that reports itself as providing spoken feedback.
  */
 public class SpeakingAccessibilityService extends AccessibilityService {
+    private static final String LOG_TAG = SpeakingAccessibilityService.class.getSimpleName();
     public static final ComponentName COMPONENT_NAME = new ComponentName(
             "android.view.accessibility.cts",
             "android.view.accessibility.cts.SpeakingAccessibilityService");
@@ -33,8 +36,15 @@
     public static SpeakingAccessibilityService sConnectedInstance;
 
     @Override
+    public boolean onUnbind(Intent intent) {
+        Log.v(LOG_TAG, "onUnbind [" + this + "]");
+        return false;
+    }
+
+    @Override
     public void onDestroy() {
         sConnectedInstance = null;
+        Log.v(LOG_TAG, "onDestroy ["  + this + "]");
     }
 
     @Override
@@ -47,6 +57,7 @@
             sConnectedInstance = this;
             sWaitObjectForConnecting.notifyAll();
         }
+        Log.v(LOG_TAG, "onServiceConnected ["  + this + "]");
     }
 
     @Override
diff --git a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java
index 4a591f4..9f1f913 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java
@@ -18,21 +18,31 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.Intent;
+import android.util.Log;
 import android.view.accessibility.AccessibilityEvent;
 
 /**
  * Stub accessibility service that reports itself as providing multiple feedback types.
  */
 public class SpeakingAndVibratingAccessibilityService extends AccessibilityService {
+    private static final String LOG_TAG =
+            SpeakingAndVibratingAccessibilityService.class.getSimpleName();
     public static Object sWaitObjectForConnecting = new Object();
 
     public static SpeakingAndVibratingAccessibilityService sConnectedInstance;
 
     @Override
-    public void onDestroy() {
-        sConnectedInstance = null;
+    public boolean onUnbind(Intent intent) {
+        Log.v(LOG_TAG, "onUnbind [" + this + "]");
+        return false;
     }
 
+    @Override
+    public void onDestroy() {
+        sConnectedInstance = null;
+        Log.v(LOG_TAG, "onDestroy ["  + this + "]");
+    }
 
     @Override
     protected void onServiceConnected() {
@@ -40,6 +50,7 @@
             sConnectedInstance = this;
             sWaitObjectForConnecting.notifyAll();
         }
+        Log.v(LOG_TAG, "onServiceConnected ["  + this + "]");
     }
 
     @Override
diff --git a/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java b/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java
index 41edf9f..6f47546 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java
@@ -18,19 +18,29 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.Intent;
+import android.util.Log;
 import android.view.accessibility.AccessibilityEvent;
 
 /**
  * Stub accessibility service that reports itself as providing haptic feedback.
  */
 public class VibratingAccessibilityService extends AccessibilityService {
+    private static final String LOG_TAG = VibratingAccessibilityService.class.getSimpleName();
     public static Object sWaitObjectForConnecting = new Object();
 
     public static VibratingAccessibilityService sConnectedInstance;
 
     @Override
+    public boolean onUnbind(Intent intent) {
+        Log.v(LOG_TAG, "onUnbind [" + this + "]");
+        return false;
+    }
+
+    @Override
     public void onDestroy() {
         sConnectedInstance = null;
+        Log.v(LOG_TAG, "onDestroy ["  + this + "]");
     }
 
     @Override
@@ -43,6 +53,7 @@
             sConnectedInstance = this;
             sWaitObjectForConnecting.notifyAll();
         }
+        Log.v(LOG_TAG, "onServiceConnected ["  + this + "]");
     }
 
     @Override
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index e7e313e..8b50cbe 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -93,6 +93,17 @@
         </service>
 
         <service
+                android:name=".TouchExplorationStubAccessibilityService"
+                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService" />
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+            </intent-filter>
+            <meta-data
+                    android:name="android.accessibilityservice"
+                    android:resource="@xml/stub_touch_exploration_a11y_service" />
+        </service>
+        <service
             android:name=".InstrumentedAccessibilityService"
             android:label="@string/title_soft_keyboard_modes_accessibility_service"
             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index ad5d3fd..52fe23e 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -147,7 +147,9 @@
 
     <string name="stub_gesture_dispatch_a11y_service_description">com.android.accessibilityservice.cts.StubGestureAccessibilityService</string>
 
-    <string name="stub_gesture_detector_a11y_service_description">com.android.accessibilityservice.cts.StubService</string>
+    <string name="stub_gesture_detector_a11y_service_description">com.android.accessibilityservice.cts.GestureDetectionStubAccessibilityService</string>
+
+    <string name="stub_touch_exploration_a11y_service_description">com.android.accessibilityservice.cts.TouchExplorationStubAccessibilityService</string>
 
     <string name="stub_fprint_a11y_service_description">com.android.accessibilityservice.cts.StubFingerprintGestureAccessibilityService</string>
 
diff --git a/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
new file mode 100644
index 0000000..33c5ec8
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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.
+-->
+
+<accessibility-service
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:description="@string/stub_touch_exploration_a11y_service_description"
+        android:accessibilityEventTypes="typeAllMask"
+        android:accessibilityFeedbackType="feedbackGeneric"
+        android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode|flagReportViewIds"
+        android:canRequestTouchExplorationMode="true"
+        android:canRetrieveWindowContent="true"
+        android:canPerformGestures="true" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
index 63601cd..9a0430d 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
@@ -14,18 +14,22 @@
 
 package android.accessibilityservice.cts;
 
+import static org.junit.Assert.fail;
+
 import android.app.Instrumentation;
 import android.view.accessibility.AccessibilityEvent;
+
 import java.util.ArrayList;
+import java.util.List;
 
 /** Accessibility service stub, which will collect recognized gestures. */
 public class GestureDetectionStubAccessibilityService extends InstrumentedAccessibilityService {
     private static final long GESTURE_RECOGNIZE_TIMEOUT_MS = 3000;
-    private static final long EVENT_RECOGNIZE_TIMEOUT_MS = 3000;
+    private static final long EVENT_RECOGNIZE_TIMEOUT_MS = 5000;
     // Member variables
-    private final Object mLock = new Object();
+    protected final Object mLock = new Object();
     private ArrayList<Integer> mCollectedGestures = new ArrayList();
-    private ArrayList<Integer> mCollectedEvents = new ArrayList();
+    protected ArrayList<Integer> mCollectedEvents = new ArrayList();
 
     public static GestureDetectionStubAccessibilityService enableSelf(
             Instrumentation instrumentation) {
@@ -122,4 +126,37 @@
             }
         }
     }
+
+    /** Insure that the specified accessibility events have been received. */
+    public void assertPropagated(int... events) {
+        waitUntilEvent(events.length);
+        // Set up readable error reporting.
+        List<String> received = new ArrayList<>();
+        List<String> expected = new ArrayList<>();
+        for (int event : events) {
+            expected.add(AccessibilityEvent.eventTypeToString(event));
+        }
+        for (int i = 0; i < getEventsSize(); ++i) {
+            received.add(AccessibilityEvent.eventTypeToString(getEvent(i)));
+        }
+
+        if (events.length != getEventsSize()) {
+            String message =
+                    String.format(
+                            "Received %d events when expecting %d. Received %s, expected %s",
+                            received.size(),
+                            expected.size(),
+                            received.toString(),
+                            expected.toString());
+            fail(message);
+        }
+        else if (!expected.equals(received)) {
+            String message =
+                    String.format(
+                            "Received %s, expected %s",
+                            received.toString(),
+                            expected.toString());
+            fail(message);
+        }
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
index 0ff067f..5a863a8 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
@@ -16,6 +16,9 @@
 
 package android.accessibilityservice.cts;
 
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Instrumentation;
@@ -23,11 +26,9 @@
 import android.os.Handler;
 import android.os.SystemClock;
 import android.provider.Settings;
-import androidx.annotation.CallSuper;
-import android.test.InstrumentationTestCase;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
-
+import androidx.annotation.CallSuper;
 import java.lang.ref.WeakReference;
 import java.util.HashMap;
 import java.util.List;
@@ -36,9 +37,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
 public class InstrumentedAccessibilityService extends AccessibilityService {
 
     private static final boolean DEBUG = false;
@@ -48,7 +46,7 @@
 
     // Timeout disabled in #DEBUG mode to prevent breakpoint-related failures
     private static final int TIMEOUT_SERVICE_ENABLE = DEBUG ? Integer.MAX_VALUE : 10000;
-    private static final int TIMEOUT_SERVICE_PERFORM_SYNC = DEBUG ? Integer.MAX_VALUE : 5000;
+    private static final int TIMEOUT_SERVICE_PERFORM_SYNC = DEBUG ? Integer.MAX_VALUE : 10000;
 
     private static final HashMap<Class, WeakReference<InstrumentedAccessibilityService>>
             sInstances = new HashMap<>();
@@ -57,7 +55,6 @@
     final Object mInterruptWaitObject = new Object();
     public boolean mOnInterruptCalled;
 
-
     @Override
     @CallSuper
     protected void onServiceConnected() {
@@ -104,13 +101,14 @@
     public <T extends Object> T getOnService(Callable<T> callable) {
         AtomicReference<T> returnValue = new AtomicReference<>(null);
         AtomicReference<Throwable> throwable = new AtomicReference<>(null);
-        runOnServiceSync(() -> {
-            try {
-                returnValue.set(callable.call());
-            } catch (Throwable e) {
-                throwable.set(e);
-            }
-        });
+        runOnServiceSync(
+                () -> {
+                    try {
+                        returnValue.set(callable.call());
+                    } catch (Throwable e) {
+                        throwable.set(e);
+                    }
+                });
         if (throwable.get() != null) {
             throw new RuntimeException(throwable.get());
         }
@@ -155,21 +153,23 @@
             Instrumentation instrumentation, Class<T> clazz) {
         final String serviceName = clazz.getSimpleName();
         final Context context = instrumentation.getContext();
-        final String enabledServices = Settings.Secure.getString(
-                context.getContentResolver(),
-                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+        final String enabledServices =
+                Settings.Secure.getString(
+                        context.getContentResolver(),
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
         if (enabledServices != null) {
             assertFalse("Service is already enabled", enabledServices.contains(serviceName));
         }
-        final AccessibilityManager manager = (AccessibilityManager) context.getSystemService(
-                Context.ACCESSIBILITY_SERVICE);
+        final AccessibilityManager manager =
+                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
         final List<AccessibilityServiceInfo> serviceInfos =
                 manager.getInstalledAccessibilityServiceList();
         for (AccessibilityServiceInfo serviceInfo : serviceInfos) {
             final String serviceId = serviceInfo.getId();
             if (serviceId.endsWith(serviceName)) {
                 ShellCommandBuilder.create(instrumentation)
-                        .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                        .putSecureSetting(
+                                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                                 enabledServices + COMPONENT_NAME_SEPARATOR + serviceId)
                         .putSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED, "1")
                         .run();
@@ -177,11 +177,15 @@
                 final T instance = getInstanceForClass(clazz, TIMEOUT_SERVICE_ENABLE);
                 if (instance == null) {
                     ShellCommandBuilder.create(instrumentation)
-                            .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                                    enabledServices)
+                            .putSecureSetting(
+                                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices)
                             .run();
-                    throw new RuntimeException("Starting accessibility service " + serviceName
-                            + " took longer than " + TIMEOUT_SERVICE_ENABLE + "ms");
+                    throw new RuntimeException(
+                            "Starting accessibility service "
+                                    + serviceName
+                                    + " took longer than "
+                                    + TIMEOUT_SERVICE_ENABLE
+                                    + "ms");
                 }
                 return instance;
             }
@@ -189,8 +193,8 @@
         throw new RuntimeException("Accessibility service " + serviceName + " not found");
     }
 
-    private static <T extends InstrumentedAccessibilityService> T getInstanceForClass(Class clazz,
-            long timeoutMillis) {
+    private static <T extends InstrumentedAccessibilityService> T getInstanceForClass(
+            Class clazz, long timeoutMillis) {
         final long timeoutTimeMillis = SystemClock.uptimeMillis() + timeoutMillis;
         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
             synchronized (sInstances) {
@@ -218,11 +222,12 @@
         final Context context = instrumentation.getContext();
         final AccessibilityManager manager =
                 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        manager.addAccessibilityStateChangeListener(b -> {
-            synchronized (waitLockForA11yOff) {
-                waitLockForA11yOff.notifyAll();
-            }
-        });
+        manager.addAccessibilityStateChangeListener(
+                b -> {
+                    synchronized (waitLockForA11yOff) {
+                        waitLockForA11yOff.notifyAll();
+                    }
+                });
 
         ShellCommandBuilder.create(instrumentation)
                 .deleteSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
@@ -233,7 +238,8 @@
         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
             synchronized (waitLockForA11yOff) {
                 if (manager.getEnabledAccessibilityServiceList(
-                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty()) {
+                                AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
+                        .isEmpty()) {
                     return;
                 }
                 try {
@@ -243,7 +249,9 @@
                 }
             }
         }
-        throw new RuntimeException("Disabling all accessibility services took longer than "
-                + TIMEOUT_SERVICE_ENABLE + "ms");
+        throw new RuntimeException(
+                "Disabling all accessibility services took longer than "
+                        + TIMEOUT_SERVICE_ENABLE
+                        + "ms");
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
new file mode 100644
index 0000000..fd7a226
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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 android.accessibilityservice.cts;
+
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_HOVER_ENTER;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_HOVER_EXIT;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
+
+import android.app.Instrumentation;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * This accessibility service stub collects all events relating to touch exploration rather than
+ * just the few collected by GestureDetectionStubAccessibilityService
+ */
+public class TouchExplorationStubAccessibilityService
+        extends GestureDetectionStubAccessibilityService {
+    @Override
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        synchronized (mLock) {
+            switch (event.getEventType()) {
+                case TYPE_GESTURE_DETECTION_START:
+                case TYPE_GESTURE_DETECTION_END:
+                case TYPE_VIEW_HOVER_ENTER:
+                case TYPE_VIEW_HOVER_EXIT:
+                case TYPE_VIEW_FOCUSED:
+                case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+                case TYPE_VIEW_CLICKED:
+                case TYPE_VIEW_LONG_CLICKED:
+                    mCollectedEvents.add(event.getEventType());
+            }
+        }
+        super.onAccessibilityEvent(event);
+    }
+
+    public static TouchExplorationStubAccessibilityService enableSelf(
+            Instrumentation instrumentation) {
+        return InstrumentedAccessibilityService.enableService(
+                instrumentation, TouchExplorationStubAccessibilityService.class);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
new file mode 100644
index 0000000..e9f27e3
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
@@ -0,0 +1,356 @@
+/*
+ * 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 android.accessibilityservice.cts;
+
+import static android.accessibilityservice.cts.utils.AsyncUtils.await;
+import static android.accessibilityservice.cts.utils.GestureUtils.add;
+import static android.accessibilityservice.cts.utils.GestureUtils.click;
+import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
+import static android.accessibilityservice.cts.utils.GestureUtils.doubleTap;
+import static android.accessibilityservice.cts.utils.GestureUtils.doubleTapAndHold;
+import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_HOVER_ENTER;
+import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_HOVER_ENTER;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_HOVER_EXIT;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
+
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
+import android.accessibilityservice.cts.utils.EventCapturingClickListener;
+import android.accessibilityservice.cts.utils.EventCapturingHoverListener;
+import android.accessibilityservice.cts.utils.EventCapturingLongClickListener;
+import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.platform.test.annotations.AppModeFull;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * A set of tests for testing touch exploration. Each test dispatches a gesture and checks for the
+ * appropriate hover and/or touch events followed by the appropriate accessibility events. Some
+ * tests will then check for events from the view.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class TouchExplorerTest {
+    // Constants
+    private static final float GESTURE_LENGTH_INCHES = 1.0f;
+    private TouchExplorationStubAccessibilityService mService;
+    private Instrumentation mInstrumentation;
+    private UiAutomation mUiAutomation;
+    private boolean mHasTouchscreen;
+    private boolean mScreenBigEnough;
+    private EventCapturingHoverListener mHoverListener = new EventCapturingHoverListener(false);
+    private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener(false);
+    private EventCapturingClickListener mClickListener = new EventCapturingClickListener();
+    private EventCapturingLongClickListener mLongClickListener =
+            new EventCapturingLongClickListener();
+
+    @Rule
+    public ActivityTestRule<GestureDispatchActivity> mActivityRule =
+            new ActivityTestRule<>(GestureDispatchActivity.class, false);
+
+    Point mCenter; // Center of screen. Gestures all start from this point.
+    PointF mTapLocation;
+    float mSwipeDistance;
+    View mView;
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mUiAutomation = mInstrumentation.getUiAutomation();
+        PackageManager pm = mInstrumentation.getContext().getPackageManager();
+        mHasTouchscreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+                || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
+        // Find screen size, check that it is big enough for gestures.
+        // Gestures will start in the center of the screen, so we need enough horiz/vert space.
+        WindowManager windowManager =
+                (WindowManager)
+                        mInstrumentation.getContext().getSystemService(Context.WINDOW_SERVICE);
+        final DisplayMetrics metrics = new DisplayMetrics();
+        windowManager.getDefaultDisplay().getRealMetrics(metrics);
+        mCenter = new Point((int) metrics.widthPixels / 2, (int) metrics.heightPixels / 2);
+        mTapLocation = new PointF(mCenter);
+        mScreenBigEnough = (metrics.widthPixels / (2 * metrics.xdpi) > GESTURE_LENGTH_INCHES);
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        mService = TouchExplorationStubAccessibilityService.enableSelf(mInstrumentation);
+        mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
+        mView.setOnHoverListener(mHoverListener);
+        mView.setOnTouchListener(mTouchListener);
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mSwipeDistance = mView.getWidth() / 4;
+                    mView.setOnClickListener(mClickListener);
+                    mView.setOnLongClickListener(mLongClickListener);
+                });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        if (mService != null) {
+            mService.runOnServiceSync(() -> mService.disableSelfAndRemove());
+            mService = null;
+        }
+    }
+
+    /** Test a slow swipe which should initiate touch exploration. */
+    @Test
+    @AppModeFull
+    public void testSlowSwipe() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0), 400));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER,         ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_VIEW_FOCUSED,
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                TYPE_VIEW_HOVER_ENTER,
+                TYPE_VIEW_HOVER_EXIT,
+                TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                TYPE_TOUCH_INTERACTION_END);
+    }
+
+    /** Test a fast swipe which should not initiate touch exploration. */
+    @Test
+    @AppModeFull
+    public void testFastSwipe() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0)));
+        mHoverListener.assertNonePropagated();
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_VIEW_FOCUSED,
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_GESTURE_DETECTION_START,
+                TYPE_GESTURE_DETECTION_END,
+                TYPE_TOUCH_INTERACTION_END);
+    }
+
+    /** Test a basic single tap which should initiate touch exploration. */
+    @Test
+    @AppModeFull
+    public void testSingleTap() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        dispatch(click(mTapLocation));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_VIEW_FOCUSED,
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                TYPE_VIEW_HOVER_ENTER,
+                TYPE_VIEW_HOVER_EXIT,
+                TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                TYPE_TOUCH_INTERACTION_END);
+    }
+
+    /**
+     * Test the case where we double tap and no item has accessibility focus, so TouchExplorer sends
+     * touch events to the last touch-explored coordinates to simulate a click.
+     */
+    @Test
+    @AppModeFull
+    public void testDoubleTapNoAccessibilityFocus() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        // Do a single tap so there is a valid last touch-explored location.
+        dispatch(click(mTapLocation));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+        // We don't really care about these events but we need to make sure all the events we want
+        // to clear have arrived before we clear them.
+        mService.assertPropagated(
+                TYPE_VIEW_FOCUSED,
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                TYPE_VIEW_HOVER_ENTER,
+                TYPE_VIEW_HOVER_EXIT,
+                TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                TYPE_TOUCH_INTERACTION_END);
+        mService.clearEvents();
+        dispatch(doubleTap(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        // The click gets delivered as a series of touch events.
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
+        mClickListener.assertClicked(mView);
+    }
+
+    /**
+     * Test the case where we want to click on the item that has accessibility focus by using
+     * AccessibilityNodeInfo.performAction.
+     */
+    @Test
+    @AppModeFull
+    public void testDoubleTapAccessibilityFocus() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        syncAccessibilityFocusToInputFocus();
+        dispatch(doubleTap(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        // The click should not be delivered via touch events in this case.
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_VIEW_FOCUSED,
+                TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_INTERACTION_END,
+                TYPE_VIEW_CLICKED);
+        mClickListener.assertClicked(mView);
+    }
+
+    /**
+     * Test the case where we double tap but there is neither an accessibility focus or a last
+     * touch-explored location. Nothing should happen.
+     */
+    @Test
+    @AppModeFull
+    public void testDoubleTapNoFocus() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        dispatch(doubleTap(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_VIEW_FOCUSED, TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
+        mService.clearEvents();
+        mClickListener.assertNoneClicked();
+    }
+
+    /** Test the case where we want to long click on the item that has accessibility focus. */
+    @Test
+    @AppModeFull
+    public void testDoubleTapAndHoldAccessibilityFocus() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        syncAccessibilityFocusToInputFocus();
+        dispatch(doubleTapAndHold(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        // The long click still gets delivered as a series of touch events even though we derive the
+        // location from accessibility focus.
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
+        mService.assertPropagated(
+                TYPE_VIEW_FOCUSED,
+                TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_VIEW_LONG_CLICKED,
+                TYPE_TOUCH_INTERACTION_END);
+        mLongClickListener.assertLongClicked(mView);
+    }
+
+    /**
+     * Test the case where we double tap and hold and no item has accessibility focus, so
+     * TouchExplorer sends touch events to the last touch-explored coordinates to simulate a long
+     * click.
+     */
+    @Test
+    @AppModeFull
+    public void testDoubleTapAndHoldNoAccessibilityFocus() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        // Do a single tap so there is a valid last touch-explored location.
+        dispatch(click(mTapLocation));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+        // We don't really care about these events but we need to make sure all the events we want
+        // to clear have arrived before we clear them.
+        mService.assertPropagated(
+                TYPE_VIEW_FOCUSED,
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                TYPE_VIEW_HOVER_ENTER,
+                TYPE_VIEW_HOVER_EXIT,
+                TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                TYPE_TOUCH_INTERACTION_END);
+        mService.clearEvents();
+        dispatch(doubleTapAndHold(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        // The click gets delivered as a series of touch events.
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START, TYPE_VIEW_LONG_CLICKED, TYPE_TOUCH_INTERACTION_END);
+        mLongClickListener.assertLongClicked(mView);
+    }
+
+    /**
+     * Test the case where we double tap and hold but there is neither an accessibility focus or a
+     * last touch-explored location. Nothing should happen.
+     */
+    @Test
+    @AppModeFull
+    public void testDoubleTapAndHoldNoFocus() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        dispatch(doubleTap(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_VIEW_FOCUSED, TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
+        mService.clearEvents();
+        mLongClickListener.assertNoneLongClicked();
+    }
+
+    public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
+        GestureDescription.Builder builder =
+                new GestureDescription.Builder().addStroke(firstStroke);
+        for (StrokeDescription stroke : rest) {
+            builder.addStroke(stroke);
+        }
+        dispatch(builder.build());
+    }
+
+    public void dispatch(GestureDescription gesture) {
+        await(dispatchGesture(mService, gesture));
+    }
+
+    /** Set the accessibility focus to the element that has input focus. */
+    private void syncAccessibilityFocusToInputFocus() {
+        mService.runOnServiceSync(
+                () -> {
+                    mUiAutomation
+                            .getRootInActiveWindow()
+                            .findFocus(AccessibilityNodeInfo.FOCUS_INPUT)
+                            .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+                });
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingClickListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingClickListener.java
new file mode 100644
index 0000000..116bccf
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingClickListener.java
@@ -0,0 +1,72 @@
+/*
+ * 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 android.accessibilityservice.cts.utils;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.view.View;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/** A click event listener that keeps an ordered record of events. */
+public class EventCapturingClickListener implements View.OnClickListener {
+
+    private final BlockingQueue<View> mViews = new LinkedBlockingQueue<>();
+
+    @Override
+    public void onClick(View view) {
+        mViews.offer(view);
+    }
+
+    /** Insure that the specified views have received click events. */
+    public void assertClicked(View... views) {
+        View view;
+        try {
+            for (View v : views) {
+                long waitTime = 5; // seconds
+                view = mViews.poll(waitTime, SECONDS);
+                assertNotNull(
+                        "Expected click event for "
+                                + v.toString()
+                                + " but none present after "
+                                + waitTime
+                                + " seconds",
+                        view);
+                if (v != view) {
+                    fail("Unexpected click event for view" + view.toString());
+                }
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** Insure that no click events have been received. */
+    public void assertNoneClicked() {
+        try {
+            long waitTime = 1; // seconds
+            View view = mViews.poll(waitTime, SECONDS);
+            if (view != null) {
+                fail("Unexpected click event for view" + view.toString());
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
new file mode 100644
index 0000000..87d46ba
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
@@ -0,0 +1,105 @@
+/*
+ * 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 android.accessibilityservice.cts.utils;
+
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.view.MotionEvent;
+import android.view.View;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/** This Listener listens for and logs hover events so they can be checked later by tests. */
+public class EventCapturingHoverListener implements View.OnHoverListener {
+
+    private boolean shouldConsumeEvents; // whether or not to keep events from propagating to other
+    // listeners
+    private final BlockingQueue<MotionEvent> mEvents = new LinkedBlockingQueue<>();
+
+    public EventCapturingHoverListener(boolean shouldConsumeEvents) {
+        this.shouldConsumeEvents = shouldConsumeEvents;
+    }
+
+    public EventCapturingHoverListener() {
+        this.shouldConsumeEvents = true;
+    }
+
+    @Override
+    public boolean onHover(View view, MotionEvent MotionEvent) {
+        assertTrue(mEvents.offer(MotionEvent.obtain(MotionEvent)));
+        return shouldConsumeEvents;
+    }
+
+    /** Insure that no hover events have been detected. */
+    public void assertNonePropagated() {
+        try {
+            long waitTime = 1; // seconds
+            MotionEvent event = mEvents.poll(waitTime, SECONDS);
+            if (event != null) {
+                fail("Unexpected touch event " + event.toString());
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Check for the specified hover events. Note that specifying ACTION_HOVER_MOVE will match one
+     * or more consecutive ACTION_HOVER_MOVE events.
+     */
+    public void assertPropagated(int... eventTypes) {
+        MotionEvent ev;
+        long waitTime = 5; // seconds
+        try {
+            List<String> expected = new ArrayList<>();
+            List<String> received = new ArrayList<>();
+            for (int e : eventTypes) {
+                expected.add(MotionEvent.actionToString(e));
+            }
+            ev = mEvents.poll(waitTime, SECONDS);
+            assertNotNull(
+                    "Expected " + expected + " but none present after " + waitTime + " seconds",
+                    ev);
+            // By this point there is at least one received event.
+            received.add(MotionEvent.actionToString(ev.getActionMasked()));
+            ev = mEvents.poll(waitTime, SECONDS);
+            while (ev != null) {
+                int action = ev.getActionMasked();
+                if (action != ACTION_HOVER_MOVE) {
+                    received.add(MotionEvent.actionToString(action));
+                } else {
+                    // Add the current event if the previous received event was not ACTION_MOVE
+                    String prev = received.get(received.size() - 1);
+                    if (!prev.equals(MotionEvent.actionToString(ACTION_HOVER_MOVE))) {
+                        received.add(MotionEvent.actionToString(action));
+                    }
+                }
+                ev = mEvents.poll(waitTime, SECONDS);
+            }
+            assertEquals(expected, received);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingLongClickListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingLongClickListener.java
new file mode 100644
index 0000000..5daf89e
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingLongClickListener.java
@@ -0,0 +1,87 @@
+/*
+ * 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 android.accessibilityservice.cts.utils;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.view.View;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/** A click event listener that keeps an ordered record of events. */
+public class EventCapturingLongClickListener implements View.OnLongClickListener {
+
+    // whether or not to keep events from propagating to other listeners.
+    // Note that setting this to false means that the accessibility service will see both a long
+    // click and a click event when you perform a double tap and hold gesture
+    private boolean shouldConsumeEvents;
+    private final BlockingQueue<View> mViews = new LinkedBlockingQueue<>();
+
+    public EventCapturingLongClickListener(boolean shouldConsumeEvents) {
+        this.shouldConsumeEvents = shouldConsumeEvents;
+    }
+
+    public EventCapturingLongClickListener() {
+        this.shouldConsumeEvents = true;
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        mViews.offer(view);
+        return true;
+    }
+
+    /** Insure that the specified views have received long click events. */
+    public void assertLongClicked(View... views) {
+        View view;
+        try {
+            for (View v : views) {
+                long waitTime = 5; // seconds
+                view = mViews.poll(waitTime, SECONDS);
+                assertNotNull(
+                        "Expected long click event for "
+                                + v.toString()
+                                + " but none present after "
+                                + waitTime
+                                + " seconds",
+                        view);
+                if (v != view) {
+                    fail("Unexpected long click event for view" + view.toString());
+                }
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** Insure that no click events have been received. */
+    public void assertNoneLongClicked() {
+        try {
+            long waitTime = 1; // seconds
+            View view = mViews.poll(waitTime, SECONDS);
+            if (view != null) {
+                fail("Unexpected long click event for view" + view.toString());
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
index d7ae4592..7768af6 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
@@ -16,22 +16,92 @@
 
 package android.accessibilityservice.cts.utils;
 
+import static android.view.MotionEvent.ACTION_MOVE;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.view.MotionEvent;
 import android.view.View;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 
 public class EventCapturingTouchListener implements View.OnTouchListener {
-
+    // whether or not to keep events from propagating to other listeners
+    private boolean shouldConsumeEvents;
+    // Todo (b/128917646): fix the tests that rely on this being public.
     public final BlockingQueue<MotionEvent> events = new LinkedBlockingQueue<>();
 
+    public EventCapturingTouchListener(boolean shouldConsumeEvents) {
+        this.shouldConsumeEvents = shouldConsumeEvents;
+    }
+
+    public EventCapturingTouchListener() {
+        this.shouldConsumeEvents = true;
+    }
+
     @Override
     public boolean onTouch(View view, MotionEvent motionEvent) {
         assertTrue(events.offer(MotionEvent.obtain(motionEvent)));
-        return true;
+        return shouldConsumeEvents;
+    }
+
+    /** Insure that no touch events have been detected. */
+    public void assertNonePropagated() {
+        try {
+            long waitTime = 1; // seconds
+            MotionEvent event = events.poll(waitTime, SECONDS);
+            if (event != null) {
+                fail("Unexpected touch event " + event.toString());
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Check for the specified touch events. Note that specifying ACTION_MOVE will match one or more
+     * consecutive ACTION_MOVE events.
+     */
+    public void assertPropagated(int... eventTypes) {
+        MotionEvent ev;
+        long waitTime = 5; // seconds
+        try {
+            List<String> expected = new ArrayList<>();
+            List<String> received = new ArrayList<>();
+            for (int e : eventTypes) {
+                expected.add(MotionEvent.actionToString(e));
+            }
+            ev = events.poll(waitTime, SECONDS);
+            assertNotNull(
+                    "Expected " + expected + " but none present after " + waitTime + " seconds",
+                    ev);
+            // By this point there is at least one received event.
+            received.add(MotionEvent.actionToString(ev.getActionMasked()));
+            ev = events.poll(waitTime, SECONDS);
+            while (ev != null) {
+                int action = ev.getActionMasked();
+                if (action != ACTION_MOVE) {
+                    received.add(MotionEvent.actionToString(action));
+                } else {
+                    // Add the current event if the previous received event was not ACTION_MOVE
+                    String prev = received.get(received.size() - 1);
+                    if (!prev.equals(MotionEvent.actionToString(ACTION_MOVE))) {
+                        received.add(MotionEvent.actionToString(action));
+                    }
+                }
+                ev = events.poll(waitTime, SECONDS);
+            }
+            assertEquals(expected, received);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
index 076a6da..f15d940 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
@@ -73,7 +73,7 @@
 
     public static StrokeDescription longClick(PointF point) {
         return new StrokeDescription(path(point), 0,
-                ViewConfiguration.getLongPressTimeout() * 3 / 2);
+                ViewConfiguration.getLongPressTimeout() * 3);
     }
 
     public static StrokeDescription swipe(PointF from, PointF to) {
@@ -141,4 +141,33 @@
     public static PointF ceil(PointF p) {
         return new PointF((float) Math.ceil(p.x), (float) Math.ceil(p.y));
     }
-}
+
+    public static GestureDescription doubleTap(PointF point) {
+        return multiTap(point, 2);
+    }
+
+    public static GestureDescription tripleTap(PointF point) {
+      return multiTap(point, 3);
+    }
+
+    public static GestureDescription multiTap(PointF point, int taps) {
+        GestureDescription.Builder builder = new GestureDescription.Builder();
+        long time = 0;
+        for (int i = 0; i < taps; i++) {
+            StrokeDescription stroke = click(point);
+            builder.addStroke(startingAt(time, stroke));
+            time += stroke.getDuration() + 40;
+        }
+        return builder.build();
+    }
+
+    public static GestureDescription doubleTapAndHold(PointF point) {
+        GestureDescription.Builder builder = new GestureDescription.Builder();
+        StrokeDescription tap1 = click(point);
+        StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 40, longClick(point));
+        builder.addStroke(tap1);
+        builder.addStroke(tap2);
+        return builder.build();
+    }
+
+}
\ No newline at end of file
diff --git a/tests/admin/OWNERS b/tests/admin/OWNERS
new file mode 100644
index 0000000..46669be
--- /dev/null
+++ b/tests/admin/OWNERS
@@ -0,0 +1,9 @@
+# Bug component: 100560
+sandness@google.com
+rubinxu@google.com
+eranm@google.com
+irinaid@google.com
+pgrafov@google.com
+alexkershaw@google.com
+arangelov@google.com
+scottjonathan@google.com
diff --git a/tests/contentcaptureservice/AndroidManifest.xml b/tests/contentcaptureservice/AndroidManifest.xml
index 7f2c108..f0d262f 100644
--- a/tests/contentcaptureservice/AndroidManifest.xml
+++ b/tests/contentcaptureservice/AndroidManifest.xml
@@ -22,6 +22,49 @@
 
         <uses-library android:name="android.test.runner" />
 
+        <activity android:name=".BlankActivity"
+                  android:label="Blank"
+                  android:taskAffinity=".BlankActivity"
+                  android:theme="@android:style/Theme.NoTitleBar">
+            <intent-filter>
+                <!-- This intent filter is not really needed by CTS, but it makes easier to launch
+                     this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".BlankWithTitleActivity"
+                  android:label="Blanka"
+                  android:taskAffinity=".BlankWithTitleActivity">
+            <intent-filter>
+                <!-- This intent filter is not really needed by CTS, but it makes easier to launch
+                     this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".LoginActivity"
+                  android:label="Login"
+                  android:taskAffinity=".LoginActivity"
+                  android:theme="@android:style/Theme.NoTitleBar">
+            <intent-filter>
+                <!-- This intent filter is not really needed by CTS, but it makes easier to launch
+                     this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".ChildlessActivity"
+                  android:label="Childless"
+                  android:taskAffinity=".ChildlessActivity"
+                  android:theme="@android:style/Theme.NoTitleBar">
+            <intent-filter>
+                <!-- This intent filter is not really needed by CTS, but it makes easier to launch
+                     this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
         <activity android:name=".CustomViewActivity"
                   android:label="CustomView"
                   android:taskAffinity=".CustomViewActivity"
diff --git a/tests/contentcaptureservice/res/layout/childless_activity.xml b/tests/contentcaptureservice/res/layout/childless_activity.xml
new file mode 100644
index 0000000..8cc67d5
--- /dev/null
+++ b/tests/contentcaptureservice/res/layout/childless_activity.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/root_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/contentcaptureservice/res/layout/login_activity.xml b/tests/contentcaptureservice/res/layout/login_activity.xml
new file mode 100644
index 0000000..1164b4f
--- /dev/null
+++ b/tests/contentcaptureservice/res/layout/login_activity.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/root_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:orientation="vertical" >
+
+      <TextView
+          android:id="@+id/username_label"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:text="Username" />
+
+      <EditText
+          android:id="@+id/username"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content" />
+
+      <TextView
+          android:id="@+id/password_label"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:text="Password" />
+
+      <EditText
+          android:id="@+id/password"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:inputType="textPassword" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
index cf941ed..d3c426d 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
@@ -114,7 +114,7 @@
                 mServiceWatcher.waitOnDestroy();
             }
         } catch (Throwable t) {
-            Log.e(TAG, "error disablign service", t);
+            Log.e(TAG, "error disabling service", t);
         }
     }
 
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractRootViewActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractRootViewActivity.java
new file mode 100644
index 0000000..0c6ca18
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractRootViewActivity.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 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 android.contentcaptureservice.cts;
+
+import android.app.Activity;
+import android.contentcaptureservice.cts.common.DoubleVisitor;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Base class for classes that have a {@code root_view} root view.
+ */
+abstract class AbstractRootViewActivity extends AbstractContentCaptureActivity {
+
+    private static final String TAG = AbstractRootViewActivity.class.getSimpleName();
+
+    private static DoubleVisitor<AbstractRootViewActivity, LinearLayout> sRootViewVisitor;
+    private static DoubleVisitor<AbstractRootViewActivity, LinearLayout> sOnAnimationVisitor;
+
+    private LinearLayout mRootView;
+
+    /**
+     * Sets a visitor called when the activity is created.
+     */
+    static void onRootView(@NonNull DoubleVisitor<AbstractRootViewActivity, LinearLayout> visitor) {
+        sRootViewVisitor = visitor;
+    }
+
+    /**
+     * Sets a visitor to be called on {@link Activity#onEnterAnimationComplete()}.
+     */
+    static void onAnimationComplete(
+            @NonNull DoubleVisitor<AbstractRootViewActivity, LinearLayout> visitor) {
+        sOnAnimationVisitor = visitor;
+    }
+
+    @Override
+    protected final void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentViewOnCreate(savedInstanceState);
+
+        mRootView = findViewById(R.id.root_view);
+
+        Log.d(TAG, "onCreate(): parents for " + getClass() + ": rootView=" + mRootView
+                + "\ngrandParent=" + getGrandParent()
+                + "\ngrandGrandParent=" + getGrandGrandParent());
+
+        if (sRootViewVisitor != null) {
+            Log.d(TAG, "Applying visitor to " + this + "/" + mRootView);
+            try {
+                sRootViewVisitor.visit(this, mRootView);
+            } finally {
+                sRootViewVisitor = null;
+            }
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        Log.d(TAG, "AutofillIds for " + getClass() + ": "
+                + " rootView=" + getRootView().getAutofillId()
+                + ", grandParent=" + getGrandParent().getAutofillId()
+                + ", grandGrandParent=" + getGrandGrandParent().getAutofillId());
+    }
+
+    @Override
+    public void onEnterAnimationComplete() {
+        if (sOnAnimationVisitor != null) {
+            Log.i(TAG, "onEnterAnimationComplete(): applying visitor on " + this);
+            try {
+                sOnAnimationVisitor.visit(this, mRootView);
+            } finally {
+                sOnAnimationVisitor = null;
+            }
+        } else {
+            Log.i(TAG, "onEnterAnimationComplete(): no visitor on " + this);
+        }
+    }
+
+    public LinearLayout getRootView() {
+        return mRootView;
+    }
+
+    // TODO(b/122315042): remove this method when not needed anymore
+    @NonNull
+    public ViewGroup getGrandParent() {
+        return (ViewGroup) mRootView.getParent();
+    }
+
+    // TODO(b/122315042): remove this method when not needed anymore
+    @NonNull
+    public ViewGroup getGrandGrandParent() {
+        return (ViewGroup) getGrandParent().getParent();
+    }
+
+    /**
+     * The real "onCreate" method that should be extended by subclasses.
+     *
+     */
+    protected abstract void setContentViewOnCreate(Bundle savedInstanceState);
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
index 5203460..0f7e298 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
@@ -226,6 +226,22 @@
         assertSessionId(expectedSessionId, expectedView);
     }
 
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_TREE_APPEARING} event.
+     */
+    public static void assertViewTreeStarted(@NonNull List<ContentCaptureEvent> events,
+            int index) {
+        assertSessionLevelEvent(events, index, TYPE_VIEW_TREE_APPEARING);
+    }
+
+    /**
+     * Asserts the contents of a {@link #TYPE_VIEW_TREE_APPEARED} event.
+     */
+    public static void assertViewTreeFinished(@NonNull List<ContentCaptureEvent> events,
+            int index) {
+        assertSessionLevelEvent(events, index, TYPE_VIEW_TREE_APPEARED);
+    }
+
     private static void assertSessionLevelEvent(@NonNull List<ContentCaptureEvent> events,
             int index, int expectedType) {
         final ContentCaptureEvent event = getEvent(events, index, expectedType);
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivity.java
new file mode 100644
index 0000000..ae9134f
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 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 android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertNoViewLevelEvents;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+
+import androidx.annotation.NonNull;
+
+public class BlankActivity extends AbstractContentCaptureActivity {
+
+    @Override
+    public void assertDefaultEvents(@NonNull Session session) {
+        assertNoViewLevelEvents(session, this);
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
new file mode 100644
index 0000000..20b62fb
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2018 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 android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.CtsContentCaptureService.CONTENT_CAPTURE_SERVICE_COMPONENT_NAME;
+import static android.contentcaptureservice.cts.Helper.resetService;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+@AppModeFull(reason = "BlankWithTitleActivityTest is enough")
+public class BlankActivityTest
+        extends AbstractContentCaptureIntegrationAutoActivityLaunchTest<BlankActivity> {
+
+    private static final String TAG = BlankActivityTest.class.getSimpleName();
+
+    private static final ActivityTestRule<BlankActivity> sActivityRule = new ActivityTestRule<>(
+            BlankActivity.class, false, false);
+
+    public BlankActivityTest() {
+        super(BlankActivity.class);
+    }
+
+    @Override
+    protected ActivityTestRule<BlankActivity> getActivityTestRule() {
+        return sActivityRule;
+    }
+
+    @Test
+    public void testSimpleSessionLifecycle() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        activity.assertDefaultEvents(session);
+    }
+
+    @Test
+    public void testGetServiceComponentName() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        service.waitUntilConnected();
+
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        try {
+            assertThat(activity.getContentCaptureManager().getServiceComponentName())
+                    .isEqualTo(CONTENT_CAPTURE_SERVICE_COMPONENT_NAME);
+
+            resetService();
+            service.waitUntilDisconnected();
+
+            assertThat(activity.getContentCaptureManager().getServiceComponentName())
+                    .isNotEqualTo(CONTENT_CAPTURE_SERVICE_COMPONENT_NAME);
+        } finally {
+            activity.finish();
+            watcher.waitFor(DESTROYED);
+        }
+    }
+
+    @Test
+    public void testGetServiceComponentName_onUiThread() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        service.waitUntilConnected();
+
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final AtomicReference<ComponentName> ref = new AtomicReference<>();
+        activity.syncRunOnUiThread(
+                () -> ref.set(activity.getContentCaptureManager().getServiceComponentName()));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        assertThat(ref.get()).isEqualTo(CONTENT_CAPTURE_SERVICE_COMPONENT_NAME);
+    }
+
+    @Test
+    public void testIsContentCaptureFeatureEnabled_onUiThread() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        service.waitUntilConnected();
+
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final AtomicBoolean ref = new AtomicBoolean();
+        activity.syncRunOnUiThread(() -> ref
+                .set(activity.getContentCaptureManager().isContentCaptureFeatureEnabled()));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        assertThat(ref.get()).isTrue();
+    }
+
+    @Test
+    public void testDisableContentCaptureService_onUiThread() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        service.waitUntilConnected();
+
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        service.disableSelf();
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+    }
+
+    @Test
+    public void testOnConnectionEvents() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        service.waitUntilConnected();
+
+        resetService();
+        service.waitUntilDisconnected();
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivity.java
new file mode 100644
index 0000000..81198f7
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivity.java
@@ -0,0 +1,70 @@
+/*
+ * 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 android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertDecorViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
+import static android.contentcaptureservice.cts.Assertions.assertSessionPaused;
+import static android.contentcaptureservice.cts.Assertions.assertSessionResumed;
+import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
+import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.util.Log;
+import android.view.View;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSessionId;
+import android.view.contentcapture.ViewNode;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+public class BlankWithTitleActivity extends AbstractContentCaptureActivity {
+
+    private static final String TAG = BlankWithTitleActivity.class.getSimpleName();
+
+    @Override
+    public void assertDefaultEvents(@NonNull Session session) {
+        final ContentCaptureSessionId sessionId = session.id;
+        assertRightActivity(session, sessionId, this);
+
+        final View decorView = getDecorView();
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events(" + events.size() + "): " + events);
+
+        final int minEvents = 9; // TODO(b/122315042): disappeared not always sent
+        assertThat(events.size()).isAtLeast(minEvents);
+
+        assertSessionResumed(events, 0);
+        assertViewTreeStarted(events, 1);
+        assertDecorViewAppeared(events, 2, decorView);
+        // TODO(b/123540067): ignoring 3 intermediate parents
+        final ViewNode title = assertViewAppeared(events, 6).getViewNode();
+        assertThat(title.getText()).isEqualTo("Blanka");
+        assertViewTreeFinished(events, 7);
+        assertSessionPaused(events, 8);
+        if (false) { // TODO(b/123540067): disabled because it includes the parent
+            assertViewsOptionallyDisappeared(events, minEvents, decorView.getAutofillId(),
+                    title.getAutofillId());
+        }
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivityTest.java
new file mode 100644
index 0000000..5cf0e5c3
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivityTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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 android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
+import android.content.Intent;
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Test;
+
+public class BlankWithTitleActivityTest
+        extends AbstractContentCaptureIntegrationAutoActivityLaunchTest<BlankWithTitleActivity> {
+
+    private static final String TAG = BlankWithTitleActivityTest.class.getSimpleName();
+
+    private static final ActivityTestRule<BlankWithTitleActivity> sActivityRule =
+            new ActivityTestRule<>(BlankWithTitleActivity.class, false, false);
+
+    public BlankWithTitleActivityTest() {
+        super(BlankWithTitleActivity.class);
+    }
+
+    @Override
+    protected ActivityTestRule<BlankWithTitleActivity> getActivityTestRule() {
+        return sActivityRule;
+    }
+
+    @Test
+    public void testSimpleSessionLifecycle() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankWithTitleActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        activity.assertDefaultEvents(session);
+    }
+
+    @AppModeFull(reason = "testSimpleSessionLifecycle() is enough")
+    @Test
+    public void testSimpleSessionLifecycle_noAnimation() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final BlankWithTitleActivity activity = launchActivity(
+                (intent) -> intent.addFlags(
+                        Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_NEW_TASK));
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        activity.assertDefaultEvents(session);
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivity.java
new file mode 100644
index 0000000..be6fd85
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 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 android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertNoViewLevelEvents;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+
+public class ChildlessActivity extends AbstractRootViewActivity {
+
+    @Override
+    protected void setContentViewOnCreate(Bundle savedInstanceState) {
+        setContentView(R.layout.childless_activity);
+    }
+
+    @Override
+    public void assertDefaultEvents(@NonNull Session session) {
+        // Should be empty because the root view is not important for content capture without a
+        // child that is important.
+        assertNoViewLevelEvents(session, this);
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
new file mode 100644
index 0000000..2d3bb6f
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
@@ -0,0 +1,1259 @@
+/*
+ * Copyright (C) 2018 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 android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.LifecycleOrder.CREATION;
+import static android.contentcaptureservice.cts.Assertions.LifecycleOrder.DESTRUCTION;
+import static android.contentcaptureservice.cts.Assertions.assertChildSessionContext;
+import static android.contentcaptureservice.cts.Assertions.assertDecorViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertLifecycleOrder;
+import static android.contentcaptureservice.cts.Assertions.assertMainSessionContext;
+import static android.contentcaptureservice.cts.Assertions.assertNoViewLevelEvents;
+import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
+import static android.contentcaptureservice.cts.Assertions.assertSessionPaused;
+import static android.contentcaptureservice.cts.Assertions.assertSessionResumed;
+import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewDisappeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
+import static android.contentcaptureservice.cts.Assertions.assertViewsDisappeared;
+import static android.contentcaptureservice.cts.Helper.newImportantView;
+import static android.contentcaptureservice.cts.Helper.sContext;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.ComponentName;
+import android.content.LocusId;
+import android.contentcaptureservice.cts.CtsContentCaptureService.DisconnectListener;
+import android.contentcaptureservice.cts.CtsContentCaptureService.ServiceWatcher;
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.contentcaptureservice.cts.common.ActivityLauncher;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
+import android.view.contentcapture.ContentCaptureSessionId;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+@AppModeFull(reason = "BlankWithTitleActivityTest is enough")
+public class ChildlessActivityTest
+        extends AbstractContentCaptureIntegrationAutoActivityLaunchTest<ChildlessActivity> {
+
+    private static final String TAG = ChildlessActivityTest.class.getSimpleName();
+
+    private static final ActivityTestRule<ChildlessActivity> sActivityRule = new ActivityTestRule<>(
+            ChildlessActivity.class, false, false);
+
+    public ChildlessActivityTest() {
+        super(ChildlessActivity.class);
+    }
+
+    @Override
+    protected ActivityTestRule<ChildlessActivity> getActivityTestRule() {
+        return sActivityRule;
+    }
+
+    @Before
+    @After
+    public void resetActivityStaticState() {
+        ChildlessActivity.onRootView(null);
+    }
+
+    @Test
+    public void testDefaultLifecycle() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        activity.assertDefaultEvents(session);
+    }
+
+    @Test
+    public void testGetContentCapture_disabledWhenNoService() throws Exception {
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        assertThat(activity.getContentCaptureManager().isContentCaptureEnabled()).isFalse();
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+    }
+
+    @Test
+    public void testGetContentCapture_enabledWhenNoService() throws Exception {
+        enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        assertThat(activity.getContentCaptureManager().isContentCaptureEnabled()).isTrue();
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+    }
+
+    @Test
+    public void testLaunchAnotherActivity() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher1 = startWatcher();
+
+        // Launch and finish 1st activity
+        final ChildlessActivity activity1 = launchActivity();
+        watcher1.waitFor(RESUMED);
+        activity1.finish();
+        watcher1.waitFor(DESTROYED);
+
+        // Launch and finish 2nd activity
+        final ActivityLauncher<LoginActivity> anotherActivityLauncher = new ActivityLauncher<>(
+                sContext, mActivitiesWatcher, LoginActivity.class);
+        final ActivityWatcher watcher2 = anotherActivityLauncher.getWatcher();
+        final LoginActivity activity2 = anotherActivityLauncher.launchActivity();
+        watcher2.waitFor(RESUMED);
+        activity2.finish();
+        watcher2.waitFor(DESTROYED);
+
+        // Assert the sessions
+        final List<ContentCaptureSessionId> sessionIds = service.getAllSessionIds();
+        assertThat(sessionIds).hasSize(2);
+        final ContentCaptureSessionId sessionId1 = sessionIds.get(0);
+        Log.v(TAG, "session id1: " + sessionId1);
+        final ContentCaptureSessionId sessionId2 = sessionIds.get(1);
+        Log.v(TAG, "session id2: " + sessionId2);
+
+        final Session session1 = service.getFinishedSession(sessionId1);
+        activity1.assertDefaultEvents(session1);
+
+        final Session session2 = service.getFinishedSession(sessionId2);
+        activity2.assertDefaultEvents(session2);
+    }
+
+
+    @Test
+    public void testLaunchAnotherActivity_onTopOfIt() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher1 = startWatcher();
+
+        // Launch 1st activity
+        final ChildlessActivity activity1 = launchActivity();
+        watcher1.waitFor(RESUMED);
+
+        // Launch and finish 2nd activity
+        final ActivityLauncher<LoginActivity> anotherActivityLauncher = new ActivityLauncher<>(
+                sContext, mActivitiesWatcher, LoginActivity.class);
+        final ActivityWatcher watcher2 = anotherActivityLauncher.getWatcher();
+        final LoginActivity activity2 = anotherActivityLauncher.launchActivity();
+
+        watcher2.waitFor(RESUMED);
+        activity2.finish();
+        watcher2.waitFor(DESTROYED);
+
+        // Finish 1st activity
+        activity1.finish();
+        watcher1.waitFor(DESTROYED);
+
+        // Assert the activity lifecycle events
+        final ComponentName name1 = activity1.getComponentName();
+        final ComponentName name2 = activity2.getComponentName();
+        service.assertThat()
+            .activityResumed(name1)
+            .activityPaused(name1)
+            .activityResumed(name2)
+            .activityStopped(name1)
+            .activityPaused(name2)
+            .activityResumed(name1)
+            .activityDestroyed(name2)
+            .activityPaused(name1);
+
+        // Assert the sessions
+        final List<ContentCaptureSessionId> sessionIds = service.getAllSessionIds();
+        assertThat(sessionIds).hasSize(2);
+        final ContentCaptureSessionId sessionId1 = sessionIds.get(0);
+        Log.v(TAG, "session id1: " + sessionId1);
+        final ContentCaptureSessionId sessionId2 = sessionIds.get(1);
+        Log.v(TAG, "session id2: " + sessionId2);
+
+        final Session session1 = service.getFinishedSession(sessionId1);
+        final List<ContentCaptureEvent> events1 = session1.getEvents();
+        Log.v(TAG, "events on " + activity1 + ": " + events1);
+        assertThat(events1).hasSize(4);
+        assertSessionResumed(events1, 0);
+        assertSessionPaused(events1, 1);
+        assertSessionResumed(events1, 2);
+        assertSessionPaused(events1, 3);
+
+        final Session session2 = service.getFinishedSession(sessionId2);
+        activity2.assertDefaultEvents(session2);
+
+    }
+
+    @Test
+    public void testAddAndRemoveNoImportantChild() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        // Child must be created inside the lambda because it needs to use the Activity context.
+        final AtomicReference<TextView> childRef = new AtomicReference<>();
+
+        ChildlessActivity.onRootView((activity, rootView) -> {
+            final TextView child = new TextView(activity);
+            child.setText("VIEW, Y U NO IMPORTANT?");
+            child.setImportantForContentCapture(View.IMPORTANT_FOR_CONTENT_CAPTURE_NO);
+
+            rootView.addView(child);
+        });
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        // Remove view
+        final TextView child = childRef.get();
+        activity.syncRunOnUiThread(() -> activity.getRootView().removeView(child));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        // Should be empty because the root view is not important for content capture without a
+        // child that is important.
+        assertNoViewLevelEvents(session, activity);
+    }
+
+    @Test
+    public void testAddAndRemoveImportantChild() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        // TODO(b/120494182): Child must be created inside the lambda because it needs to use the
+        // Activity context.
+        final AtomicReference<TextView> childRef = new AtomicReference<>();
+
+        ChildlessActivity.onRootView((activity, rootView) -> {
+            final TextView text = newImportantView(activity, "Important I am");
+            rootView.addView(text);
+            childRef.set(text);
+        });
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        // Remove view
+        final LinearLayout rootView = activity.getRootView();
+        final TextView child = childRef.get();
+        activity.syncRunOnUiThread(() -> rootView.removeView(child));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events(" + events.size() + "): " + events);
+
+        final AutofillId rootId = rootView.getAutofillId();
+
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+        final View decorView = activity.getDecorView();
+
+        // Assert just the relevant events
+        assertThat(events.size()).isAtLeast(12);
+        assertSessionResumed(events, 0);
+        assertViewTreeStarted(events, 1);
+        assertDecorViewAppeared(events, 2, decorView);
+        assertViewAppeared(events, 3, grandpa2, decorView.getAutofillId());
+        assertViewAppeared(events, 4, grandpa1, grandpa2.getAutofillId());
+        assertViewAppeared(events, 5, sessionId, rootView, grandpa1.getAutofillId());
+        assertViewAppeared(events, 6, sessionId, child, rootId);
+        assertViewTreeFinished(events, 7);
+        assertViewTreeStarted(events, 8);
+        assertViewDisappeared(events, 9, child.getAutofillId());
+        assertViewTreeFinished(events, 10);
+        assertSessionPaused(events, 11);
+
+        // TODO(b/122315042): assert parents disappeared
+    }
+
+    @Test
+    public void testAddImportantChildAfterSessionStarted() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        // Add View
+        final LinearLayout rootView = activity.getRootView();
+        final TextView child = newImportantView(activity, "Important I am");
+        activity.runOnUiThread(() -> rootView.addView(child));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events(" + events.size() + "): " + events);
+
+        final View grandpa = activity.getGrandParent();
+
+        // Assert just the relevant events
+
+        assertThat(events.size()).isAtLeast(6);
+        // TODO(b/122959591): figure out the child is coming first
+        assertSessionResumed(events, 0);
+        assertViewTreeStarted(events, 1);
+        assertViewAppeared(events, 2, sessionId, child, rootView.getAutofillId());
+        assertViewAppeared(events, 3, sessionId, rootView, grandpa.getAutofillId());
+        assertViewTreeFinished(events, 4);
+        assertSessionPaused(events, 5);
+    }
+
+    @Test
+    public void testAddAndRemoveImportantChildOnDifferentSession() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final LinearLayout rootView = activity.getRootView();
+        final View grandpa = activity.getGrandParent();
+
+        final ContentCaptureSession mainSession = rootView.getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        final ContentCaptureSession childSession = mainSession
+                .createContentCaptureSession(newContentCaptureContextBuilder("child")
+                        .build());
+        final ContentCaptureSessionId childSessionId = childSession.getContentCaptureSessionId();
+        Log.v(TAG, "child session id: " + childSessionId);
+
+        final TextView child = newImportantView(activity, "Important I am");
+        final AutofillId childId = child.getAutofillId();
+        Log.v(TAG, "childId: " + childId);
+        child.setContentCaptureSession(childSession);
+        activity.runOnUiThread(() -> rootView.addView(child));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> sessionIds = service.getAllSessionIds();
+        assertThat(sessionIds).containsExactly(mainSessionId, childSessionId).inOrder();
+
+        // Assert sessions
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+        final List<ContentCaptureEvent> mainEvents = mainTestSession.getEvents();
+        Log.v(TAG, "mainEvents(" + mainEvents.size() + "): " + mainEvents);
+
+        assertThat(mainEvents.size()).isAtLeast(5);
+        assertSessionResumed(mainEvents, 0);
+        assertViewTreeStarted(mainEvents, 1);
+        assertViewAppeared(mainEvents, 2, mainSessionId, rootView, grandpa.getAutofillId());
+        assertViewTreeFinished(mainEvents, 3);
+        assertSessionPaused(mainEvents, 4);
+
+        final Session childTestSession = service.getFinishedSession(childSessionId);
+        assertChildSessionContext(childTestSession, "child");
+        final List<ContentCaptureEvent> childEvents = childTestSession.getEvents();
+        Log.v(TAG, "childEvents(" + childEvents.size() + "): " + childEvents);
+        final int minEvents = 3;
+        assertThat(childEvents.size()).isAtLeast(minEvents);
+        assertViewTreeStarted(childEvents, 0);
+        assertViewAppeared(childEvents, 1, childSessionId, child, rootView.getAutofillId());
+        assertViewTreeFinished(childEvents, 2);
+        // TODO(b/122315042): assert parents disappeared
+    }
+
+    /**
+     * Tests scenario where new sessions are added from the main session, but they're not nested
+     * neither have views attached to them.
+     */
+    @Test
+    public void testDinamicallyManageChildlessSiblingSessions() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final ContentCaptureSession mainSession = activity.getRootView().getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create 1st session
+        final ContentCaptureContext context1 = newContentCaptureContextBuilder("session1")
+                .build();
+        final ContentCaptureSession childSession1 = mainSession
+                .createContentCaptureSession(context1);
+        final ContentCaptureSessionId childSessionId1 = childSession1.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 1: " + childSessionId1);
+
+        // Create 2nd session
+        final ContentCaptureContext context2 = newContentCaptureContextBuilder("session2")
+                .build();
+        final ContentCaptureSession childSession2 = mainSession
+                .createContentCaptureSession(context2);
+        final ContentCaptureSessionId childSessionId2 = childSession2.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 2: " + childSessionId2);
+
+        // Close 1st session before opening 3rd
+        childSession1.close();
+
+        // Create 3nd session...
+        final ContentCaptureContext context3 = newContentCaptureContextBuilder("session3")
+                .build();
+        final ContentCaptureSession childSession3 = mainSession
+                .createContentCaptureSession(context3);
+        final ContentCaptureSessionId childSessionId3 = childSession3.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 3: " + childSessionId3);
+
+        // ...and close it right away
+        childSession3.close();
+
+        // Create 4nd session
+        final ContentCaptureContext context4 = newContentCaptureContextBuilder("session4")
+                .build();
+        final ContentCaptureSession childSession4 = mainSession
+                .createContentCaptureSession(context4);
+        final ContentCaptureSessionId childSessionId4 = childSession4.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 4: " + childSessionId4);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(
+                mainSessionId,
+                childSessionId1,
+                childSessionId2,
+                childSessionId3,
+                childSessionId4)
+            .inOrder();
+
+        // Assert main sessions info
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+
+        final Session childTestSession1 = service.getFinishedSession(childSessionId1);
+        assertChildSessionContext(childTestSession1, "session1");
+
+        final Session childTestSession2 = service.getFinishedSession(childSessionId2);
+        assertChildSessionContext(childTestSession2, "session2");
+
+        final Session childTestSession3 = service.getFinishedSession(childSessionId3);
+        assertChildSessionContext(childTestSession3, "session3");
+
+        final Session childTestSession4 = service.getFinishedSession(childSessionId4);
+        assertChildSessionContext(childTestSession4, "session4");
+
+        // Gets all events first so they're all logged before the assertions
+        final List<ContentCaptureEvent> mainEvents = mainTestSession.getEvents();
+        final List<ContentCaptureEvent> events1 = childTestSession1.getEvents();
+        final List<ContentCaptureEvent> events2 = childTestSession2.getEvents();
+        final List<ContentCaptureEvent> events3 = childTestSession3.getEvents();
+        final List<ContentCaptureEvent> events4 = childTestSession4.getEvents();
+        Log.v(TAG, "mainEvents(" + mainEvents.size() + "): " + mainEvents);
+        Log.v(TAG, "events1(" + events1.size() + "): " + events1);
+        Log.v(TAG, "events2(" + events2.size() + "): " + events2);
+        Log.v(TAG, "events3(" + events3.size() + "): " + events3);
+        Log.v(TAG, "events4(" + events4.size() + "): " + events4);
+
+        assertNoViewLevelEvents(mainTestSession, activity);
+        assertThat(events1).isEmpty();
+        assertThat(events2).isEmpty();
+        assertThat(events3).isEmpty();
+        assertThat(events4).isEmpty();
+
+        // Assert lifecycle methods were called in the right order
+        assertLifecycleOrder(1, mainTestSession,   CREATION);
+        assertLifecycleOrder(2, childTestSession1, CREATION);
+        assertLifecycleOrder(3, childTestSession2, CREATION);
+        assertLifecycleOrder(4, childTestSession1, DESTRUCTION);
+        assertLifecycleOrder(5, childTestSession3, CREATION);
+        assertLifecycleOrder(6, childTestSession3, DESTRUCTION);
+        assertLifecycleOrder(7, childTestSession4, CREATION);
+        assertLifecycleOrder(8, childTestSession2, DESTRUCTION);
+        assertLifecycleOrder(9, childTestSession4, DESTRUCTION);
+        assertLifecycleOrder(10, mainTestSession,  DESTRUCTION);
+    }
+
+    @Test
+    public void testDinamicallyAddOneChildOnAnotherSession_manuallyCloseSession() throws Exception {
+        dinamicallyAddOneChildOnAnotherSessionTest(/* manuallyCloseSession= */ true);
+    }
+
+    @Test
+    public void testDinamicallyAddOneChildOnAnotherSession_autoCloseSession() throws Exception {
+        dinamicallyAddOneChildOnAnotherSessionTest(/* manuallyCloseSession= */ false);
+    }
+
+    /**
+     * Tests scenario where just 1 session with 1 dinamically added view is created.
+     */
+    private void dinamicallyAddOneChildOnAnotherSessionTest(boolean manuallyCloseSession)
+            throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+        final ContentCaptureSession mainSession = activity.getRootView().getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create session
+        final ContentCaptureSession childSession = mainSession
+                .createContentCaptureSession(
+                        newContentCaptureContextBuilder("child_session").build());
+        final ContentCaptureSessionId childSessionId = childSession.getContentCaptureSessionId();
+        Log.v(TAG, "child session: " + childSessionId);
+
+        final TextView child = addChild(activity, childSession, "Sweet O'Mine");
+        if (manuallyCloseSession) {
+            waitAndClose(childSession);
+        }
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(mainSessionId, childSessionId).inOrder();
+
+        // Assert main session
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+        // TODO(b/123540067): ideally it should be empty, but has intermediate parents stuff...
+        // assertThat(mainTestSession.getEvents()).isEmpty();
+
+        // Assert child session
+        final Session childTestSession = service.getFinishedSession(childSessionId);
+        assertChildSessionContext(childTestSession, "child_session");
+        final List<ContentCaptureEvent> childEvents = childTestSession.getEvents();
+        assertThat(childEvents.size()).isAtLeast(3);
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        assertViewTreeStarted(childEvents, 0);
+        assertViewAppeared(childEvents, 1, child, rootId);
+        assertViewTreeFinished(childEvents, 2);
+
+        // Assert lifecycle methods were called in the right order
+        assertLifecycleOrder(1, mainTestSession,  CREATION);
+        assertLifecycleOrder(2, childTestSession, CREATION);
+        assertLifecycleOrder(3, childTestSession, DESTRUCTION);
+        assertLifecycleOrder(4, mainTestSession, DESTRUCTION);
+    }
+
+    /**
+     * Tests scenario where new sessions with children are added from the main session.
+     */
+    @Test
+    public void testDinamicallyManageSiblingSessions() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+        final LinearLayout rootView = activity.getRootView();
+        final ContentCaptureSession mainSession = rootView.getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create 1st session
+        final ContentCaptureContext context1 = newContentCaptureContextBuilder("session1")
+                .build();
+        final ContentCaptureSession childSession1 = mainSession
+                .createContentCaptureSession(context1);
+        final ContentCaptureSessionId childSessionId1 = childSession1.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 1: " + childSessionId1);
+
+        // Session 1, child 1
+        final TextView s1c1 = addChild(activity, childSession1, "s1c1");
+
+        // Create 2nd session
+        final ContentCaptureContext context2 = newContentCaptureContextBuilder("session2")
+                .build();
+        final ContentCaptureSession childSession2 = mainSession
+                .createContentCaptureSession(context2);
+        final ContentCaptureSessionId childSessionId2 = childSession2.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 2: " + childSessionId2);
+
+        final TextView s2c1 = newImportantView(activity, childSession2, "s2c1");
+        final TextView s2c2 = newImportantView(activity, childSession2, "s2c1");
+
+        // Add 2 children together so they're wrapped a view_tree batch
+        activity.runOnUiThread(() -> {
+            rootView.addView(s2c1);
+            rootView.addView(s2c2);
+        });
+
+        // Close 1st session before opening 3rd
+        waitAndClose(childSession1);
+
+        // Create 3nd session...
+        final ContentCaptureContext context3 = newContentCaptureContextBuilder("session3")
+                .build();
+        final ContentCaptureSession childSession3 = mainSession
+                .createContentCaptureSession(context3);
+        final ContentCaptureSessionId childSessionId3 = childSession3.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 3: " + childSessionId3);
+
+        final TextView s3c1 = newImportantView(activity, childSession3, "s3c1");
+        final TextView s3c2 = newImportantView(activity, childSession3, "s3c1");
+        final TextView s3c3 = newImportantView(activity, childSession3, "s3c3");
+
+        // Add 2 children together so they're wrapped a view_tree batch
+        activity.runOnUiThread(() -> {
+            rootView.addView(s3c1);
+            rootView.addView(s3c2);
+        });
+
+        // TODO(b/123024698): need to wait until the 4 events are flushed - ideally we should block
+        // waiting until the service received them
+        sleep();
+
+        // Add 2 children so they're wrapped a view_tree batch
+        activity.runOnUiThread(() -> {
+            rootView.removeView(s3c1);
+            rootView.addView(s3c3);
+        });
+
+        // ...and close it right away
+        waitAndClose(childSession3);
+
+        // Create 4nd session
+        final ContentCaptureContext context4 = newContentCaptureContextBuilder("session4")
+                .build();
+        final ContentCaptureSession childSession4 = mainSession
+                .createContentCaptureSession(context4);
+        final ContentCaptureSessionId childSessionId4 = childSession4.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 4: " + childSessionId4);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(
+                mainSessionId,
+                childSessionId1,
+                childSessionId2,
+                childSessionId3,
+                childSessionId4)
+            .inOrder();
+
+        // Assert main sessions info
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+        final List<ContentCaptureEvent> mainEvents = mainTestSession.getEvents();
+        Log.v(TAG, "main session events(" + mainEvents.size() + "): " + mainEvents);
+
+        // Gets all events first so they're all logged before the assertions
+        final Session childTestSession1 = service.getFinishedSession(childSessionId1);
+        assertChildSessionContext(childTestSession1, "session1");
+        final List<ContentCaptureEvent> events1 = childTestSession1.getEvents();
+        Log.v(TAG, "events1(" + events1.size() + "): " + events1);
+
+        final Session childTestSession2 = service.getFinishedSession(childSessionId2);
+        final List<ContentCaptureEvent> events2 = childTestSession2.getEvents();
+        assertChildSessionContext(childTestSession2, "session2");
+        Log.v(TAG, "events2(" + events2.size() + "): " + events2);
+        final Session childTestSession3 = service.getFinishedSession(childSessionId3);
+        assertChildSessionContext(childTestSession3, "session3");
+        List<ContentCaptureEvent> events3 = childTestSession3.getEvents();
+        Log.v(TAG, "events3(" + events3.size() + "): " + events3);
+
+        final AutofillId rootId = rootView.getAutofillId();
+        final View grandpa = activity.getGrandParent();
+
+        assertThat(mainEvents).hasSize(8);
+        assertSessionResumed(mainEvents, 0);
+        assertViewTreeStarted(mainEvents, 1);
+        assertViewAppeared(mainEvents, 2, rootView, grandpa.getAutofillId());
+        assertViewTreeFinished(mainEvents, 3);
+        assertSessionPaused(mainEvents, 4); // TODO(b/122959591): investigate why
+        assertViewTreeStarted(mainEvents, 5);
+        assertViewDisappeared(mainEvents, 6, rootId);
+        assertViewTreeFinished(mainEvents, 7);
+
+        assertThat(events1).hasSize(3);
+        assertViewTreeStarted(events1, 0);
+        assertViewAppeared(events1, 1, s1c1, rootId);
+        assertViewTreeFinished(events1, 2);
+
+        assertThat(events2.size()).isAtLeast(4);
+        assertViewTreeStarted(events2, 0);
+        assertViewAppeared(events2, 1, s2c1, rootId);
+        assertViewAppeared(events2, 2, s2c2, rootId);
+        assertViewTreeFinished(events2, 3);
+        // TODO(b/122315042): assert parents disappeared
+
+        assertThat(events3).hasSize(8);
+        assertViewTreeStarted(events3, 0);
+        assertViewAppeared(events3, 1, s3c1, rootId);
+        assertViewAppeared(events3, 2, s3c2, rootId);
+        assertViewTreeFinished(events3, 3);
+        assertViewTreeStarted(events3, 4);
+        assertViewDisappeared(events3, 5, s3c1.getAutofillId());
+        assertViewAppeared(events3, 6, s3c3, rootId);
+        assertViewTreeFinished(events3, 7);
+
+        final Session childTestSession4 = service.getFinishedSession(childSessionId4);
+        assertChildSessionContext(childTestSession4, "session4");
+        assertThat(childTestSession4.getEvents()).isEmpty();
+
+        // Assert lifecycle methods were called in the right order
+        assertLifecycleOrder(1, mainTestSession,   CREATION);
+        assertLifecycleOrder(2, childTestSession1, CREATION);
+        assertLifecycleOrder(3, childTestSession2, CREATION);
+        assertLifecycleOrder(4, childTestSession1, DESTRUCTION);
+        assertLifecycleOrder(5, childTestSession3, CREATION);
+        assertLifecycleOrder(6, childTestSession3, DESTRUCTION);
+        assertLifecycleOrder(7, childTestSession4, CREATION);
+        assertLifecycleOrder(8, childTestSession2, DESTRUCTION);
+        assertLifecycleOrder(9, childTestSession4, DESTRUCTION);
+        assertLifecycleOrder(10, mainTestSession,  DESTRUCTION);
+    }
+
+    @Test
+    public void testNestedSessions_simplestScenario() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final ContentCaptureSession mainSession = activity.getRootView().getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create child session
+        final ContentCaptureContext childContext = newContentCaptureContextBuilder("child")
+                .build();
+        final ContentCaptureSession childSession = mainSession
+                .createContentCaptureSession(childContext);
+        final ContentCaptureSessionId childSessionId = childSession.getContentCaptureSessionId();
+        Log.v(TAG, "child session id: " + childSessionId);
+
+        // Create grand child session
+        final ContentCaptureContext grandChild = newContentCaptureContextBuilder("grandChild")
+                .build();
+        final ContentCaptureSession grandChildSession = childSession
+                .createContentCaptureSession(grandChild);
+        final ContentCaptureSessionId grandChildSessionId = grandChildSession
+                .getContentCaptureSessionId();
+        Log.v(TAG, "child session id: " + grandChildSessionId);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(
+                mainSessionId,
+                childSessionId,
+                grandChildSessionId)
+            .inOrder();
+
+        // Assert sessions
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+        assertNoViewLevelEvents(mainTestSession, activity);
+
+        final Session childTestSession = service.getFinishedSession(childSessionId);
+        assertChildSessionContext(childTestSession, "child");
+        assertThat(childTestSession.getEvents()).isEmpty();
+
+        final Session grandChildTestSession = service.getFinishedSession(grandChildSessionId);
+        assertChildSessionContext(grandChildTestSession, "grandChild");
+        assertThat(grandChildTestSession.getEvents()).isEmpty();
+
+        // Assert lifecycle methods were called in the right order
+        assertLifecycleOrder(1, mainTestSession, CREATION);
+        assertLifecycleOrder(2, childTestSession, CREATION);
+        assertLifecycleOrder(3, grandChildTestSession, CREATION);
+        assertLifecycleOrder(4, grandChildTestSession, DESTRUCTION);
+        assertLifecycleOrder(5, childTestSession, DESTRUCTION);
+        assertLifecycleOrder(6, mainTestSession,  DESTRUCTION);
+    }
+
+    /**
+     * Tests scenario where new sessions are added from each other session, but they're not nested
+     * neither have views attached to them.
+     *
+     * <p>This test actions are exactly the same as
+     * {@link #testDinamicallyManageChildlessSiblingSessions()}, except for session nesting (and
+     * order of lifecycle events).
+     */
+    @Test
+    public void testDinamicallyManageChildlessNestedSessions() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final ContentCaptureSession mainSession = activity.getRootView().getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create 1st session
+        final ContentCaptureContext context1 = newContentCaptureContextBuilder("session1")
+                .build();
+        final ContentCaptureSession childSession1 = mainSession
+                .createContentCaptureSession(context1);
+        final ContentCaptureSessionId childSessionId1 = childSession1.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 1: " + childSessionId1);
+
+        // Create 2nd session
+        final ContentCaptureContext context2 = newContentCaptureContextBuilder("session2")
+                .build();
+        final ContentCaptureSession childSession2 = childSession1
+                .createContentCaptureSession(context2);
+        final ContentCaptureSessionId childSessionId2 = childSession2.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 2: " + childSessionId2);
+
+        // Close 1st session before opening 3rd
+        childSession1.close();
+
+        // Create 3nd session...
+        final ContentCaptureContext context3 = newContentCaptureContextBuilder("session3")
+                .build();
+        final ContentCaptureSession childSession3 = mainSession
+                .createContentCaptureSession(context3);
+        final ContentCaptureSessionId childSessionId3 = childSession3.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 3: " + childSessionId3);
+
+        // ...and close it right away
+        childSession3.close();
+
+        // Create 4nd session
+        final ContentCaptureContext context4 = newContentCaptureContextBuilder("session4")
+                .build();
+        final ContentCaptureSession childSession4 = mainSession
+                .createContentCaptureSession(context4);
+        final ContentCaptureSessionId childSessionId4 = childSession4.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 4: " + childSessionId4);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(
+                mainSessionId,
+                childSessionId1,
+                childSessionId2,
+                childSessionId3,
+                childSessionId4)
+            .inOrder();
+
+        // Assert main sessions info
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+        assertNoViewLevelEvents(mainTestSession, activity);
+
+        final Session childTestSession1 = service.getFinishedSession(childSessionId1);
+        assertChildSessionContext(childTestSession1, "session1");
+        assertThat(childTestSession1.getEvents()).isEmpty();
+
+        final Session childTestSession2 = service.getFinishedSession(childSessionId2);
+        assertChildSessionContext(childTestSession2, "session2");
+        assertThat(childTestSession2.getEvents()).isEmpty();
+
+        final Session childTestSession3 = service.getFinishedSession(childSessionId3);
+        assertChildSessionContext(childTestSession3, "session3");
+        assertThat(childTestSession3.getEvents()).isEmpty();
+
+        final Session childTestSession4 = service.getFinishedSession(childSessionId4);
+        assertChildSessionContext(childTestSession4, "session4");
+        assertThat(childTestSession4.getEvents()).isEmpty();
+
+        // Assert lifecycle methods were called in the right order
+        assertLifecycleOrder(1, mainTestSession,   CREATION);
+        assertLifecycleOrder(2, childTestSession1, CREATION);
+        assertLifecycleOrder(3, childTestSession2, CREATION);
+        assertLifecycleOrder(4, childTestSession2, DESTRUCTION);
+        assertLifecycleOrder(5, childTestSession1, DESTRUCTION);
+        assertLifecycleOrder(6, childTestSession3, CREATION);
+        assertLifecycleOrder(7, childTestSession3, DESTRUCTION);
+        assertLifecycleOrder(8, childTestSession4, CREATION);
+        assertLifecycleOrder(9, childTestSession4, DESTRUCTION);
+        assertLifecycleOrder(10, mainTestSession,  DESTRUCTION);
+    }
+
+    /**
+     * Tests scenario where views from different session are removed in sequence - they should not
+     * have been batched.
+     */
+    @Test
+    public void testRemoveChildrenFromDifferentSessions() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ChildlessActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+        final LinearLayout rootView = activity.getRootView();
+        final ContentCaptureSession mainSession = rootView.getContentCaptureSession();
+        final ContentCaptureSessionId mainSessionId = mainSession.getContentCaptureSessionId();
+        Log.v(TAG, "main session id: " + mainSessionId);
+
+        // Create 1st session
+        final ContentCaptureContext context1 = newContentCaptureContextBuilder("session1")
+                .build();
+        final ContentCaptureSession childSession1 = mainSession
+                .createContentCaptureSession(context1);
+        final ContentCaptureSessionId childSessionId1 = childSession1.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 1: " + childSessionId1);
+
+        // Session 1, child 1
+        final TextView s1c1 = addChild(activity, childSession1, "s1c1");
+        final AutofillId s1c1Id = s1c1.getAutofillId();
+        Log.v(TAG, "childrens from session1: " + s1c1Id);
+
+        // Create 2nd session
+        final ContentCaptureContext context2 = newContentCaptureContextBuilder("session2")
+                .build();
+        final ContentCaptureSession childSession2 = mainSession
+                .createContentCaptureSession(context2);
+        final ContentCaptureSessionId childSessionId2 = childSession2.getContentCaptureSessionId();
+        Log.v(TAG, "child session id 2: " + childSessionId2);
+
+        final TextView s2c1 = newImportantView(activity, childSession2, "s2c1");
+        final AutofillId s2c1Id = s2c1.getAutofillId();
+        final TextView s2c2 = newImportantView(activity, childSession2, "s2c2");
+        final AutofillId s2c2Id = s2c2.getAutofillId();
+        Log.v(TAG, "childrens from session2: " + s2c1Id + ", " + s2c2Id);
+
+        // Add 2 children together so they're wrapped a view_tree batch
+        activity.syncRunOnUiThread(() -> {
+            rootView.addView(s2c1);
+            rootView.addView(s2c2);
+        });
+
+        // Remove views - should generate one batch event for s2 and one single event for s1
+        waitAndRemoveViews(activity, s2c1, s2c2, s1c1);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final List<ContentCaptureSessionId> receivedIds = service.getAllSessionIds();
+        assertThat(receivedIds).containsExactly(
+                mainSessionId,
+                childSessionId1,
+                childSessionId2)
+            .inOrder();
+
+        // Assert main sessions info
+        final Session mainTestSession = service.getFinishedSession(mainSessionId);
+        assertMainSessionContext(mainTestSession, activity);
+        final List<ContentCaptureEvent> mainEvents = mainTestSession.getEvents();
+        Log.v(TAG, "mainEvents(" + mainEvents.size() + "): " + mainEvents);
+
+        // Logs events before asserting
+        final Session childTestSession1 = service.getFinishedSession(childSessionId1);
+        assertChildSessionContext(childTestSession1, "session1");
+        final List<ContentCaptureEvent> events1 = childTestSession1.getEvents();
+        Log.v(TAG, "events1(" + events1.size() + "): " + events1);
+        final Session childTestSession2 = service.getFinishedSession(childSessionId2);
+        final List<ContentCaptureEvent> events2 = childTestSession2.getEvents();
+        assertChildSessionContext(childTestSession2, "session2");
+        Log.v(TAG, "events2(" + events2.size() + "): " + events2);
+
+        // Assert children
+        assertThat(events1.size()).isAtLeast(6);
+        final AutofillId rootId = rootView.getAutofillId();
+        assertViewTreeStarted(events1, 0);
+        assertViewAppeared(events1, 1, s1c1, rootId);
+        assertViewTreeFinished(events1, 2);
+        assertViewTreeStarted(events1, 3);
+        assertViewDisappeared(events1, 4, s1c1Id);
+        assertViewTreeFinished(events1, 5);
+
+        assertThat(events2.size()).isAtLeast(7);
+        assertViewTreeStarted(events2, 0);
+        assertViewAppeared(events2, 1, s2c1, rootId);
+        assertViewAppeared(events2, 2, s2c2, rootId);
+        assertViewTreeFinished(events2, 3);
+        assertViewTreeStarted(events2, 4);
+        assertViewsDisappeared(events2, 5, s2c1Id, s2c2Id);
+        assertViewTreeFinished(events2, 6);
+    }
+
+    /* TODO(b/119638528): add more scenarios for nested sessions, such as:
+     * - add views to the children sessions
+     * - s1 -> s2 -> s3 and main -> s4; close(s1) then generate events on view from s3
+     * - s1 -> s2 -> s3 and main -> s4; close(s2) then generate events on view from s3
+     * - s1 -> s2 and s3->s4 -> s4
+     * - etc
+     */
+
+    private enum DisabledReason {
+        BY_API,
+        BY_SETTINGS,
+        BY_DEVICE_CONFIG
+    }
+
+    private void setFeatureEnabled(@NonNull CtsContentCaptureService service,
+            @NonNull DisabledReason reason,
+            boolean enabled) {
+        switch (reason) {
+            case BY_API:
+                if (enabled) {
+                    // The service cannot re-enable itself, so we use settings instead.
+                    setFeatureEnabledBySettings(true);
+                } else {
+                    service.disableSelf();
+                }
+                break;
+            case BY_SETTINGS:
+                setFeatureEnabledBySettings(enabled);
+                break;
+            case BY_DEVICE_CONFIG:
+                setFeatureEnabledByDeviceConfig(Boolean.toString(enabled));
+                break;
+            default:
+                throw new IllegalArgumentException("invalid reason: " + reason);
+        }
+    }
+
+    @Test
+    public void testIsContentCaptureFeatureEnabled_notService() throws Exception {
+        final ContentCaptureManager mgr = getContentCaptureManagerHack();
+        assertThrows(SecurityException.class,  () -> mgr.isContentCaptureFeatureEnabled());
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_disabledBySettings() throws Exception {
+        setContentCaptureFeatureEnabledTest_disabled(DisabledReason.BY_SETTINGS);
+    }
+
+    private void setContentCaptureFeatureEnabledTest_disabled(@NonNull DisabledReason reason)
+            throws Exception {
+        final ContentCaptureManager mgr = getContentCaptureManagerHack();
+
+        final CtsContentCaptureService service = enableService();
+        assertThat(mgr.isContentCaptureFeatureEnabled()).isTrue();
+        final DisconnectListener disconnectedListener = service.setOnDisconnectListener();
+
+        setFeatureEnabled(service, reason, /* enabled= */ false);
+
+        disconnectedListener.waitForOnDisconnected();
+        assertThat(mgr.isContentCaptureFeatureEnabled()).isFalse();
+        assertThat(mgr.isContentCaptureEnabled()).isFalse();
+
+        final ActivityWatcher watcher = startWatcher();
+        final ChildlessActivity activity = launchActivity();
+
+        watcher.waitFor(RESUMED);
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        assertThat(service.getAllSessionIds()).isEmpty();
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_disabledThenReEnabledBySettings()
+            throws Exception {
+        setContentCaptureFeatureEnabledTest_disabledThenReEnabled(DisabledReason.BY_SETTINGS);
+    }
+
+    private void setContentCaptureFeatureEnabledTest_disabledThenReEnabled(
+            @NonNull DisabledReason reason) throws Exception {
+        final ContentCaptureManager mgr = getContentCaptureManagerHack();
+
+        final CtsContentCaptureService service1 = enableService();
+        assertThat(mgr.isContentCaptureFeatureEnabled()).isTrue();
+        final DisconnectListener disconnectedListener = service1.setOnDisconnectListener();
+
+        setFeatureEnabled(service1, reason, /* enabled= */ false);
+        disconnectedListener.waitForOnDisconnected();
+
+        assertThat(mgr.isContentCaptureFeatureEnabled()).isFalse();
+        assertThat(mgr.isContentCaptureEnabled()).isFalse();
+
+        // Launch and finish 1st activity while it's disabled
+        final ActivityWatcher watcher1 = startWatcher();
+        final ChildlessActivity activity1 = launchActivity();
+        watcher1.waitFor(RESUMED);
+        activity1.finish();
+        watcher1.waitFor(DESTROYED);
+
+        // Re-enable feature
+        final ServiceWatcher reconnectionWatcher = CtsContentCaptureService.setServiceWatcher();
+        reconnectionWatcher.whitelistSelf();
+        setFeatureEnabled(service1, reason, /* enabled= */ true);
+        final CtsContentCaptureService service2 = reconnectionWatcher.waitOnCreate();
+        assertThat(mgr.isContentCaptureFeatureEnabled()).isTrue();
+
+        // Launch and finish 2nd activity while it's enabled
+        final ActivityLauncher<CustomViewActivity> launcher2 = new ActivityLauncher<>(
+                sContext, mActivitiesWatcher, CustomViewActivity.class);
+        final ActivityWatcher watcher2 = launcher2.getWatcher();
+        final CustomViewActivity activity2 = launcher2.launchActivity();
+        watcher2.waitFor(RESUMED);
+        activity2.finish();
+        watcher2.waitFor(DESTROYED);
+
+        assertThat(service1.getAllSessionIds()).isEmpty();
+        final Session session = service2.getOnlyFinishedSession();
+        activity2.assertDefaultEvents(session);
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_disabledByApi() throws Exception {
+        setContentCaptureFeatureEnabledTest_disabled(DisabledReason.BY_API);
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_disabledThenReEnabledByApi()
+            throws Exception {
+        setContentCaptureFeatureEnabledTest_disabledThenReEnabled(DisabledReason.BY_API);
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_disabledByDeviceConfig() throws Exception {
+        setContentCaptureFeatureEnabledTest_disabled(DisabledReason.BY_DEVICE_CONFIG);
+        // Reset service, otherwise it will reconnect when the deviceConfig value is reset
+        // on cleanup, which will cause the test to fail
+        Helper.resetService();
+    }
+
+    @Test
+    public void testSetContentCaptureFeatureEnabled_disabledThenReEnabledByDeviceConfig()
+            throws Exception {
+        setContentCaptureFeatureEnabledTest_disabledThenReEnabled(DisabledReason.BY_DEVICE_CONFIG);
+        // Reset service, otherwise it will reconnect when the deviceConfig value is reset
+        // on cleanup, which will cause the test to fail
+        Helper.resetService();
+    }
+
+    // TODO(b/123406031): add tests that mix feature_enabled with user_restriction_enabled (and
+    // make sure mgr.isContentCaptureFeatureEnabled() returns only the state of the 1st)
+
+    private TextView addChild(@NonNull ChildlessActivity activity,
+            @NonNull ContentCaptureSession session, @NonNull String text) {
+        final TextView child = newImportantView(activity, text);
+        child.setContentCaptureSession(session);
+        Log.i(TAG, "adding " + child.getAutofillId() + " on session "
+                + session.getContentCaptureSessionId());
+        activity.runOnUiThread(() -> activity.getRootView().addView(child));
+        return child;
+    }
+
+    // TODO(b/123024698): these method are used in cases where we cannot close a session because we
+    // would miss intermediate events, so we need to sleep. This is a hack (it's slow and flaky):
+    // ideally we should block and wait until the service receives the event, but right now
+    // we don't get the service events until after the activity is finished, so we cannot do that...
+    private void waitAndClose(@NonNull ContentCaptureSession session) {
+        Log.d(TAG, "sleeping before closing " + session.getContentCaptureSessionId());
+        sleep();
+        session.close();
+    }
+
+    private void waitAndRemoveViews(@NonNull ChildlessActivity activity, @NonNull View... views) {
+        Log.d(TAG, "sleeping before removing " + Arrays.toString(views));
+        sleep();
+        activity.syncRunOnUiThread(() -> {
+            for (View view : views) {
+                activity.getRootView().removeView(view);
+            }
+        });
+    }
+
+    private void sleep() {
+        Log.d(TAG, "sleeping for 1s ");
+        SystemClock.sleep(1_000);
+    }
+
+    // TODO(b/120494182): temporary hack to get the manager, which currently is only available on
+    // Activity contexts (and would be null from sContext)
+    @NonNull
+    private ContentCaptureManager getContentCaptureManagerHack() throws InterruptedException {
+        final AtomicReference<ContentCaptureManager> ref = new AtomicReference<>();
+        LoginActivity.onRootView(
+                (activity, rootView) -> ref.set(activity.getContentCaptureManager()));
+
+        final ActivityLauncher<LoginActivity> launcher = new ActivityLauncher<>(
+                sContext, mActivitiesWatcher, LoginActivity.class);
+        final ActivityWatcher watcher = launcher.getWatcher();
+        final LoginActivity activity = launcher.launchActivity();
+        watcher.waitFor(RESUMED);
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final ContentCaptureManager mgr = ref.get();
+        assertThat(mgr).isNotNull();
+
+        return mgr;
+    }
+
+    private void setFeatureEnabledByDeviceConfig(@Nullable String value) {
+        Log.d(TAG, "setFeatureEnabledByDeviceConfig(): " + value);
+
+        sKillSwitchManager.set(value);
+    }
+
+    @NonNull
+    private ContentCaptureContext.Builder newContentCaptureContextBuilder(@NonNull String id) {
+        return new ContentCaptureContext.Builder(new LocusId(id));
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
index e01837c..91f02c7 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
@@ -25,12 +25,13 @@
 import android.service.contentcapture.ActivityEvent;
 import android.service.contentcapture.ContentCaptureService;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureEvent;
 import android.view.contentcapture.ContentCaptureSessionId;
-import android.view.contentcapture.DataRemovalRequest;
+import android.view.contentcapture.UserDataRemovalRequest;
 import android.view.contentcapture.ViewNode;
 
 import androidx.annotation.NonNull;
@@ -103,9 +104,9 @@
     // but that would make the tests flaker.
 
     /**
-     * Used for testing onDataRemovalRequest.
+     * Used for testing onUserDataRemovalRequest.
      */
-    private DataRemovalRequest mRemovalRequest;
+    private UserDataRemovalRequest mRemovalRequest;
 
     /**
      * List of activity lifecycle events received.
@@ -280,8 +281,8 @@
     }
 
     @Override
-    public void onDataRemovalRequest(DataRemovalRequest request) {
-        Log.i(TAG, "onDataRemovalRequest(id=" + mId + ",req=" + request + ")");
+    public void onUserDataRemovalRequest(UserDataRemovalRequest request) {
+        Log.i(TAG, "onUserDataRemovalRequest(id=" + mId + ",req=" + request + ")");
         mRemovalRequest = request;
     }
 
@@ -292,9 +293,9 @@
     }
 
     /**
-     * Gets the cached DataRemovalRequest for testing.
+     * Gets the cached UserDataRemovalRequest for testing.
      */
-    public DataRemovalRequest getRemovalRequest() {
+    public UserDataRemovalRequest getRemovalRequest() {
         return mRemovalRequest;
     }
 
@@ -503,12 +504,21 @@
         }
 
         /**
-         * Whitelist stuff when the service connects.
+         * Whitelists stuff when the service connects.
          */
         public void whitelist(@Nullable Pair<Set<String>, Set<ComponentName>> whitelist) {
             mWhitelist = whitelist;
         }
 
+       /**
+        * Whitelists just this package.
+        */
+        public void whitelistSelf() {
+            final ArraySet<String> pkgs = new ArraySet<>(1);
+            pkgs.add(MY_PACKAGE);
+            whitelist(new Pair<>(pkgs, null));
+        }
+
         @Override
         public String toString() {
             return "mService: " + mService + " created: " + (mCreated.getCount() == 0)
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomView.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomView.java
index cdc9b27..754cef0 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomView.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomView.java
@@ -22,12 +22,12 @@
 import android.util.Log;
 import android.view.View;
 import android.view.ViewStructure;
-import android.view.contentcapture.ContentCaptureSession;
 
 import androidx.annotation.NonNull;
 
 /**
- * A view that can be used to emulate custom behavior (like virtual children)
+ * A view that can be used to emulate custom behavior (like virtual children) on
+ * {@link #onProvideContentCaptureStructure(ViewStructure, int)}.
  */
 public class CustomView extends View {
 
@@ -37,24 +37,18 @@
 
     public CustomView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        setImportantForContentCapture(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES);
     }
 
-    public void onProvideContentCaptureStructure(ViewStructure structure) {
+    @Override
+    public void onProvideContentCaptureStructure(ViewStructure structure, int flags) {
         if (mDelegate != null) {
             Log.d(TAG, "onProvideContentCaptureStructure(): delegating");
             structure.setClassName(getAccessibilityClassName().toString());
             mDelegate.visit(structure);
             Log.d(TAG, "onProvideContentCaptureStructure(): delegated");
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        if (changed) {
-            final ContentCaptureSession session = getContentCaptureSession();
-            final ViewStructure structure = session.newViewStructure(this);
-            onProvideContentCaptureStructure(structure);
-            session.notifyViewAppeared(structure);
+        } else {
+            superOnProvideContentCaptureStructure(structure, flags);
         }
     }
 
@@ -63,6 +57,11 @@
         return CustomView.class.getName();
     }
 
+    void superOnProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
+        Log.d(TAG, "calling super.onProvideContentCaptureStructure()");
+        super.onProvideContentCaptureStructure(structure, flags);
+    }
+
     void setContentCaptureDelegate(@NonNull Visitor<ViewStructure> delegate) {
         mDelegate = delegate;
     }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivity.java
index 7c38342..fb1a141 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivity.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivity.java
@@ -15,9 +15,13 @@
  */
 package android.contentcaptureservice.cts;
 
+import static android.contentcaptureservice.cts.Assertions.assertDecorViewAppeared;
 import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
 import static android.contentcaptureservice.cts.Assertions.assertSessionPaused;
 import static android.contentcaptureservice.cts.Assertions.assertSessionResumed;
+import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
 import static android.contentcaptureservice.cts.Assertions.assertViewWithUnknownParentAppeared;
 import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
 
@@ -47,13 +51,13 @@
      * <p>Used on {@link #assertInitialViewsAppeared(Session, int)} and
      * {@link #assertInitialViewsDisappeared(List, int)}.
      */
-    public static final int MIN_EVENTS = 2;
+    public static final int MIN_EVENTS = 7;
 
     CustomView mCustomView;
 
     /**
      * Sets a delegate that provides the behavior of
-     * {@link CustomView#onProvideContentCaptureStructure(ViewStructure)}.
+     * {@link CustomView#onProvideContentCaptureStructure(ViewStructure, int)}.
      */
     static void setCustomViewDelegate(@NonNull DoubleVisitor<CustomView, ViewStructure> delegate) {
         sCustomViewDelegate = delegate;
@@ -112,7 +116,12 @@
 
         // Assert just the relevant events
         assertSessionResumed(events, 0);
-        assertViewWithUnknownParentAppeared(events, 1, session.id, mCustomView);
+        assertViewTreeStarted(events, 1);
+        assertDecorViewAppeared(events, 2, getDecorView());
+        assertViewAppeared(events, 3, grandpa2, decorView.getAutofillId());
+        assertViewAppeared(events, 4, grandpa1, grandpa2.getAutofillId());
+        assertViewWithUnknownParentAppeared(events, 5, session.id, mCustomView);
+        assertViewTreeFinished(events, 6);
 
         return events;
     }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
index d725850..ee98060 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
@@ -15,10 +15,14 @@
  */
 package android.contentcaptureservice.cts;
 
+import static android.contentcaptureservice.cts.Assertions.assertDecorViewAppeared;
 import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
 import static android.contentcaptureservice.cts.Assertions.assertSessionPaused;
 import static android.contentcaptureservice.cts.Assertions.assertSessionResumed;
+import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
 import static android.contentcaptureservice.cts.Assertions.assertViewTextChanged;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
 import static android.contentcaptureservice.cts.Assertions.assertViewWithUnknownParentAppeared;
 import static android.contentcaptureservice.cts.Assertions.assertVirtualViewAppeared;
 import static android.contentcaptureservice.cts.Assertions.assertVirtualViewDisappeared;
@@ -152,15 +156,20 @@
 
         // Assert just the relevant events
         assertSessionResumed(events, 0);
+        assertViewTreeStarted(events, 1);
+        assertDecorViewAppeared(events, 2, decorView);
+        assertViewAppeared(events, 3, grandpa2, decorView.getAutofillId());
+        assertViewAppeared(events, 4, grandpa1, grandpa2.getAutofillId());
 
         final ContentCaptureSession mainSession = activity.mCustomView.getContentCaptureSession();
-        assertVirtualViewAppeared(events, 1, mainSession, customViewId, 1, "child");
-        assertVirtualViewDisappeared(events, 2, customViewId, mainSession, 1);
+        assertVirtualViewAppeared(events, 5, mainSession, customViewId, 1, "child");
+        assertVirtualViewDisappeared(events, 6, customViewId, mainSession, 1);
 
         // This is the "wrong" part - the parent is notified last
-        assertViewWithUnknownParentAppeared(events, 3, session.id, activity.mCustomView);
+        assertViewWithUnknownParentAppeared(events, 7, session.id, activity.mCustomView);
 
-        assertSessionPaused(events, 4);
+        assertViewTreeFinished(events, 8);
+        assertSessionPaused(events, 9);
 
         activity.assertInitialViewsDisappeared(events, additionalEvents);
     }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java
index a9596b6..8c26294 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java
@@ -23,6 +23,7 @@
 import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.Log;
+import android.view.View;
 import android.view.contentcapture.ContentCaptureSession;
 import android.widget.TextView;
 
@@ -115,6 +116,7 @@
     public static TextView newImportantView(@NonNull Context context, @NonNull String text) {
         final TextView child = new TextView(context);
         child.setText(text);
+        child.setImportantForContentCapture(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES);
         Log.v(TAG, "newImportantView(text=" + text + ", id=" + child.getAutofillId() + ")");
         return child;
     }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
new file mode 100644
index 0000000..3747e2b
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2018 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 android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertDecorViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
+import static android.contentcaptureservice.cts.Assertions.assertSessionId;
+import static android.contentcaptureservice.cts.Assertions.assertSessionResumed;
+import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
+import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSessionId;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+public class LoginActivity extends AbstractRootViewActivity {
+
+    private static final String TAG = LoginActivity.class.getSimpleName();
+
+    /**
+     * Mininum number of events generated when the activity starts.
+     *
+     * <p>Used on {@link #assertInitialViewsAppeared(Session, int)} and
+     * {@link #assertInitialViewsDisappeared(List, int)}.
+     */
+    public static final int MIN_EVENTS = 11;
+
+    TextView mUsernameLabel;
+    EditText mUsername;
+    TextView mPasswordLabel;
+    EditText mPassword;
+
+    @Override
+    protected void setContentViewOnCreate(Bundle savedInstanceState) {
+        setContentView(R.layout.login_activity);
+
+        mUsernameLabel = findViewById(R.id.username_label);
+        mUsername = findViewById(R.id.username);
+        mPasswordLabel = findViewById(R.id.password_label);
+        mPassword = findViewById(R.id.password);
+    }
+
+    @Override
+    public void assertDefaultEvents(@NonNull Session session) {
+        final int additionalEvents = 0;
+        final List<ContentCaptureEvent> events = assertInitialViewsAppeared(session,
+                additionalEvents);
+        assertInitialViewsDisappeared(events, additionalEvents);
+    }
+
+    /**
+     * Asserts the events generated when this activity was launched, up to the
+     * {@code TYPE_INITIAL_VIEW_HIERARCHY_FINISHED} event.
+     */
+    @NonNull
+    public List<ContentCaptureEvent> assertInitialViewsAppeared(@NonNull Session session,
+            int additionalEvents) {
+        final List<ContentCaptureEvent> events = assertJustInitialViewsAppeared(session,
+                additionalEvents);
+        assertViewTreeFinished(events, MIN_EVENTS - 1);
+
+        return events;
+    }
+
+    /**
+     * Asserts the events generated when this activity was launched, but without the
+     * {@code TYPE_INITIAL_VIEW_HIERARCHY_FINISHED} event.
+     */
+    @NonNull
+    public List<ContentCaptureEvent> assertJustInitialViewsAppeared(@NonNull Session session,
+            int additionalEvents) {
+        final LoginActivity activity = this;
+        final ContentCaptureSessionId sessionId = session.id;
+        assertRightActivity(session, sessionId, activity);
+
+        // Sanity check
+        assertSessionId(sessionId, activity.mUsernameLabel);
+        assertSessionId(sessionId, activity.mUsername);
+        assertSessionId(sessionId, activity.mPassword);
+        assertSessionId(sessionId, activity.mPasswordLabel);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events(" + events.size() + "): " + events);
+        // TODO(b/123540067): ideally it should be X so it reflects just the views defined
+        // in the layout - right now it's generating events for 2 intermediate parents
+        // (android:action_mode_bar_stub and android:content), we should try to create an
+        // activity without them
+
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+
+        assertThat(events.size()).isAtLeast(MIN_EVENTS + additionalEvents);
+
+        // TODO(b/123540067): get rid of those intermediated parents
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+        final View decorView = activity.getDecorView();
+        final View rootView = activity.getRootView();
+
+        assertSessionResumed(events, 0);
+        assertViewTreeStarted(events, 1);
+        assertDecorViewAppeared(events, 2, decorView);
+        assertViewAppeared(events, 3, grandpa2, decorView.getAutofillId());
+        assertViewAppeared(events, 4, grandpa1, grandpa2.getAutofillId());
+        assertViewAppeared(events, 5, sessionId, rootView, grandpa1.getAutofillId());
+        assertViewAppeared(events, 6, sessionId, activity.mUsernameLabel, rootId);
+        assertViewAppeared(events, 7, sessionId, activity.mUsername, rootId);
+        assertViewAppeared(events, 8, sessionId, activity.mPasswordLabel, rootId);
+        assertViewAppeared(events, 9, sessionId, activity.mPassword, rootId);
+
+        return events;
+    }
+
+    /**
+     * Asserts the initial views disappeared after the activity was finished.
+     */
+    public void assertInitialViewsDisappeared(@NonNull List<ContentCaptureEvent> events,
+            int additionalEvents) {
+        // TODO(b/122315042): this method is currently a mess, so let's disable for now and properly
+        // fix these assertions later...
+        if (true) return;
+
+        final LoginActivity activity = this;
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        final View decorView = activity.getDecorView();
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+
+        // Besides the additional events from the test case, we also need to account for the
+        final int i = MIN_EVENTS + additionalEvents;
+
+        assertViewTreeStarted(events, i);
+
+        // TODO(b/122315042): sometimes we get decor view disappareared events, sometimes we don't
+        // As we don't really care about those, let's fix it!
+        try {
+            assertViewsOptionallyDisappeared(events, i + 1,
+                    rootId,
+                    grandpa1.getAutofillId(), grandpa2.getAutofillId(),
+                    activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+                    activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId());
+        } catch (AssertionError e) {
+            Log.e(TAG, "Hack-ignoring assertion without decor view: " + e);
+            // Try again removing it...
+            assertViewsOptionallyDisappeared(events, i + 1,
+                    rootId,
+                    grandpa1.getAutofillId(), grandpa2.getAutofillId(),
+                    decorView.getAutofillId(),
+                    activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+                    activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId());
+        }
+
+        assertViewTreeFinished(events, i + 2);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        Log.d(TAG, "AutofillIds: " + "usernameLabel=" + mUsernameLabel.getAutofillId()
+                + ", username=" + mUsername.getAutofillId()
+                + ", passwordLabel=" + mPasswordLabel.getAutofillId()
+                + ", password=" + mPassword.getAutofillId());
+    }
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
new file mode 100644
index 0000000..ab50586
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -0,0 +1,781 @@
+/*
+ * Copyright (C) 2018 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 android.contentcaptureservice.cts;
+
+import static android.contentcaptureservice.cts.Assertions.assertChildSessionContext;
+import static android.contentcaptureservice.cts.Assertions.assertContextUpdated;
+import static android.contentcaptureservice.cts.Assertions.assertDecorViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertMainSessionContext;
+import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
+import static android.contentcaptureservice.cts.Assertions.assertRightRelationship;
+import static android.contentcaptureservice.cts.Assertions.assertSessionId;
+import static android.contentcaptureservice.cts.Assertions.assertSessionPaused;
+import static android.contentcaptureservice.cts.Assertions.assertSessionResumed;
+import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
+import static android.contentcaptureservice.cts.Assertions.assertViewTextChanged;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
+import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
+import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
+import static android.contentcaptureservice.cts.Helper.MY_PACKAGE;
+import static android.contentcaptureservice.cts.Helper.newImportantView;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+import static android.view.contentcapture.UserDataRemovalRequest.FLAG_IS_PREFIX;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.content.LocusId;
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.contentcaptureservice.cts.common.ActivitiesWatcher.ActivityWatcher;
+import android.contentcaptureservice.cts.common.DoubleVisitor;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSession;
+import android.view.contentcapture.ContentCaptureSessionId;
+import android.view.contentcapture.UserDataRemovalRequest;
+import android.view.contentcapture.UserDataRemovalRequest.LocusIdRequest;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+@AppModeFull(reason = "BlankWithTitleActivityTest is enough")
+public class LoginActivityTest
+        extends AbstractContentCaptureIntegrationAutoActivityLaunchTest<LoginActivity> {
+
+    private static final String TAG = LoginActivityTest.class.getSimpleName();
+
+    private static final int NO_FLAGS = 0;
+
+    private static final ActivityTestRule<LoginActivity> sActivityRule = new ActivityTestRule<>(
+            LoginActivity.class, false, false);
+
+    public LoginActivityTest() {
+        super(LoginActivity.class);
+    }
+
+    @Override
+    protected ActivityTestRule<LoginActivity> getActivityTestRule() {
+        return sActivityRule;
+    }
+
+    @Before
+    @After
+    public void resetActivityStaticState() {
+        LoginActivity.onRootView(null);
+    }
+
+    @Test
+    public void testSimpleLifecycle_defaultSession() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        activity.assertDefaultEvents(session);
+
+        final ComponentName name = activity.getComponentName();
+        service.assertThat()
+                .activityResumed(name)
+                .activityPaused(name);
+    }
+
+    @Test
+    public void testSimpleLifecycle_rootViewSession() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ContentCaptureContext clientContext = newContentCaptureContext();
+
+        final AtomicReference<ContentCaptureSession> mainSessionRef = new AtomicReference<>();
+        final AtomicReference<ContentCaptureSession> childSessionRef = new AtomicReference<>();
+
+        LoginActivity.onRootView((activity, rootView) -> {
+            final ContentCaptureSession mainSession = rootView.getContentCaptureSession();
+            mainSessionRef.set(mainSession);
+            final ContentCaptureSession childSession = mainSession
+                    .createContentCaptureSession(clientContext);
+            childSessionRef.set(childSession);
+            Log.i(TAG, "Setting root view (" + rootView + ") session to " + childSession);
+            rootView.setContentCaptureSession(childSession);
+        });
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final ContentCaptureSessionId mainSessionId = mainSessionRef.get()
+                .getContentCaptureSessionId();
+        final ContentCaptureSessionId childSessionId = childSessionRef.get()
+                .getContentCaptureSessionId();
+        Log.v(TAG, "session ids: main=" + mainSessionId + ", child=" + childSessionId);
+
+        // Sanity checks
+        assertSessionId(childSessionId, activity.getRootView());
+        assertSessionId(childSessionId, activity.mUsernameLabel);
+        assertSessionId(childSessionId, activity.mUsername);
+        assertSessionId(childSessionId, activity.mPassword);
+        assertSessionId(childSessionId, activity.mPasswordLabel);
+
+        // Get the sessions
+        final Session mainSession = service.getFinishedSession(mainSessionId);
+        final Session childSession = service.getFinishedSession(childSessionId);
+
+        assertRightActivity(mainSession, mainSessionId, activity);
+        assertRightRelationship(mainSession, childSession);
+
+        // Sanity check
+        final List<ContentCaptureSessionId> allSessionIds = service.getAllSessionIds();
+        assertThat(allSessionIds).containsExactly(mainSessionId, childSessionId);
+
+        /*
+         * Asserts main session
+         */
+
+        // Checks context
+        assertMainSessionContext(mainSession, activity);
+
+        // Check events
+        final List<ContentCaptureEvent> mainEvents = mainSession.getEvents();
+        Log.v(TAG, "events(" + mainEvents.size() + ") for main session: " + mainEvents);
+
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+        final View decorView = activity.getDecorView();
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+
+        final int minEvents = 7; // TODO(b/122315042): disappeared not always sent
+        assertThat(mainEvents.size()).isAtLeast(minEvents);
+        assertSessionResumed(mainEvents, 0);
+        assertViewTreeStarted(mainEvents, 1);
+        assertDecorViewAppeared(mainEvents, 2, decorView);
+        assertViewAppeared(mainEvents, 3, grandpa2, decorView.getAutofillId());
+        assertViewAppeared(mainEvents, 4, grandpa1, grandpa2.getAutofillId());
+        assertViewTreeFinished(mainEvents, 5);
+        // TODO(b/122315042): these assertions are currently a mess, so let's disable for now and
+        // properly fix them later...
+        if (false) {
+            int pausedIndex = 6;
+            final boolean disappeared = assertViewsOptionallyDisappeared(mainEvents, pausedIndex,
+                    decorView.getAutofillId(),
+                    grandpa2.getAutofillId(), grandpa1.getAutofillId());
+            if (disappeared) {
+                pausedIndex += 3;
+            }
+            assertSessionPaused(mainEvents, pausedIndex);
+        }
+
+        /*
+         * Asserts child session
+         */
+
+        // Checks context
+        assertChildSessionContext(childSession, "file://dev/null");
+
+        assertContentCaptureContext(childSession.context);
+
+        // Check events
+        final List<ContentCaptureEvent> childEvents = childSession.getEvents();
+        Log.v(TAG, "events for child session: " + childEvents);
+        final int minChildEvents = 5;
+        assertThat(childEvents.size()).isAtLeast(minChildEvents);
+        assertViewAppeared(childEvents, 0, childSessionId, activity.getRootView(),
+                grandpa1.getAutofillId());
+        assertViewAppeared(childEvents, 1, childSessionId, activity.mUsernameLabel, rootId);
+        assertViewAppeared(childEvents, 2, childSessionId, activity.mUsername, rootId);
+        assertViewAppeared(childEvents, 3, childSessionId, activity.mPasswordLabel, rootId);
+        assertViewAppeared(childEvents, 4, childSessionId, activity.mPassword, rootId);
+
+        assertViewsOptionallyDisappeared(childEvents, minChildEvents,
+                rootId,
+                activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+                activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId());
+    }
+
+    @Test
+    public void testSimpleLifecycle_changeContextAfterCreate() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        final ContentCaptureContext newContext1 = newContentCaptureContext();
+        final ContentCaptureContext newContext2 = null;
+
+        final View rootView = activity.getRootView();
+        final ContentCaptureSession mainSession = rootView.getContentCaptureSession();
+        assertThat(mainSession).isNotNull();
+        Log.i(TAG, "Updating root view (" + rootView + ") context to " + newContext1);
+        mainSession.setContentCaptureContext(newContext1);
+        assertContentCaptureContext(mainSession.getContentCaptureContext());
+
+        Log.i(TAG, "Updating root view (" + rootView + ") context to " + newContext2);
+        mainSession.setContentCaptureContext(newContext2);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        final int additionalEvents = 2;
+        final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
+                additionalEvents);
+
+        final ContentCaptureEvent event1 = assertContextUpdated(events, LoginActivity.MIN_EVENTS);
+        final ContentCaptureContext actualContext = event1.getContentCaptureContext();
+        assertContentCaptureContext(actualContext);
+
+        final ContentCaptureEvent event2 = assertContextUpdated(events,
+                LoginActivity.MIN_EVENTS + 1);
+        assertThat(event2.getContentCaptureContext()).isNull();
+    }
+
+    @Test
+    public void testSimpleLifecycle_changeContextOnCreate() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ContentCaptureContext newContext = newContentCaptureContext();
+
+        LoginActivity.onRootView((activity, rootView) -> {
+            final ContentCaptureSession mainSession = rootView.getContentCaptureSession();
+            Log.i(TAG, "Setting root view (" + rootView + ") context to " + newContext);
+            mainSession.setContentCaptureContext(newContext);
+            assertContentCaptureContext(mainSession.getContentCaptureContext());
+        });
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+        final ContentCaptureSessionId sessionId = session.id;
+        assertRightActivity(session, sessionId, activity);
+
+        // Sanity check
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events(" + events.size() + "): " + events);
+        // TODO(b/123540067): ideally it should be X so it reflects just the views defined
+        // in the layout - right now it's generating events for 2 intermediate parents
+        // (android:action_mode_bar_stub and android:content), we should try to create an
+        // activity without them
+
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+
+        assertThat(events.size()).isAtLeast(11);
+
+        // TODO(b/123540067): get rid of those intermediated parents
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+        final View decorView = activity.getDecorView();
+        final View rootView = activity.getRootView();
+
+        final ContentCaptureEvent ctxUpdatedEvent = assertContextUpdated(events, 0);
+        final ContentCaptureContext actualContext = ctxUpdatedEvent.getContentCaptureContext();
+        assertContentCaptureContext(actualContext);
+
+        assertSessionResumed(events, 1);
+        assertViewTreeStarted(events, 2);
+        assertDecorViewAppeared(events, 3, decorView);
+        assertViewAppeared(events, 4, grandpa2, decorView.getAutofillId());
+        assertViewAppeared(events, 5, grandpa1, grandpa2.getAutofillId());
+        assertViewAppeared(events, 6, sessionId, rootView, grandpa1.getAutofillId());
+        assertViewAppeared(events, 7, sessionId, activity.mUsernameLabel, rootId);
+        assertViewAppeared(events, 8, sessionId, activity.mUsername, rootId);
+        assertViewAppeared(events, 9, sessionId, activity.mPasswordLabel, rootId);
+        assertViewAppeared(events, 10, sessionId, activity.mPassword, rootId);
+    }
+
+    @Test
+    public void testTextChanged() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> ((LoginActivity) activity).mUsername
+                .setText("user"));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.syncRunOnUiThread(() -> {
+            activity.mUsername.setText("USER");
+            activity.mPassword.setText("PASS");
+        });
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        final ContentCaptureSessionId sessionId = session.id;
+
+        assertRightActivity(session, sessionId, activity);
+
+        final int additionalEvents = 2;
+        final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
+                additionalEvents);
+
+        final int i = LoginActivity.MIN_EVENTS;
+
+        assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "USER");
+        assertViewTextChanged(events, i + 1, activity.mPassword.getAutofillId(), "PASS");
+
+        activity.assertInitialViewsDisappeared(events, additionalEvents);
+    }
+
+    @Test
+    public void testTextChangeBuffer() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> ((LoginActivity) activity).mUsername
+                .setText(""));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.syncRunOnUiThread(() -> {
+            activity.mUsername.setText("a");
+            activity.mUsername.setText("ab");
+
+            activity.mPassword.setText("d");
+            activity.mPassword.setText("de");
+
+            activity.mUsername.setText("abc");
+        });
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        final ContentCaptureSessionId sessionId = session.id;
+
+        assertRightActivity(session, sessionId, activity);
+
+        final int additionalEvents = 3;
+        final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
+                additionalEvents);
+
+        final int i = LoginActivity.MIN_EVENTS;
+
+        assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "ab");
+        assertViewTextChanged(events, i + 1, activity.mPassword.getAutofillId(), "de");
+        assertViewTextChanged(events, i + 2, activity.mUsername.getAutofillId(), "abc");
+
+        activity.assertInitialViewsDisappeared(events, additionalEvents);
+    }
+
+    @Test
+    public void testDisabledByFlagSecure() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getWindow()
+                .addFlags(WindowManager.LayoutParams.FLAG_SECURE));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        assertThat((session.context.getFlags()
+                & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0).isTrue();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        assertThat(events).isEmpty();
+    }
+
+    @Test
+    public void testDisabledByApp() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager()
+                .setContentCaptureEnabled(false));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        assertThat(activity.getContentCaptureManager().isContentCaptureEnabled()).isFalse();
+
+        activity.syncRunOnUiThread(() -> activity.mUsername.setText("D'OH"));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        assertThat((session.context.getFlags()
+                & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0).isTrue();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        assertThat(events).isEmpty();
+    }
+
+    @Test
+    public void testDisabledFlagSecureAndByApp() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> {
+            activity.getContentCaptureManager().setContentCaptureEnabled(false);
+            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
+        });
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        assertThat(activity.getContentCaptureManager().isContentCaptureEnabled()).isFalse();
+        activity.syncRunOnUiThread(() -> activity.mUsername.setText("D'OH"));
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        assertThat((session.context.getFlags()
+                & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0).isTrue();
+        assertThat((session.context.getFlags()
+                & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0).isTrue();
+        final ContentCaptureSessionId sessionId = session.id;
+        Log.v(TAG, "session id: " + sessionId);
+
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        assertThat(events).isEmpty();
+    }
+
+    @Test
+    public void testUserDataRemovalRequest_forEverything() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager()
+                .removeUserData(new UserDataRemovalRequest.Builder().forEverything()
+                        .build()));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        UserDataRemovalRequest request = service.getRemovalRequest();
+        assertThat(request).isNotNull();
+        assertThat(request.isForEverything()).isTrue();
+        assertThat(request.getLocusIdRequests()).isNull();
+        assertThat(request.getPackageName()).isEqualTo(MY_PACKAGE);
+    }
+
+    @Test
+    public void testUserDataRemovalRequest_oneId() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final LocusId locusId = new LocusId("com.example");
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager()
+                .removeUserData(new UserDataRemovalRequest.Builder()
+                        .addLocusId(locusId, NO_FLAGS)
+                        .build()));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        UserDataRemovalRequest request = service.getRemovalRequest();
+        assertThat(request).isNotNull();
+        assertThat(request.isForEverything()).isFalse();
+        assertThat(request.getPackageName()).isEqualTo(MY_PACKAGE);
+
+        final List<LocusIdRequest> requests = request.getLocusIdRequests();
+        assertThat(requests.size()).isEqualTo(1);
+
+        final LocusIdRequest actualRequest = requests.get(0);
+        assertThat(actualRequest.getLocusId()).isEqualTo(locusId);
+        assertThat(actualRequest.getFlags()).isEqualTo(NO_FLAGS);
+    }
+
+    @Test
+    public void testUserDataRemovalRequest_manyIds() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final LocusId locusId1 = new LocusId("com.example");
+        final LocusId locusId2 = new LocusId("com.example2");
+
+        LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager()
+                .removeUserData(new UserDataRemovalRequest.Builder()
+                        .addLocusId(locusId1, NO_FLAGS)
+                        .addLocusId(locusId2, FLAG_IS_PREFIX)
+                        .build()));
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final UserDataRemovalRequest request = service.getRemovalRequest();
+        assertThat(request).isNotNull();
+        assertThat(request.isForEverything()).isFalse();
+        assertThat(request.getPackageName()).isEqualTo(MY_PACKAGE);
+
+        final List<LocusIdRequest> requests = request.getLocusIdRequests();
+        assertThat(requests.size()).isEqualTo(2);
+
+        final LocusIdRequest actualRequest1 = requests.get(0);
+        assertThat(actualRequest1.getLocusId()).isEqualTo(locusId1);
+        assertThat(actualRequest1.getFlags()).isEqualTo(NO_FLAGS);
+
+        final LocusIdRequest actualRequest2 = requests.get(1);
+        assertThat(actualRequest2.getLocusId()).isEqualTo(locusId2);
+        assertThat(actualRequest2.getFlags()).isEqualTo(FLAG_IS_PREFIX);
+    }
+
+    @Test
+    public void testAddChildren_rightAway() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+        final View[] children = new View[2];
+
+        final DoubleVisitor<AbstractRootViewActivity, LinearLayout> visitor = (activity,
+                rootView) -> {
+            final TextView child1 = newImportantView(activity, "c1");
+            children[0] = child1;
+            Log.v(TAG, "Adding child1(" + child1.getAutofillId() + "): " + child1);
+            rootView.addView(child1);
+            final TextView child2 = newImportantView(activity, "c1");
+            children[1] = child2;
+            Log.v(TAG, "Adding child2(" + child2.getAutofillId() + "): " + child2);
+            rootView.addView(child2);
+        };
+        LoginActivity.onRootView(visitor);
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        final ContentCaptureSessionId sessionId = session.id;
+        assertRightActivity(session, sessionId, activity);
+
+        final List<ContentCaptureEvent> events = activity.assertJustInitialViewsAppeared(session,
+                /* additionalEvents= */ 2);
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        int i = LoginActivity.MIN_EVENTS - 1;
+        assertViewAppeared(events, i, sessionId, children[0], rootId);
+        assertViewAppeared(events, i + 1, sessionId, children[1], rootId);
+        assertViewTreeFinished(events, i + 2);
+
+        activity.assertInitialViewsDisappeared(events, children.length);
+    }
+
+    @Test
+    public void testAddChildren_afterAnimation() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+        final View[] children = new View[2];
+
+        final DoubleVisitor<AbstractRootViewActivity, LinearLayout> visitor = (activity,
+                rootView) -> {
+            final TextView child1 = newImportantView(activity, "c1");
+            children[0] = child1;
+            Log.v(TAG, "Adding child1(" + child1.getAutofillId() + "): " + child1);
+            rootView.addView(child1);
+            final TextView child2 = newImportantView(activity, "c1");
+            children[1] = child2;
+            Log.v(TAG, "Adding child2(" + child2.getAutofillId() + "): " + child2);
+            rootView.addView(child2);
+        };
+        LoginActivity.onAnimationComplete(visitor);
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        final ContentCaptureSessionId sessionId = session.id;
+        assertRightActivity(session, sessionId, activity);
+        final int additionalEvents = 2; // 2 children views
+        final List<ContentCaptureEvent> events = activity.assertJustInitialViewsAppeared(session,
+                additionalEvents);
+        assertThat(events.size()).isAtLeast(LoginActivity.MIN_EVENTS + 5);
+        final View decorView = activity.getDecorView();
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        int i = LoginActivity.MIN_EVENTS - 1;
+
+        assertViewTreeFinished(events, i);
+        assertViewTreeStarted(events, i + 1);
+        assertViewAppeared(events, i + 2, sessionId, children[0], rootId);
+        assertViewAppeared(events, i + 3, sessionId, children[1], rootId);
+        assertViewTreeFinished(events, i + 4);
+
+        // TODO(b/122315042): assert parents disappeared
+        if (true) return;
+
+        // TODO(b/122315042): sometimes we get decor view disappareared events, sometimes we don't
+        // As we don't really care about those, let's fix it!
+        try {
+            assertViewsOptionallyDisappeared(events, LoginActivity.MIN_EVENTS + additionalEvents,
+                    rootId,
+                    grandpa1.getAutofillId(), grandpa2.getAutofillId(),
+                    activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+                    activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId(),
+                    children[0].getAutofillId(), children[1].getAutofillId());
+        } catch (AssertionError e) {
+            Log.e(TAG, "Hack-ignoring assertion without decor view: " + e);
+            // Try again removing it...
+            assertViewsOptionallyDisappeared(events, LoginActivity.MIN_EVENTS + additionalEvents,
+                    rootId,
+                    grandpa1.getAutofillId(), grandpa2.getAutofillId(),
+                    decorView.getAutofillId(),
+                    activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+                    activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId(),
+                    children[0].getAutofillId(), children[1].getAutofillId());
+
+        }
+    }
+
+    @Test
+    public void testWhitelist_packageNotWhitelisted() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        service.setContentCaptureWhitelist((Set) null, (Set) null);
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        assertThat(service.getAllSessionIds()).isEmpty();
+    }
+
+    @Test
+    public void testWhitelist_activityNotWhitelisted() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ArraySet<ComponentName> components = new ArraySet<>();
+        components.add(new ComponentName(MY_PACKAGE, "some.activity"));
+        service.setContentCaptureWhitelist(null, components);
+        final ActivityWatcher watcher = startWatcher();
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        assertThat(service.getAllSessionIds()).isEmpty();
+    }
+
+    /**
+     * Creates a context that can be assert by
+     * {@link #assertContentCaptureContext(ContentCaptureContext)}.
+     */
+    private ContentCaptureContext newContentCaptureContext() {
+        final String id = "file://dev/null";
+        final Bundle bundle = new Bundle();
+        bundle.putString("DUDE", "SWEET");
+        return new ContentCaptureContext.Builder(new LocusId(id)).setExtras(bundle).build();
+    }
+
+    /**
+     * Asserts a context that can has been created by {@link #newContentCaptureContext()}.
+     */
+    private void assertContentCaptureContext(@NonNull ContentCaptureContext context) {
+        assertWithMessage("null context").that(context).isNotNull();
+        assertWithMessage("wrong ID on context %s", context).that(context.getLocusId().getId())
+                .isEqualTo("file://dev/null");
+        final Bundle extras = context.getExtras();
+        assertWithMessage("no extras on context %s", context).that(extras).isNotNull();
+        assertWithMessage("wrong number of extras on context %s", context).that(extras.size())
+                .isEqualTo(1);
+        assertWithMessage("wrong extras on context %s", context).that(extras.getString("DUDE"))
+                .isEqualTo("SWEET");
+    }
+
+    // TODO(b/123540602): add moar test cases for different sessions:
+    // - session1 on rootView, session2 on children
+    // - session1 on rootView, session2 on child1, session3 on child2
+    // - combination above where the CTS test explicitly finishes a session
+
+    // TODO(b/123540602): add moar test cases for different scenarios, like:
+    // - dynamically adding /
+    // - removing views
+    // - pausing / resuming activity / tapping home
+    // - changing text
+    // - secure flag with child sessions
+    // - making sure events are flushed when activity pause / resume
+
+    // TODO(b/126262658): moar lifecycle events, like multiple activities.
+
+}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/DataRemovalRequestTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/UserDataRemovalRequestTest.java
similarity index 80%
rename from tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/DataRemovalRequestTest.java
rename to tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/UserDataRemovalRequestTest.java
index 4041405..b3ad57a 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/DataRemovalRequestTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/UserDataRemovalRequestTest.java
@@ -15,7 +15,7 @@
  */
 package android.contentcaptureservice.cts.unit;
 
-import static android.view.contentcapture.DataRemovalRequest.FLAG_IS_PREFIX;
+import static android.view.contentcapture.UserDataRemovalRequest.FLAG_IS_PREFIX;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -25,8 +25,8 @@
 import android.content.LocusId;
 import android.os.Parcel;
 import android.platform.test.annotations.AppModeFull;
-import android.view.contentcapture.DataRemovalRequest;
-import android.view.contentcapture.DataRemovalRequest.LocusIdRequest;
+import android.view.contentcapture.UserDataRemovalRequest;
+import android.view.contentcapture.UserDataRemovalRequest.LocusIdRequest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -36,13 +36,13 @@
 
 @AppModeFull(reason = "unit test")
 @RunWith(MockitoJUnitRunner.class)
-public class DataRemovalRequestTest {
+public class UserDataRemovalRequestTest {
 
     private static final int NO_FLAGS = 0;
 
     private final LocusId mLocusId = new LocusId("content://com.example/");
 
-    private DataRemovalRequest.Builder mBuilder = new DataRemovalRequest.Builder();
+    private UserDataRemovalRequest.Builder mBuilder = new UserDataRemovalRequest.Builder();
 
     @Test
     public void testBuilder_addLocusId_invalid() {
@@ -76,20 +76,20 @@
 
     @Test
     public void testBuild_validForEverything_directly() {
-        final DataRemovalRequest request = new DataRemovalRequest.Builder().forEverything()
+        final UserDataRemovalRequest request = new UserDataRemovalRequest.Builder().forEverything()
                 .build();
         assertForEverything(request);
     }
 
     @Test
     public void testBuild_validForEverything_parcel() {
-        final DataRemovalRequest request = new DataRemovalRequest.Builder().forEverything()
+        final UserDataRemovalRequest request = new UserDataRemovalRequest.Builder().forEverything()
                 .build();
-        final DataRemovalRequest clone = cloneThroughParcel(request);
+        final UserDataRemovalRequest clone = cloneThroughParcel(request);
         assertForEverything(clone);
     }
 
-    private void assertForEverything(final DataRemovalRequest request) {
+    private void assertForEverything(final UserDataRemovalRequest request) {
         assertThat(request).isNotNull();
         assertThat(request.isForEverything()).isTrue();
         assertThat(request.getLocusIdRequests()).isNull();
@@ -97,26 +97,26 @@
 
     @Test
     public void testBuild_validForIds_directly() {
-        final DataRemovalRequest request = buildForIds();
+        final UserDataRemovalRequest request = buildForIds();
         assertForIds(request);
     }
 
     @Test
     public void testBuild_validForIds_parcel() {
-        final DataRemovalRequest request = buildForIds();
-        final DataRemovalRequest clone = cloneThroughParcel(request);
+        final UserDataRemovalRequest request = buildForIds();
+        final UserDataRemovalRequest clone = cloneThroughParcel(request);
         assertForIds(clone);
     }
 
-    private DataRemovalRequest buildForIds() {
-        final DataRemovalRequest.Builder builder = new DataRemovalRequest.Builder();
+    private UserDataRemovalRequest buildForIds() {
+        final UserDataRemovalRequest.Builder builder = new UserDataRemovalRequest.Builder();
         assertThat(builder.addLocusId(new LocusId("prefix1True"), FLAG_IS_PREFIX)).isNotNull();
         assertThat(builder.addLocusId(new LocusId("prefix2False"), NO_FLAGS)).isNotNull();
 
         return builder.build();
     }
 
-    private void assertForIds(DataRemovalRequest request) {
+    private void assertForIds(UserDataRemovalRequest request) {
         assertThat(request).isNotNull();
         assertThat(request.isForEverything()).isFalse();
         final List<LocusIdRequest> requests = request.getLocusIdRequests();
@@ -149,7 +149,7 @@
                 .that(request.getFlags()).isEqualTo(expectedFlags);
     }
 
-    private DataRemovalRequest cloneThroughParcel(DataRemovalRequest request) {
+    private UserDataRemovalRequest cloneThroughParcel(UserDataRemovalRequest request) {
         final Parcel parcel = Parcel.obtain();
 
         try {
@@ -159,7 +159,7 @@
 
             // Read from parcel
             parcel.setDataPosition(0);
-            final DataRemovalRequest clone = DataRemovalRequest.CREATOR
+            final UserDataRemovalRequest clone = UserDataRemovalRequest.CREATOR
                     .createFromParcel(parcel);
             assertThat(clone).isNotNull();
             return clone;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java
index c83cb2e..87039b3 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java
@@ -29,9 +29,7 @@
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
 
 import android.content.ComponentName;
 import android.platform.test.annotations.Presubmit;
@@ -40,9 +38,6 @@
 
 import org.junit.Test;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  * Build/Install/Run:
  *     atest CtsWindowManagerDeviceTestCases:AmStartOptionsTests
@@ -92,9 +87,10 @@
         // Start LaunchingActivity again and finish TestActivity
         final int flags =
                 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP;
-        final String result = executeShellCommand(
-                "am start -W -f " + flags + " -n " + getActivityName(LAUNCHING_ACTIVITY));
-        verifyShellOutput(result, LAUNCHING_ACTIVITY, false);
+        executeShellCommand("am start -W -f " + flags + " -n " + getActivityName(LAUNCHING_ACTIVITY)
+                + " --display " + DEFAULT_DISPLAY);
+        waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
+                "Activity must be launched.");
     }
 
     private void testDashW(final ComponentName entryActivity, final ComponentName actualActivity)
@@ -112,73 +108,14 @@
 
     private void startActivityAndVerifyResult(final ComponentName entryActivity,
             final ComponentName actualActivity, boolean shouldStart) {
-        // See TODO below
-        // final LogSeparator logSeparator = separateLogs();
-
         // Pass in different data only when cold starting. This is to make the intent
         // different in subsequent warm/hot launches, so that the entrypoint alias
         // activity is always started, but the actual activity is not started again
         // because of the NEW_TASK and singleTask flags.
-        final String result = executeShellCommand(
-                "am start -n " + getActivityName(entryActivity) + " -W"
-                + (shouldStart ? " -d about:blank" : ""));
+        executeShellCommand("am start -n " + getActivityName(entryActivity) + " -W --display "
+                + DEFAULT_DISPLAY + (shouldStart ? " -d about:blank" : ""));
 
-        // Verify shell command return value
-        verifyShellOutput(result, actualActivity, shouldStart);
-
-        // TODO: Disable logcat check for now.
-        // Logcat of WM or AM tag could be lost (eg. chatty if earlier events generated
-        // too many lines), and make the test look flaky. We need to either use event
-        // log or swith to other mechanisms. Only verify shell output for now, it should
-        // still catch most failures.
-
-        // Verify adb logcat log
-        //verifyLogcat(actualActivity, shouldStart, logSeparator);
-    }
-
-    private static final Pattern sNotStartedWarningPattern = Pattern.compile(
-            "Warning: Activity not started(.*)");
-    private static final Pattern sStatusPattern = Pattern.compile(
-            "Status: (.*)");
-    private static final Pattern sActivityPattern = Pattern.compile(
-            "Activity: (.*)");
-    private static final String sStatusOk = "ok";
-
-    private void verifyShellOutput(
-            final String result, final ComponentName activity, boolean shouldStart) {
-        boolean warningFound = false;
-        String status = null;
-        String reportedActivity = null;
-
-        final String[] lines = result.split("\\n");
-        // Going from the end of logs to beginning in case if some other activity is started first.
-        for (int i = lines.length - 1; i >= 0; i--) {
-            final String line = lines[i].trim();
-            Matcher matcher = sNotStartedWarningPattern.matcher(line);
-            if (matcher.matches()) {
-                warningFound = true;
-                continue;
-            }
-            matcher = sStatusPattern.matcher(line);
-            if (matcher.matches()) {
-                status = matcher.group(1);
-                continue;
-            }
-            matcher = sActivityPattern.matcher(line);
-            if (matcher.matches()) {
-                reportedActivity = matcher.group(1);
-                continue;
-            }
-        }
-
-        assertEquals("Status is ok", sStatusOk, status);
-        assertEquals("Reported activity is " +  getActivityName(activity),
-                getActivityName(activity), reportedActivity);
-
-        if (shouldStart && warningFound) {
-            fail("Should start new activity but brought something to front.");
-        } else if (!shouldStart && !warningFound){
-            fail("Should bring existing activity to front but started new activity.");
-        }
+        waitAndAssertTopResumedActivity(actualActivity, DEFAULT_DISPLAY,
+                "Activity must be launched");
     }
 }
diff --git a/tests/leanbackjank/OWNERS b/tests/leanbackjank/OWNERS
new file mode 100644
index 0000000..729b9b6
--- /dev/null
+++ b/tests/leanbackjank/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 188489
+dake@google.com
\ No newline at end of file
diff --git a/tests/tests/animation/AndroidTest.xml b/tests/tests/animation/AndroidTest.xml
index 13b2a02..7b9d17d 100644
--- a/tests/tests/animation/AndroidTest.xml
+++ b/tests/tests/animation/AndroidTest.xml
@@ -17,6 +17,7 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsAnimationTestCases.apk" />
diff --git a/tests/tests/animation/OWNERS b/tests/tests/animation/OWNERS
new file mode 100644
index 0000000..475f1b8
--- /dev/null
+++ b/tests/tests/animation/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 47085
+tianliu@google.com
+mount@google.com
+andreykulikov@google.com
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
index e3be1d1..e67fce8 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
index ce18075..8a48104 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
index f991189..2145eec 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
index f2798b4..5428052 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
index aee71ec..70ee76a 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
index a879e3c..0a195a8 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
Binary files differ
diff --git a/tests/tests/graphics/src/android/graphics/cts/CameraTest.java b/tests/tests/graphics/src/android/graphics/cts/CameraTest.java
index ff8f77c..92f6322 100644
--- a/tests/tests/graphics/src/android/graphics/cts/CameraTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/CameraTest.java
@@ -91,8 +91,8 @@
         float[] f = new float[9];
         m2.getValues(f);
         assertArrayEquals(new float[] {
-                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.0017361111f, 1.0f
-        }, f, 0.0f);
+                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
+        }, f, 0.0017361111f);
     }
 
     @Test
@@ -107,8 +107,8 @@
         float[] f = new float[9];
         m2.getValues(f);
         assertArrayEquals(new float[] {
-                0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0017361111f, 0.0f, 1.0f
-        }, f, 0.0f);
+                0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f
+        }, f, 0.0017361111f);
     }
 
     @Test
@@ -124,7 +124,7 @@
         m2.getValues(f);
         assertArrayEquals(new float[] {
                 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
-        }, f, 0.0f);
+        }, f, 0.0017361111f);
     }
 
     @Test
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..abaea3f
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_webm_av1_7000kbps_60fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1920x1080_webm_av1_7000kbps_60fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..e2277e1
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_webm_av1_7000kbps_60fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_320x180_webm_av1_200kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_320x180_webm_av1_200kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..7cf844e
--- /dev/null
+++ b/tests/tests/media/res/raw/video_320x180_webm_av1_200kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_3840x2160_webm_av1_11000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_3840x2160_webm_av1_11000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..a61e8f9
--- /dev/null
+++ b/tests/tests/media/res/raw/video_3840x2160_webm_av1_11000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_3840x2160_webm_av1_18000kbps_60fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_3840x2160_webm_av1_18000kbps_60fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..51e4d27
--- /dev/null
+++ b/tests/tests/media/res/raw/video_3840x2160_webm_av1_18000kbps_60fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..5671197
--- /dev/null
+++ b/tests/tests/media/res/raw/video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_640x360_webm_av1_470kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_640x360_webm_av1_470kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..1d99b2c
--- /dev/null
+++ b/tests/tests/media/res/raw/video_640x360_webm_av1_470kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
index 3391aa3..1918e65 100644
--- a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
+++ b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
@@ -107,6 +107,15 @@
                 R.raw.bbb_s1_320x180_webm_vp9_0p11_600kbps_30fps_vorbis_mono_64kbps_48000hz);
     }
 
+    public Iterable<Codec> AV1(CodecFactory factory) {
+        return factory.createCodecList(
+                mContext,
+                MediaFormat.MIMETYPE_VIDEO_AV1,
+                R.raw.video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                R.raw.video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                R.raw.video_320x180_webm_av1_200kbps_30fps_vorbis_stereo_128kbps_48000hz);
+    }
+
     CodecFactory ALL = new CodecFactory();
     CodecFactory SW  = new SWCodecFactory();
     CodecFactory HW  = new HWCodecFactory();
@@ -115,19 +124,20 @@
     public Iterable<Codec> HEVC()  { return HEVC(ALL);  }
     public Iterable<Codec> VP8()   { return VP8(ALL);   }
     public Iterable<Codec> VP9()   { return VP9(ALL);   }
+    public Iterable<Codec> AV1()   { return AV1(ALL);   }
     public Iterable<Codec> Mpeg4() { return Mpeg4(ALL); }
     public Iterable<Codec> H263()  { return H263(ALL);  }
 
     public Iterable<Codec> AllCodecs() {
-        return chain(H264(ALL), HEVC(ALL), VP8(ALL), VP9(ALL), Mpeg4(ALL), H263(ALL));
+        return chain(H264(ALL), HEVC(ALL), VP8(ALL), VP9(ALL), AV1(ALL), Mpeg4(ALL), H263(ALL));
     }
 
     public Iterable<Codec> SWCodecs() {
-        return chain(H264(SW), HEVC(SW), VP8(SW), VP9(SW), Mpeg4(SW), H263(SW));
+        return chain(H264(SW), HEVC(SW), VP8(SW), VP9(SW), AV1(SW), Mpeg4(SW), H263(SW));
     }
 
     public Iterable<Codec> HWCodecs() {
-        return chain(H264(HW), HEVC(HW), VP8(HW), VP9(HW), Mpeg4(HW), H263(HW));
+        return chain(H264(HW), HEVC(HW), VP8(HW), VP9(HW), AV1(HW), Mpeg4(HW), H263(HW));
     }
 
     /* tests for adaptive codecs */
@@ -184,6 +194,7 @@
     public void runHEVC()  { ex(HEVC(),  allTests); }
     public void runVP8()   { ex(VP8(),   allTests); }
     public void runVP9()   { ex(VP9(),   allTests); }
+    public void runAV1()   { ex(AV1(),   allTests); }
     public void runMpeg4() { ex(Mpeg4(), allTests); }
     public void runH263()  { ex(H263(),  allTests); }
 
@@ -191,6 +202,7 @@
     public void onlyHEVCHW()  { ex(HEVC(HW),  allTests); }
     public void onlyVP8HW()   { ex(VP8(HW),   allTests); }
     public void onlyVP9HW()   { ex(VP9(HW),   allTests); }
+    public void onlyAV1HW()   { ex(AV1(HW),   allTests); }
     public void onlyMpeg4HW() { ex(Mpeg4(HW), allTests); }
     public void onlyH263HW()  { ex(H263(HW),  allTests); }
 
@@ -198,6 +210,7 @@
     public void onlyHEVCSW()  { ex(HEVC(SW),  allTests); }
     public void onlyVP8SW()   { ex(VP8(SW),   allTests); }
     public void onlyVP9SW()   { ex(VP9(SW),   allTests); }
+    public void onlyAV1SW()   { ex(AV1(SW),   allTests); }
     public void onlyMpeg4SW() { ex(Mpeg4(SW), allTests); }
     public void onlyH263SW()  { ex(H263(SW),  allTests); }
 
@@ -209,6 +222,7 @@
     public void testHEVC_adaptiveEarlyEos()  { ex(HEVC(),  adaptiveEarlyEos); }
     public void testVP8_adaptiveEarlyEos()   { ex(VP8(),   adaptiveEarlyEos); }
     public void testVP9_adaptiveEarlyEos()   { ex(VP9(),   adaptiveEarlyEos); }
+    public void testAV1_adaptiveEarlyEos()   { ex(AV1(),   adaptiveEarlyEos); }
     public void testMpeg4_adaptiveEarlyEos() { ex(Mpeg4(), adaptiveEarlyEos); }
     public void testH263_adaptiveEarlyEos()  { ex(H263(),  adaptiveEarlyEos); }
 
@@ -216,6 +230,7 @@
     public void testHEVC_adaptiveEosFlushSeek()  { ex(HEVC(),  adaptiveEosFlushSeek); }
     public void testVP8_adaptiveEosFlushSeek()   { ex(VP8(),   adaptiveEosFlushSeek); }
     public void testVP9_adaptiveEosFlushSeek()   { ex(VP9(),   adaptiveEosFlushSeek); }
+    public void testAV1_adaptiveEosFlushSeek()   { ex(AV1(),   adaptiveEosFlushSeek); }
     public void testMpeg4_adaptiveEosFlushSeek() { ex(Mpeg4(), adaptiveEosFlushSeek); }
     public void testH263_adaptiveEosFlushSeek()  { ex(H263(),  adaptiveEosFlushSeek); }
 
@@ -223,6 +238,7 @@
     public void testHEVC_adaptiveSkipAhead()  { ex(HEVC(),  adaptiveSkipAhead); }
     public void testVP8_adaptiveSkipAhead()   { ex(VP8(),   adaptiveSkipAhead); }
     public void testVP9_adaptiveSkipAhead()   { ex(VP9(),   adaptiveSkipAhead); }
+    public void testAV1_adaptiveSkipAhead()   { ex(AV1(),   adaptiveSkipAhead); }
     public void testMpeg4_adaptiveSkipAhead() { ex(Mpeg4(), adaptiveSkipAhead); }
     public void testH263_adaptiveSkipAhead()  { ex(H263(),  adaptiveSkipAhead); }
 
@@ -230,6 +246,7 @@
     public void testHEVC_adaptiveSkipBack()  { ex(HEVC(),  adaptiveSkipBack); }
     public void testVP8_adaptiveSkipBack()   { ex(VP8(),   adaptiveSkipBack); }
     public void testVP9_adaptiveSkipBack()   { ex(VP9(),   adaptiveSkipBack); }
+    public void testAV1_adaptiveSkipBack()   { ex(AV1(),   adaptiveSkipBack); }
     public void testMpeg4_adaptiveSkipBack() { ex(Mpeg4(), adaptiveSkipBack); }
     public void testH263_adaptiveSkipBack()  { ex(H263(),  adaptiveSkipBack); }
 
@@ -237,6 +254,7 @@
     public void testHEVC_adaptiveReconfigDrc()  { ex(HEVC(),  adaptiveReconfigDrc); }
     public void testVP8_adaptiveReconfigDrc()   { ex(VP8(),   adaptiveReconfigDrc); }
     public void testVP9_adaptiveReconfigDrc()   { ex(VP9(),   adaptiveReconfigDrc); }
+    public void testAV1_adaptiveReconfigDrc()   { ex(AV1(),   adaptiveReconfigDrc); }
     public void testMpeg4_adaptiveReconfigDrc() { ex(Mpeg4(), adaptiveReconfigDrc); }
     public void testH263_adaptiveReconfigDrc()  { ex(H263(),  adaptiveReconfigDrc); }
 
@@ -244,6 +262,7 @@
     public void testHEVC_adaptiveSmallReconfigDrc()  { ex(HEVC(),  adaptiveSmallReconfigDrc); }
     public void testVP8_adaptiveSmallReconfigDrc()   { ex(VP8(),   adaptiveSmallReconfigDrc); }
     public void testVP9_adaptiveSmallReconfigDrc()   { ex(VP9(),   adaptiveSmallReconfigDrc); }
+    public void testAV1_adaptiveSmallReconfigDrc()   { ex(AV1(),   adaptiveSmallReconfigDrc); }
     public void testMpeg4_adaptiveSmallReconfigDrc() { ex(Mpeg4(), adaptiveSmallReconfigDrc); }
     public void testH263_adaptiveSmallReconfigDrc()  { ex(H263(),  adaptiveSmallReconfigDrc); }
 
@@ -251,6 +270,7 @@
     public void testHEVC_adaptiveDrc() { ex(HEVC(), adaptiveDrc); }
     public void testVP8_adaptiveDrc()  { ex(VP8(),  adaptiveDrc); }
     public void testVP9_adaptiveDrc()  { ex(VP9(),  adaptiveDrc); }
+    public void testAV1_adaptiveDrc()  { ex(AV1(),  adaptiveDrc); }
     public void testMpeg4_adaptiveDrc() { ex(Mpeg4(), adaptiveDrc); }
     public void testH263_adaptiveDrc() { ex(H263(), adaptiveDrc); }
 
@@ -258,16 +278,19 @@
     public void testHEVC_adaptiveDrcEarlyEos() { ex(HEVC(), new AdaptiveDrcEarlyEosTest()); }
     public void testVP8_adaptiveDrcEarlyEos()  { ex(VP8(),  new AdaptiveDrcEarlyEosTest()); }
     public void testVP9_adaptiveDrcEarlyEos()  { ex(VP9(),  new AdaptiveDrcEarlyEosTest()); }
+    public void testAV1_adaptiveDrcEarlyEos()  { ex(AV1(),  new AdaptiveDrcEarlyEosTest()); }
 
     public void testH264_adaptiveSmallDrc()  { ex(H264(),  adaptiveSmallDrc); }
     public void testHEVC_adaptiveSmallDrc()  { ex(HEVC(),  adaptiveSmallDrc); }
     public void testVP8_adaptiveSmallDrc()   { ex(VP8(),   adaptiveSmallDrc); }
     public void testVP9_adaptiveSmallDrc()   { ex(VP9(),   adaptiveSmallDrc); }
+    public void testAV1_adaptiveSmallDrc()   { ex(AV1(),   adaptiveSmallDrc); }
 
     public void testH264_earlyEos()  { ex(H264(),  earlyEos); }
     public void testHEVC_earlyEos()  { ex(HEVC(),  earlyEos); }
     public void testVP8_earlyEos()   { ex(VP8(),   earlyEos); }
     public void testVP9_earlyEos()   { ex(VP9(),   earlyEos); }
+    public void testAV1_earlyEos()   { ex(AV1(),   earlyEos); }
     public void testMpeg4_earlyEos() { ex(Mpeg4(), earlyEos); }
     public void testH263_earlyEos()  { ex(H263(),  earlyEos); }
 
@@ -275,6 +298,7 @@
     public void testHEVC_eosFlushSeek()  { ex(HEVC(),  eosFlushSeek); }
     public void testVP8_eosFlushSeek()   { ex(VP8(),   eosFlushSeek); }
     public void testVP9_eosFlushSeek()   { ex(VP9(),   eosFlushSeek); }
+    public void testAV1_eosFlushSeek()   { ex(AV1(),   eosFlushSeek); }
     public void testMpeg4_eosFlushSeek() { ex(Mpeg4(), eosFlushSeek); }
     public void testH263_eosFlushSeek()  { ex(H263(),  eosFlushSeek); }
 
@@ -282,6 +306,7 @@
     public void testHEVC_flushConfigureDrc()  { ex(HEVC(),  flushConfigureDrc); }
     public void testVP8_flushConfigureDrc()   { ex(VP8(),   flushConfigureDrc); }
     public void testVP9_flushConfigureDrc()   { ex(VP9(),   flushConfigureDrc); }
+    public void testAV1_flushConfigureDrc()   { ex(AV1(),   flushConfigureDrc); }
     public void testMpeg4_flushConfigureDrc() { ex(Mpeg4(), flushConfigureDrc); }
     public void testH263_flushConfigureDrc()  { ex(H263(),  flushConfigureDrc); }
 
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
index 207c2a2..559cf20 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
@@ -28,6 +28,7 @@
 import android.media.AudioPlaybackConfiguration;
 
 import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.internal.annotations.GuardedBy;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -353,16 +354,16 @@
     }
 
     private static class MyAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
-        private int mCalled = 0;
-        private int mNbConfigs = 0;
-        private List<AudioPlaybackConfiguration> mConfigs;
         private final Object mCbLock = new Object();
+        @GuardedBy("mCbLock")
+        private int mCalled;
+        @GuardedBy("mCbLock")
+        private List<AudioPlaybackConfiguration> mConfigs;
 
         void reset() {
             synchronized (mCbLock) {
                 mCalled = 0;
-                mNbConfigs = 0;
-                mConfigs.clear();
+                mConfigs = new ArrayList<AudioPlaybackConfiguration>();
             }
         }
 
@@ -373,9 +374,7 @@
         }
 
         int getNbConfigs() {
-            synchronized (mCbLock) {
-                return mNbConfigs;
-            }
+            return getConfigs().size();
         }
 
         List<AudioPlaybackConfiguration> getConfigs() {
@@ -385,13 +384,13 @@
         }
 
         MyAudioPlaybackCallback() {
+            reset();
         }
 
         @Override
         public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
             synchronized (mCbLock) {
                 mCalled++;
-                mNbConfigs = configs.size();
                 mConfigs = configs;
             }
         }
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index 32a1816..f8e0e64 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -87,6 +87,7 @@
         }
     };
     private static final int RECORD_DURATION_MS = 500;
+    private static final int TEST_TIMING_TOLERANCE_MS = 70;
 
     @Before
     public void setUp() throws Exception {
@@ -969,16 +970,14 @@
         mAudioRecord.startRecording();
         assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
 
-        try {
-            Thread.sleep(RECORD_DURATION_MS);
-        } catch (InterruptedException e) {
-        }
+        callback.await(TEST_TIMING_TOLERANCE_MS);
         assertTrue(callback.mCalled);
         assertTrue(callback.mConfigs.size() <= 1);
         if (callback.mConfigs.size() == 1) {
             checkRecordingConfig(callback.mConfigs.get(0));
         }
 
+        Thread.sleep(RECORD_DURATION_MS);
         mAudioRecord.unregisterAudioRecordingCallback(callback);
     }
 
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java
index 4258c03..c285be7 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java
@@ -34,6 +34,8 @@
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.Iterator;
 import java.util.List;
 
@@ -174,7 +176,7 @@
             assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
             mAudioRecord.startRecording();
             assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            callback.await(TEST_TIMING_TOLERANCE_MS);
 
             assertTrue("AudioRecordingCallback not called after start", callback.mCalled);
             Thread.sleep(TEST_TIMING_TOLERANCE_MS);
@@ -193,16 +195,17 @@
             // stopping recording: callback is called with no match
             callback.reset();
             mAudioRecord.stop();
-            Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
+            callback.await(TEST_TIMING_TOLERANCE_MS);
             assertTrue("AudioRecordingCallback not called after stop", callback.mCalled);
             assertEquals("Should not have found record configurations", callback.mConfigs.size(),
                     0);
+            Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
 
             // unregister callback and start recording again
             am.unregisterAudioRecordingCallback(callback);
             callback.reset();
             mAudioRecord.startRecording();
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            callback.await(TEST_TIMING_TOLERANCE_MS);
             assertFalse("Unregistered callback was called", callback.mCalled);
 
             // just call the callback once directly so it's marked as tested
@@ -249,25 +252,36 @@
     }
 
     static class MyAudioRecordingCallback extends AudioManager.AudioRecordingCallback {
-        boolean mCalled = false;
+        boolean mCalled;
         List<AudioRecordingConfiguration> mConfigs;
-        final int mTestSource;
-        final int mTestSession;
+        private final int mTestSource;
+        private final int mTestSession;
+        private CountDownLatch mCountDownLatch;
 
         void reset() {
+            mCountDownLatch = new CountDownLatch(1);
             mCalled = false;
-            mConfigs = null;
+            mConfigs = new ArrayList<AudioRecordingConfiguration>();
         }
 
         MyAudioRecordingCallback(int session, int source) {
             mTestSource = source;
             mTestSession = session;
+            reset();
         }
 
         @Override
         public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
             mCalled = true;
             mConfigs = configs;
+            mCountDownLatch.countDown();
+        }
+
+        void await(long timeoutMs) {
+            try {
+                mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+            }
         }
     }
 
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index f1655a0..ed574a9 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -1861,6 +1861,10 @@
         testDecode(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz, 240);
     }
 
+    public void testCodecBasicAV1() throws Exception {
+        testDecode(R.raw.video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz, 300);
+    }
+
     public void testH264Decode320x240() throws Exception {
         testDecode(R.raw.bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz, 300);
     }
@@ -2056,6 +2060,36 @@
                 R.raw.bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz, 300);
     }
 
+    public void testAV1Decode320x180() throws Exception {
+        testDecode(R.raw.video_320x180_webm_av1_200kbps_30fps_vorbis_stereo_128kbps_48000hz, 300);
+    }
+
+    public void testAV1Decode640x360() throws Exception {
+        testDecode(
+                R.raw.video_640x360_webm_av1_470kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                300);
+    }
+
+    public void testAV1Decode30fps1280x720() throws Exception {
+        testDecode(
+                R.raw.video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz, 300);
+    }
+
+    public void testAV1Decode60fps1920x1080() throws Exception {
+        testDecode(
+                R.raw.video_1920x1080_webm_av1_7000kbps_60fps_vorbis_stereo_128kbps_48000hz, 300);
+    }
+
+    public void testAV1Decode30fps3840x2160() throws Exception {
+        testDecode(
+                R.raw.video_3840x2160_webm_av1_11000kbps_30fps_vorbis_stereo_128kbps_48000hz, 150);
+    }
+
+    public void testAV1Decode60fps3840x2160() throws Exception {
+        testDecode(
+                R.raw.video_3840x2160_webm_av1_18000kbps_60fps_vorbis_stereo_128kbps_48000hz, 300);
+    }
+
     public void testHEVCDecode352x288() throws Exception {
         testDecode(
                 R.raw.bbb_s1_352x288_mp4_hevc_mp2_600kbps_30fps_aac_he_stereo_96kbps_48000hz, 300);
@@ -2147,6 +2181,12 @@
                 120 /* eosframe */);
     }
 
+    public void testCodecEarlyEOSAV1() throws Exception {
+        testCodecEarlyEOS(
+                R.raw.video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                120 /* eosframe */);
+    }
+
     public void testCodecResetsH264WithoutSurface() throws Exception {
         testCodecResets(
                 R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, null);
@@ -2207,12 +2247,23 @@
                 R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz, null);
     }
 
+    public void testCodecResetsAV1WithoutSurface() throws Exception {
+        testCodecResets(
+                R.raw.video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz, null);
+    }
+
     public void testCodecResetsVP9WithSurface() throws Exception {
         Surface s = getActivity().getSurfaceHolder().getSurface();
         testCodecResets(
                 R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz, s);
     }
 
+    public void testCodecResetsAV1WithSurface() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        testCodecResets(
+                R.raw.video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz, s);
+    }
+
 //    public void testCodecResetsOgg() throws Exception {
 //        testCodecResets(R.raw.sinesweepogg, null);
 //    }
@@ -2694,6 +2745,13 @@
                 new int[] {1, 44, 45, 55, 43});
     }
 
+    public void testEOSBehaviorAV1() throws Exception {
+        // this video has an I frame at 44
+        testEOSBehavior(
+                R.raw.video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                new int[] {1, 44, 45, 55, 43});
+    }
+
     /* from EncodeDecodeTest */
     private static boolean isRecognizedFormat(int colorFormat) {
         // Log.d(TAG, "color format: " + String.format("0x%08x", colorFormat));
diff --git a/tests/tests/media/src/android/media/cts/MediaRandomTest.java b/tests/tests/media/src/android/media/cts/MediaRandomTest.java
index 3db1d13..f44322f 100644
--- a/tests/tests/media/src/android/media/cts/MediaRandomTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRandomTest.java
@@ -170,6 +170,9 @@
             afd.close();
         }
     }
+    public void testPlayerRandomActionAV1() throws Exception {
+        testPlayerRandomAction(R.raw.video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz);
+    }
     public void testPlayerRandomActionH264() throws Exception {
         testPlayerRandomAction(R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz);
     }
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index ac68057..0e9ec05 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -80,6 +80,7 @@
     private static final int RECORD_TIME_LAPSE_MS = 6000;
     private static final int RECORD_TIME_LONG_MS = 20000;
     private static final int RECORDED_DUR_TOLERANCE_MS = 1000;
+    private static final int TEST_TIMING_TOLERANCE_MS = 70;
     // Tolerate 4 frames off at maximum
     private static final float RECORDED_DUR_TOLERANCE_FRAMES = 4f;
     private static final int VIDEO_WIDTH = 176;
@@ -1655,12 +1656,13 @@
         configureDefaultMediaRecorder();
         mMediaRecorder.prepare();
         mMediaRecorder.start();
-        Thread.sleep(1000);
+        callback.await(TEST_TIMING_TOLERANCE_MS);
         assertTrue(callback.mCalled);
         assertTrue(callback.mConfigs.size() <= 1);
         if (callback.mConfigs.size() == 1) {
             checkRecordingConfig(callback.mConfigs.get(0));
         }
+        Thread.sleep(RECORD_TIME_MS);
         mMediaRecorder.stop();
         mMediaRecorder.unregisterAudioRecordingCallback(callback);
     }
diff --git a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
index c2d1392..58c57d8 100644
--- a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
@@ -125,6 +125,7 @@
         testExtractor(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
         testExtractor(R.raw.bbb_s3_1280x720_webm_vp8_8mbps_60fps_opus_6ch_384kbps_48000hz);
         testExtractor(R.raw.bbb_s4_1280x720_webm_vp9_0p31_4mbps_30fps_opus_stereo_128kbps_48000hz);
+        testExtractor(R.raw.video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz);
         testExtractor(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
         testExtractor(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
 
@@ -547,6 +548,8 @@
             testVideoPlayback(
                     R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz) +
             testVideoPlayback(
+                    R.raw.video_640x360_webm_av1_470kbps_30fps_vorbis_stereo_128kbps_48000hz) +
+            testVideoPlayback(
                     R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz) +
             testVideoPlayback(
                     R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
diff --git a/tests/tests/nativemedia/aaudio/AndroidTest.xml b/tests/tests/nativemedia/aaudio/AndroidTest.xml
index b796ea6..12da94e 100644
--- a/tests/tests/nativemedia/aaudio/AndroidTest.xml
+++ b/tests/tests/nativemedia/aaudio/AndroidTest.xml
@@ -16,6 +16,8 @@
 <configuration description="Config for CTS Native Media AAudio test cases">
     <option name="config-descriptor:metadata" key="component" value="media" />
     <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/nativemidi/OWNERS b/tests/tests/nativemidi/OWNERS
new file mode 100644
index 0000000..ce5adf1
--- /dev/null
+++ b/tests/tests/nativemidi/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48436
+pmclean@google.com
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/tapjacking/OWNERS b/tests/tests/packageinstaller/tapjacking/OWNERS
new file mode 100644
index 0000000..0088192
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36137
+moltmann@google.com
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/uninstall/OWNERS b/tests/tests/packageinstaller/uninstall/OWNERS
new file mode 100644
index 0000000..0088192
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36137
+moltmann@google.com
\ No newline at end of file
diff --git a/tests/tests/permission/sdk28/OWNERS b/tests/tests/permission/sdk28/OWNERS
new file mode 100644
index 0000000..c126a70
--- /dev/null
+++ b/tests/tests/permission/sdk28/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137825
+moltmann@google.com
\ No newline at end of file
diff --git a/tests/tests/permission2/Android.bp b/tests/tests/permission2/Android.bp
index de5bd00..ccac398 100644
--- a/tests/tests/permission2/Android.bp
+++ b/tests/tests/permission2/Android.bp
@@ -26,6 +26,7 @@
     ],
     libs: ["android.test.base.stubs"],
     static_libs: [
+        "androidx.test.core",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "guava",
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index f931968..5c32536 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -1519,18 +1519,6 @@
     <permission android:name="android.permission.OVERRIDE_WIFI_CONFIG"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @hide -->
-    <permission android:name="android.permission.ACCESS_WIMAX_STATE"
-        android:description="@string/permdesc_accessWimaxState"
-        android:label="@string/permlab_accessWimaxState"
-        android:protectionLevel="normal" />
-
-    <!-- @hide -->
-    <permission android:name="android.permission.CHANGE_WIMAX_STATE"
-        android:description="@string/permdesc_changeWimaxState"
-        android:label="@string/permlab_changeWimaxState"
-        android:protectionLevel="normal" />
-
     <!-- Allows applications to act as network scorers. @hide @SystemApi-->
     <permission android:name="android.permission.SCORE_NETWORKS"
         android:protectionLevel="signature|privileged" />
@@ -1600,12 +1588,6 @@
     <permission android:name="android.permission.MANAGE_LOWPAN_INTERFACES"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @hide Allows internal management of Wi-Fi connectivity state when on
-         wireless consent mode.
-         <p>Not for use by third-party applications. -->
-    <permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED"
-        android:protectionLevel="signature" />
-
     <!-- #SystemApi @hide Allows an app to bypass Private DNS.
          <p>Not for use by third-party applications.
          TODO: publish as system API in next API release. -->
@@ -3454,6 +3436,11 @@
     <permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"
         android:protectionLevel="signature|installer" />
 
+    <!-- Allows an application to manage the companion devices.
+         @hide -->
+    <permission android:name="android.permission.MANAGE_COMPANION_DEVICES"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
          <p>Not for use by third-party applications.
          @hide
diff --git a/tests/tests/preference/OWNERS b/tests/tests/preference/OWNERS
index 827134e..0ea8182 100644
--- a/tests/tests/preference/OWNERS
+++ b/tests/tests/preference/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 25700
 lpf@google.com
 pavlis@google.com
 clarabayarri@google.com
diff --git a/tests/tests/provider/OWNERS b/tests/tests/provider/OWNERS
new file mode 100644
index 0000000..fea932d
--- /dev/null
+++ b/tests/tests/provider/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 1344
+jsharkey@android.com
+omakoto@google.com
+yamasani@google.com
+tgunn@google.com
+nicksauer@google.com
+nona@google.com
diff --git a/tests/tests/secure_element/access_control/AccessControlApp1/OWNERS b/tests/tests/secure_element/access_control/AccessControlApp1/OWNERS
deleted file mode 100644
index 9af5f4d..0000000
--- a/tests/tests/secure_element/access_control/AccessControlApp1/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 456592
-kandoiruchi@google.com
\ No newline at end of file
diff --git a/tests/tests/secure_element/access_control/AccessControlApp2/OWNERS b/tests/tests/secure_element/access_control/AccessControlApp2/OWNERS
deleted file mode 100644
index 9af5f4d..0000000
--- a/tests/tests/secure_element/access_control/AccessControlApp2/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 456592
-kandoiruchi@google.com
\ No newline at end of file
diff --git a/tests/tests/secure_element/access_control/AccessControlApp3/OWNERS b/tests/tests/secure_element/access_control/AccessControlApp3/OWNERS
deleted file mode 100644
index 9af5f4d..0000000
--- a/tests/tests/secure_element/access_control/AccessControlApp3/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 456592
-kandoiruchi@google.com
\ No newline at end of file
diff --git a/tests/tests/secure_element/access_control/OWNERS b/tests/tests/secure_element/access_control/OWNERS
new file mode 100644
index 0000000..54737c6
--- /dev/null
+++ b/tests/tests/secure_element/access_control/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 456592
+rmojumder@google.com
diff --git a/tests/tests/systemintents/OWNERS b/tests/tests/systemintents/OWNERS
new file mode 100644
index 0000000..b465964
--- /dev/null
+++ b/tests/tests/systemintents/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 25692
+ctate@google.com
\ No newline at end of file
diff --git a/tests/tests/telecom/OWNERS b/tests/tests/telecom/OWNERS
new file mode 100644
index 0000000..f4921e0
--- /dev/null
+++ b/tests/tests/telecom/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 20868
+include ../telephony/OWNERS
diff --git a/tests/tests/telecom2/OWNERS b/tests/tests/telecom2/OWNERS
new file mode 100644
index 0000000..f4921e0
--- /dev/null
+++ b/tests/tests/telecom2/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 20868
+include ../telephony/OWNERS
diff --git a/tests/tests/telecom3/OWNERS b/tests/tests/telecom3/OWNERS
new file mode 100644
index 0000000..f4921e0
--- /dev/null
+++ b/tests/tests/telecom3/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 20868
+include ../telephony/OWNERS
diff --git a/tests/tests/telephony/OWNERS b/tests/tests/telephony/OWNERS
new file mode 100644
index 0000000..73fa25e
--- /dev/null
+++ b/tests/tests/telephony/OWNERS
@@ -0,0 +1,14 @@
+# Bug component: 20868
+amitmahajan@google.com
+fionaxu@google.com
+jackyu@google.com
+rgreenwalt@google.com
+refuhoo@google.com
+mpq@google.com
+jminjie@google.com
+shuoq@google.com
+hallliu@google.com
+tgunn@google.com
+breadley@google.com
+paulye@google.com
+nazaninb@google.com
diff --git a/tests/tests/telephony/current/OWNERS b/tests/tests/telephony/current/OWNERS
deleted file mode 100644
index 7c8e7d8..0000000
--- a/tests/tests/telephony/current/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-rgreenwalt@google.com
-jackyu@google.com
-tgunn@google.com
-amitmahajan@google.com
diff --git a/tests/tests/telephony2/OWNERS b/tests/tests/telephony2/OWNERS
index 7c8e7d8..f4921e0 100644
--- a/tests/tests/telephony2/OWNERS
+++ b/tests/tests/telephony2/OWNERS
@@ -1,5 +1,2 @@
 # Bug component: 20868
-rgreenwalt@google.com
-jackyu@google.com
-tgunn@google.com
-amitmahajan@google.com
+include ../telephony/OWNERS
diff --git a/tests/tests/telephony3/OWNERS b/tests/tests/telephony3/OWNERS
new file mode 100644
index 0000000..f4921e0
--- /dev/null
+++ b/tests/tests/telephony3/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 20868
+include ../telephony/OWNERS
diff --git a/tests/tests/telephony4/OWNERS b/tests/tests/telephony4/OWNERS
index 5617896..3a905dd 100644
--- a/tests/tests/telephony4/OWNERS
+++ b/tests/tests/telephony4/OWNERS
@@ -1,2 +1,4 @@
 # Bug component: 20868
-yinxu@google.com
\ No newline at end of file
+include ../telephony/OWNERS
+
+yinxu@google.com
diff --git a/tests/tests/telephonyprovider/OWNERS b/tests/tests/telephonyprovider/OWNERS
index 92458db..7f7694d 100644
--- a/tests/tests/telephonyprovider/OWNERS
+++ b/tests/tests/telephonyprovider/OWNERS
@@ -1,12 +1,3 @@
-amitmahajan@google.com
-fionaxu@google.com
-jackyu@google.com
-rgreenwalt@google.com
-refuhoo@google.com
-mpq@google.com
-jminjie@google.com
-shuoq@google.com
-hallliu@google.com
-tgunn@google.com
-breadley@google.com
-nazaninb@google.com
+# Bug component: 450841
+include ../telephony/OWNERS
+lelandmiller@google.com
diff --git a/tests/tests/tools/processors/view_inspector/OWNERS b/tests/tests/tools/processors/view_inspector/OWNERS
new file mode 100644
index 0000000..1572c57
--- /dev/null
+++ b/tests/tests/tools/processors/view_inspector/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 25700
+ashleyrose@google.com
+aurimas@google.com
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
index 1390ccf..828d9b5 100755
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
@@ -435,7 +435,7 @@
             uiAutomation.destroy();
             assertTrue(UiAutomationTestA11yService.sConnectedInstance.isConnected());
             getInstrumentation().getUiAutomation(); // Should suppress
-            assertFalse(UiAutomationTestA11yService.sConnectedInstance.isConnected());
+            waitForAccessibilityServiceToUnbind();
         } finally {
             turnAccessibilityOff();
         }
@@ -454,7 +454,7 @@
             UiAutomation suppressingUiAutomation = getInstrumentation().getUiAutomation();
             // We verify above that the connection is broken here. Make sure we see a new one
             // after we destroy it
-            UiAutomationTestA11yService.sConnectedInstance = null;
+            waitForAccessibilityServiceToUnbind();
             suppressingUiAutomation.destroy();
             waitForAccessibilityServiceToStart();
         } finally {
@@ -474,7 +474,7 @@
             getInstrumentation().getUiAutomation();
             // We verify above that the connection is broken here. Make sure we see a new one
             // after we change the flags
-            UiAutomationTestA11yService.sConnectedInstance = null;
+            waitForAccessibilityServiceToUnbind();
             getInstrumentation()
                     .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
             waitForAccessibilityServiceToStart();
@@ -541,12 +541,12 @@
     private void waitForAccessibilityServiceToStart() {
         long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
-            synchronized(UiAutomationTestA11yService.sWaitObjectForConnecting) {
+            synchronized(UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind) {
                 if (UiAutomationTestA11yService.sConnectedInstance != null) {
                     return;
                 }
                 try {
-                    UiAutomationTestA11yService.sWaitObjectForConnecting.wait(
+                    UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind.wait(
                             timeoutTimeMillis - SystemClock.uptimeMillis());
                 } catch (InterruptedException e) {
                     // Ignored; loop again
@@ -556,6 +556,24 @@
         throw new RuntimeException("Test accessibility service not starting");
     }
 
+    private void waitForAccessibilityServiceToUnbind() {
+        long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
+        while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
+            synchronized(UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind) {
+                if (UiAutomationTestA11yService.sConnectedInstance == null) {
+                    return;
+                }
+                try {
+                    UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind.wait(
+                            timeoutTimeMillis - SystemClock.uptimeMillis());
+                } catch (InterruptedException e) {
+                    // Ignored; loop again
+                }
+            }
+        }
+        throw new RuntimeException("Test accessibility service doesn't unbind");
+    }
+
     private void turnAccessibilityOff() {
         getInstrumentation().getUiAutomation().destroy();
         final Object waitLockForA11yOff = new Object();
@@ -574,7 +592,6 @@
         ContentResolver cr = context.getContentResolver();
         Settings.Secure.putString(
                 cr, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, null);
-        UiAutomationTestA11yService.sConnectedInstance = null;
         long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
             synchronized (waitLockForA11yOff) {
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java
index 1808630..d062ed5 100644
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java
@@ -26,12 +26,16 @@
  */
 public class UiAutomationTestA11yService extends AccessibilityService {
     private static String LOG_TAG = "UiAutomationTest";
-    public static Object sWaitObjectForConnecting = new Object();
+    public static Object sWaitObjectForConnectOrUnbind = new Object();
 
     public static UiAutomationTestA11yService sConnectedInstance;
 
     @Override
     public boolean onUnbind(Intent intent) {
+        synchronized (sWaitObjectForConnectOrUnbind) {
+            sConnectedInstance = null;
+            sWaitObjectForConnectOrUnbind.notifyAll();
+        }
         Log.v(LOG_TAG, "onUnbind [" + this + "]");
         return false;
     }
@@ -52,9 +56,9 @@
 
     @Override
     protected void onServiceConnected() {
-        synchronized (sWaitObjectForConnecting) {
+        synchronized (sWaitObjectForConnectOrUnbind) {
             sConnectedInstance = this;
-            sWaitObjectForConnecting.notifyAll();
+            sWaitObjectForConnectOrUnbind.notifyAll();
         }
         Log.v(LOG_TAG, "onServiceConnected ["  + this + "]");
     }
diff --git a/tests/tests/view/OWNERS b/tests/tests/view/OWNERS
new file mode 100644
index 0000000..8200a30
--- /dev/null
+++ b/tests/tests/view/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 25700
+adamp@google.com
+shepshapard@google.com
+clarabayarri@google.com
diff --git a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
index 2bfab1e..3bc8890 100644
--- a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
@@ -31,7 +31,9 @@
 #include <android/surface_control.h>
 #include <android/sync.h>
 
+#include <errno.h>
 #include <jni.h>
+#include <time.h>
 
 namespace {
 
@@ -57,6 +59,16 @@
         return;                            \
     }
 
+#define NANOS_PER_SECOND 1000000000LL
+int64_t systemTime() {
+    struct timespec time;
+    int result = clock_gettime(CLOCK_MONOTONIC, &time);
+    if (result < 0) {
+        return -errno;
+    }
+    return (time.tv_sec * NANOS_PER_SECOND) + time.tv_nsec;
+}
+
 static AHardwareBuffer* allocateBuffer(int32_t width, int32_t height) {
     AHardwareBuffer* buffer = nullptr;
     AHardwareBuffer_Desc desc = {};
@@ -337,12 +349,16 @@
     int* contextIntPtr = reinterpret_cast<int*>(context);
     contextIntPtr[0]++;
     contextIntPtr[1] = presentFence;
+    int64_t* systemTimeLongPtr = reinterpret_cast<int64_t*>(contextIntPtr + 2);
+    *systemTimeLongPtr = systemTime();
 }
 
 jlong SurfaceTransaction_setOnComplete(JNIEnv* /*env*/, jclass, jlong surfaceTransaction) {
-    int* context = new int[2];
+    int* context = new int[4];
     context[0] = 0;
     context[1] = -1;
+    context[2] = -1;
+    context[3] = -1;
 
     ASurfaceTransaction_setOnComplete(
             reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
@@ -358,6 +374,9 @@
     int data = contextPtr[0];
     int presentFence = contextPtr[1];
 
+    int64_t* callbackTimePtr = reinterpret_cast<int64_t*>(contextPtr + 2);
+    int64_t callbackTime = *callbackTimePtr;
+
     delete[] contextPtr;
 
     if (desiredPresentTime < 0) {
@@ -367,31 +386,41 @@
         return;
     }
 
-    struct sync_file_info* syncFileInfo = sync_file_info(presentFence);
-    ASSERT(syncFileInfo, "invalid fence")
+    if (presentFence >= 0) {
+        struct sync_file_info* syncFileInfo = sync_file_info(presentFence);
+        ASSERT(syncFileInfo, "invalid fence");
 
-    if (syncFileInfo->status != 1) {
-        sync_file_info_free(syncFileInfo);
-        ASSERT(syncFileInfo->status == 1, "fence did not signal")
-    }
-
-    uint64_t presentTime = 0;
-    struct sync_fence_info* syncFenceInfo = sync_get_fence_info(syncFileInfo);
-    for (size_t i = 0; i < syncFileInfo->num_fences; i++) {
-        if (syncFenceInfo[i].timestamp_ns > presentTime) {
-            presentTime = syncFenceInfo[i].timestamp_ns;
+        if (syncFileInfo->status != 1) {
+            sync_file_info_free(syncFileInfo);
+            ASSERT(syncFileInfo->status == 1, "fence did not signal")
         }
+
+        uint64_t presentTime = 0;
+        struct sync_fence_info* syncFenceInfo = sync_get_fence_info(syncFileInfo);
+        for (size_t i = 0; i < syncFileInfo->num_fences; i++) {
+            if (syncFenceInfo[i].timestamp_ns > presentTime) {
+                presentTime = syncFenceInfo[i].timestamp_ns;
+            }
+        }
+
+        sync_file_info_free(syncFileInfo);
+        close(presentFence);
+
+        // In the worst case the worst present time should be no more than three frames off from the
+        // desired present time. Since the test case is using a virtual display and there are no
+        // requirements for virtual display refresh rate timing, lets assume a refresh rate of 16fps.
+        ASSERT(presentTime < desiredPresentTime + 0.188 * 1e9, "transaction was presented too late");
+        ASSERT(presentTime >= desiredPresentTime, "transaction was presented too early");
+    } else {
+        ASSERT(presentFence == -1, "invalid fences should be -1");
+        // The device doesn't support present fences. We will use the callback time to roughly
+        // verify the result. Since the callback could take up to half a frame, do the normal bound
+        // check plus an additional half frame.
+        ASSERT(callbackTime < desiredPresentTime + (0.188 + 0.031) * 1e9,
+                                                  "transaction was presented too late");
+        ASSERT(callbackTime >= desiredPresentTime, "transaction was presented too early");
     }
 
-    sync_file_info_free(syncFileInfo);
-    close(presentFence);
-
-    // In the worst case the worst present time should be no more than three frames off from the
-    // desired present time. Since the test case is using a virtual display and there are no
-    // requirements for virtual display refresh rate timing, lets assume a refresh rate of 16fps.
-    ASSERT(presentTime < desiredPresentTime + 0.188 * 1e9, "transaction was presented too late");
-    ASSERT(presentTime >= desiredPresentTime, "transaction was presented too early");
-
     ASSERT(data >= 1, "did not receive a callback")
     ASSERT(data <= 1, "received too many callbacks")
 }
diff --git a/tests/tests/widget/OWNERS b/tests/tests/widget/OWNERS
new file mode 100644
index 0000000..12f176d
--- /dev/null
+++ b/tests/tests/widget/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 25700
+adamp@google.com
+mount@google.com
+shepshapard@google.com
+clarabayarri@google.com