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