Merge changes from topic "am-8554d4f5-ed3e-488b-afae-b6c0b4d99d2a" into oc-dev am: 79e1e3295d am: cb0a8c502e am: b54fd3b7bd am: 8efb994710 am: 7d154a9289
am: 86eb0c819d
Change-Id: I5bd19e80ceebd4dd03bc762096161dd0f95d046a
diff --git a/hostsidetests/backup/OWNERS b/hostsidetests/backup/OWNERS
index 3637e32..c28c4d8 100644
--- a/hostsidetests/backup/OWNERS
+++ b/hostsidetests/backup/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 41666
# Use this reviewer by default.
br-framework-team+reviews@google.com
diff --git a/hostsidetests/backup/RestoreSessionTest/Android.bp b/hostsidetests/backup/RestoreSessionTest/Android.bp
new file mode 100644
index 0000000..29f6e59
--- /dev/null
+++ b/hostsidetests/backup/RestoreSessionTest/Android.bp
@@ -0,0 +1,27 @@
+// 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.
+
+java_library {
+ name: "CtsRestoreSessionApp",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.test.rules",
+ "platform-test-annotations",
+ "truth-prebuilt",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+ sdk_version: "system_current",
+}
diff --git a/hostsidetests/backup/RestoreSessionTest/AndroidManifest.xml b/hostsidetests/backup/RestoreSessionTest/AndroidManifest.xml
new file mode 100644
index 0000000..130e3aa
--- /dev/null
+++ b/hostsidetests/backup/RestoreSessionTest/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 Google Inc.
+ *
+ * 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 Licensea
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.restoresessionapp">
+
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
+
+ <application
+ android:label="RestoreSessionApp"
+ android:allowBackup="true">
+
+ <uses-library android:name="android.test.runner" />
+
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.restoresessionapp" />
+</manifest>
diff --git a/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/BaseRestoreSessionAppTest.java b/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/BaseRestoreSessionAppTest.java
new file mode 100644
index 0000000..85b05d3
--- /dev/null
+++ b/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/BaseRestoreSessionAppTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 Google Inc.
+ *
+ * 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.cts.backup.restoresessionapp;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BaseRestoreSessionAppTest {
+ private static final String SHARED_PREFERENCES_FILE = "restore_session_app_prefs";
+
+ private SharedPreferences mPreferences;
+
+ @Before
+ public void setUp() {
+ mPreferences =
+ getTargetContext()
+ .getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE);
+ }
+
+ protected void clearSharedPrefs() {
+ mPreferences.edit().clear().commit();
+ }
+
+ protected void checkSharedPrefsDontExist(String prefKey) {
+ assertThat(mPreferences.getInt(prefKey, 0)).isEqualTo(0);
+ }
+
+ protected void saveValuesToSharedPrefs(String prefKey, int prefValue) {
+ mPreferences.edit().putInt(prefKey, prefValue).commit();
+ }
+
+ protected void checkSharedPrefsExist(String prefKey, int prefValue) {
+ assertThat(mPreferences.getInt(prefKey, 0)).isEqualTo(prefValue);
+ }
+}
diff --git a/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/RestoreSessionTest.java b/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/RestoreSessionTest.java
new file mode 100644
index 0000000..06b9ae0
--- /dev/null
+++ b/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/RestoreSessionTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019 Google Inc.
+ *
+ * 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.cts.backup.restoresessionapp;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertNotEquals;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupManagerMonitor;
+import android.app.backup.RestoreObserver;
+import android.app.backup.RestoreSession;
+import android.app.backup.RestoreSet;
+import android.content.Context;
+import android.os.Bundle;
+
+import android.platform.test.annotations.AppModeFull;
+import androidx.test.runner.AndroidJUnit4;
+
+// import com.android.compatibility.common.util.SystemUtil;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Device side routines to be invoked by the host side RestoreSessionHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class RestoreSessionTest {
+ private static final String PACKAGE_1 = "android.cts.backup.restoresessionapp1";
+ private static final String PACKAGE_2 = "android.cts.backup.restoresessionapp2";
+ private static final String PACKAGE_3 = "android.cts.backup.restoresessionapp3";
+
+ private static final int RESTORE_TIMEOUT_SECONDS = 10;
+
+ private BackupManager mBackupManager;
+ private Set<String> mRestorePackages;
+ private Set<String> mNonRestorePackages;
+ private CountDownLatch mRestoreObserverLatch;
+ private RestoreSession mRestoreSession;
+ private UiAutomation mUiAutomation;
+ private long mRestoreToken;
+
+ private final RestoreObserver mRestoreObserver =
+ new RestoreObserver() {
+ @Override
+ public void restoreSetsAvailable(RestoreSet[] result) {
+ super.restoreSetsAvailable(result);
+
+ long token = 0L;
+
+ for (RestoreSet restoreSet : result) {
+ long restoreToken = restoreSet.token;
+ if (doesRestoreSetContainAllPackages(restoreToken, mRestorePackages)
+ && doesRestoreSetContainAllPackages(
+ restoreToken, mNonRestorePackages)) {
+ token = restoreSet.token;
+ break;
+ }
+ }
+
+ mRestoreToken = token;
+
+ mRestoreObserverLatch.countDown();
+ }
+
+ @Override
+ public void restoreStarting(int numPackages) {
+ super.restoreStarting(numPackages);
+
+ assertEquals(
+ "Wrong number of packages in the restore set",
+ mRestorePackages.size(),
+ numPackages);
+ mRestoreObserverLatch.countDown();
+ }
+
+ @Override
+ public void onUpdate(int nowBeingRestored, String currentPackage) {
+ super.onUpdate(nowBeingRestored, currentPackage);
+
+ assertTrue(
+ "Restoring package that is not in mRestorePackages",
+ mRestorePackages.contains(currentPackage));
+ mRestoreObserverLatch.countDown();
+ }
+
+ @Override
+ public void restoreFinished(int error) {
+ super.restoreFinished(error);
+
+ assertEquals(
+ "Restore finished with error: " + error, BackupManager.SUCCESS, error);
+ mRestoreSession.endRestoreSession();
+ mRestoreObserverLatch.countDown();
+ }
+ };
+
+ @Before
+ public void setUp() throws InterruptedException {
+ Context context = getTargetContext();
+ mBackupManager = new BackupManager(context);
+
+ mRestorePackages = new HashSet<>();
+ mRestorePackages.add(PACKAGE_1);
+ mRestorePackages.add(PACKAGE_2);
+
+ mNonRestorePackages = new HashSet<>();
+ mNonRestorePackages.add(PACKAGE_3);
+
+ mRestoreToken = 0L;
+
+ mUiAutomation = getInstrumentation().getUiAutomation();
+ mUiAutomation.adoptShellPermissionIdentity();
+
+ loadAvailableRestoreSets();
+ }
+
+ @After
+ public void tearDown() {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+
+ /**
+ * Restore packages added to mRestorePackages and verify only those packages are restored. Use
+ * {@link RestoreSession#restorePackages(long, RestoreObserver, Set)}
+ */
+ @Test
+ public void testRestorePackages() throws InterruptedException {
+ testRestorePackagesInternal(false);
+ }
+
+ /**
+ * Restore packages added to mRestorePackages and verify only those packages are restored. Use
+ * {@link RestoreSession#restorePackages(long, RestoreObserver, Set, BackupManagerMonitor)}
+ */
+ @Test
+ public void testRestorePackagesWithMonitorParam() throws InterruptedException {
+ testRestorePackagesInternal(true);
+ }
+
+ private void testRestorePackagesInternal(boolean useMonitorParam) throws InterruptedException {
+ // Wait for the callbacks from RestoreObserver: one for each package from
+ // mRestorePackages plus restoreStarting and restoreFinished.
+ mRestoreObserverLatch = new CountDownLatch(mRestorePackages.size() + 2);
+ CountDownLatch backupMonitorLatch = null;
+ if (useMonitorParam) {
+ // Wait for the callbacks from BackupManagerMonitor: one for each package.
+ backupMonitorLatch = new CountDownLatch(mRestorePackages.size());
+ mRestoreSession.restorePackages(
+ mRestoreToken,
+ mRestoreObserver,
+ mRestorePackages,
+ new TestBackupMonitor(backupMonitorLatch));
+ } else {
+ mRestoreSession.restorePackages(mRestoreToken, mRestoreObserver, mRestorePackages);
+ }
+
+ awaitResultAndAssertSuccess(mRestoreObserverLatch);
+ if (backupMonitorLatch != null) {
+ awaitResultAndAssertSuccess(backupMonitorLatch);
+ }
+ }
+
+ private void loadAvailableRestoreSets() throws InterruptedException {
+ // Wait for getAvailableRestoreSets to finish and the callback to be fired.
+ mRestoreObserverLatch = new CountDownLatch(1);
+ mRestoreSession = mBackupManager.beginRestoreSession();
+ assertEquals(
+ BackupManager.SUCCESS, mRestoreSession.getAvailableRestoreSets(mRestoreObserver));
+ awaitResultAndAssertSuccess(mRestoreObserverLatch);
+
+ assertNotEquals("Restore set not found", 0L, mRestoreToken);
+ }
+
+ private boolean doesRestoreSetContainAllPackages(long restoreToken, Set<String> packages) {
+ for (String restorePackage : packages) {
+ if (mBackupManager.getAvailableRestoreToken(restorePackage) != restoreToken) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void awaitResultAndAssertSuccess(CountDownLatch latch) throws InterruptedException {
+ boolean waitResult = latch.await(RESTORE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ assertTrue("Restore timed out", waitResult);
+ }
+
+ private static class TestBackupMonitor extends BackupManagerMonitor {
+ private final CountDownLatch mLatch;
+
+ TestBackupMonitor(CountDownLatch latch) {
+ mLatch = latch;
+ }
+
+ @Override
+ public void onEvent(Bundle event) {
+ super.onEvent(event);
+
+ int eventType = event.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID);
+ assertEquals(
+ "Unexpected event from BackupManagerMonitor: " + eventType,
+ BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH,
+ eventType);
+ mLatch.countDown();
+ }
+ }
+}
diff --git a/hostsidetests/backup/restoresessionapp1/Android.bp b/hostsidetests/backup/restoresessionapp1/Android.bp
new file mode 100644
index 0000000..2eb5d0e
--- /dev/null
+++ b/hostsidetests/backup/restoresessionapp1/Android.bp
@@ -0,0 +1,32 @@
+// 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.
+
+android_test_helper_app {
+ name: "CtsRestoreSessionApp1",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.test.rules",
+ "truth-prebuilt",
+ "CtsRestoreSessionApp",
+ ],
+ srcs: [
+ "src/**/*.java"
+ ],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ ],
+ sdk_version: "system_current",
+}
diff --git a/hostsidetests/backup/restoresessionapp1/AndroidManifest.xml b/hostsidetests/backup/restoresessionapp1/AndroidManifest.xml
new file mode 100644
index 0000000..ae6b205
--- /dev/null
+++ b/hostsidetests/backup/restoresessionapp1/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 Google Inc.
+ *
+ * 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 Licensea
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.restoresessionapp1">
+
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
+
+ <application
+ android:label="RestoreSessionApp"
+ android:allowBackup="true">
+
+ <uses-library android:name="android.test.runner" />
+
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.restoresessionapp1" />
+</manifest>
diff --git a/hostsidetests/backup/restoresessionapp1/src/android/cts/backup/restoresessionapp1/RestoreSessionAppTest.java b/hostsidetests/backup/restoresessionapp1/src/android/cts/backup/restoresessionapp1/RestoreSessionAppTest.java
new file mode 100644
index 0000000..f82d058
--- /dev/null
+++ b/hostsidetests/backup/restoresessionapp1/src/android/cts/backup/restoresessionapp1/RestoreSessionAppTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 Google Inc.
+ *
+ * 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.cts.backup.restoresessionapp1;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import android.cts.backup.restoresessionapp.BaseRestoreSessionAppTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Device side routines to be invoked by the host side RestoreSessionHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RestoreSessionAppTest extends BaseRestoreSessionAppTest {
+ private static final String SHARED_PREFERENCES_KEY = "test_key_1";
+ private static final int SHARED_PREFERENCES_VALUE = 123;
+
+ @Test
+ public void testClearSharedPrefs() {
+ clearSharedPrefs();
+ }
+
+ @Test
+ public void testCheckSharedPrefsDontExist() {
+ checkSharedPrefsDontExist(SHARED_PREFERENCES_KEY);
+ }
+
+ @Test
+ public void testSaveValuesToSharedPrefs() {
+ saveValuesToSharedPrefs(SHARED_PREFERENCES_KEY, SHARED_PREFERENCES_VALUE);
+ }
+
+ @Test
+ public void testCheckSharedPrefsExist() {
+ checkSharedPrefsExist(SHARED_PREFERENCES_KEY, SHARED_PREFERENCES_VALUE);
+ }
+}
diff --git a/hostsidetests/backup/restoresessionapp2/Android.bp b/hostsidetests/backup/restoresessionapp2/Android.bp
new file mode 100644
index 0000000..e389054
--- /dev/null
+++ b/hostsidetests/backup/restoresessionapp2/Android.bp
@@ -0,0 +1,32 @@
+// 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.
+
+android_test_helper_app {
+ name: "CtsRestoreSessionApp2",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.test.rules",
+ "truth-prebuilt",
+ "CtsRestoreSessionApp",
+ ],
+ srcs: [
+ "src/**/*.java"
+ ],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ ],
+ sdk_version: "system_current",
+}
diff --git a/hostsidetests/backup/restoresessionapp2/AndroidManifest.xml b/hostsidetests/backup/restoresessionapp2/AndroidManifest.xml
new file mode 100644
index 0000000..757c801
--- /dev/null
+++ b/hostsidetests/backup/restoresessionapp2/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 Google Inc.
+ *
+ * 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 Licensea
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.restoresessionapp2">
+
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
+
+ <application
+ android:label="RestoreSessionApp"
+ android:allowBackup="true"/>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.restoresessionapp2" />
+</manifest>
diff --git a/hostsidetests/backup/restoresessionapp2/src/android/cts/backup/restoresessionapp2/RestoreSessionAppTest.java b/hostsidetests/backup/restoresessionapp2/src/android/cts/backup/restoresessionapp2/RestoreSessionAppTest.java
new file mode 100644
index 0000000..458103b
--- /dev/null
+++ b/hostsidetests/backup/restoresessionapp2/src/android/cts/backup/restoresessionapp2/RestoreSessionAppTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 Google Inc.
+ *
+ * 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.cts.backup.restoresessionapp2;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import android.cts.backup.restoresessionapp.BaseRestoreSessionAppTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Device side routines to be invoked by the host side RestoreSessionHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RestoreSessionAppTest extends BaseRestoreSessionAppTest {
+ private static final String SHARED_PREFERENCES_KEY = "test_key_2";
+ private static final int SHARED_PREFERENCES_VALUE = 124;
+
+ @Test
+ public void testClearSharedPrefs() {
+ clearSharedPrefs();
+ }
+
+ @Test
+ public void testCheckSharedPrefsDontExist() {
+ checkSharedPrefsDontExist(SHARED_PREFERENCES_KEY);
+ }
+
+ @Test
+ public void testSaveValuesToSharedPrefs() {
+ saveValuesToSharedPrefs(SHARED_PREFERENCES_KEY, SHARED_PREFERENCES_VALUE);
+ }
+
+ @Test
+ public void testCheckSharedPrefsExist() {
+ checkSharedPrefsExist(SHARED_PREFERENCES_KEY, SHARED_PREFERENCES_VALUE);
+ }
+}
diff --git a/hostsidetests/backup/restoresessionapp3/Android.bp b/hostsidetests/backup/restoresessionapp3/Android.bp
new file mode 100644
index 0000000..1451823
--- /dev/null
+++ b/hostsidetests/backup/restoresessionapp3/Android.bp
@@ -0,0 +1,32 @@
+// 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.
+
+android_test_helper_app {
+ name: "CtsRestoreSessionApp3",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.test.rules",
+ "truth-prebuilt",
+ "CtsRestoreSessionApp",
+ ],
+ srcs: [
+ "src/**/*.java"
+ ],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ ],
+ sdk_version: "system_current",
+}
diff --git a/hostsidetests/backup/restoresessionapp3/AndroidManifest.xml b/hostsidetests/backup/restoresessionapp3/AndroidManifest.xml
new file mode 100644
index 0000000..027cf68
--- /dev/null
+++ b/hostsidetests/backup/restoresessionapp3/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 Google Inc.
+ *
+ * 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 Licensea
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.restoresessionapp3">
+
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
+
+ <application
+ android:label="RestoreSessionApp"
+ android:allowBackup="true"/>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.restoresessionapp3" />
+</manifest>
diff --git a/hostsidetests/backup/restoresessionapp3/src/android/cts/backup/restoresessionapp3/RestoreSessionAppTest.java b/hostsidetests/backup/restoresessionapp3/src/android/cts/backup/restoresessionapp3/RestoreSessionAppTest.java
new file mode 100644
index 0000000..b034e1a
--- /dev/null
+++ b/hostsidetests/backup/restoresessionapp3/src/android/cts/backup/restoresessionapp3/RestoreSessionAppTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 Google Inc.
+ *
+ * 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.cts.backup.restoresessionapp3;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import android.cts.backup.restoresessionapp.BaseRestoreSessionAppTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Device side routines to be invoked by the host side RestoreSessionHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RestoreSessionAppTest extends BaseRestoreSessionAppTest {
+ private static final String SHARED_PREFERENCES_KEY = "test_key_3";
+ private static final int SHARED_PREFERENCES_VALUE = 125;
+
+ @Test
+ public void testClearSharedPrefs() {
+ clearSharedPrefs();
+ }
+
+ @Test
+ public void testCheckSharedPrefsDontExist() {
+ checkSharedPrefsDontExist(SHARED_PREFERENCES_KEY);
+ }
+
+ @Test
+ public void testSaveValuesToSharedPrefs() {
+ saveValuesToSharedPrefs(SHARED_PREFERENCES_KEY, SHARED_PREFERENCES_VALUE);
+ }
+
+ @Test
+ public void testCheckSharedPrefsExist() {
+ checkSharedPrefsExist(SHARED_PREFERENCES_KEY, SHARED_PREFERENCES_VALUE);
+ }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
new file mode 100644
index 0000000..9c5d890
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.cts.backup;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.util.BackupUtils;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import java.io.IOException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Optional;
+
+/**
+ * Tests for system APIs in {@link RestoreSession}
+ *
+ * <p>These tests use the local transport.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class RestoreSessionHostSideTest extends BaseBackupHostSideTest {
+ private static final int USER_SYSTEM = 0;
+ private static final String MAIN_TEST_APP_PKG = "android.cts.backup.restoresessionapp";
+ private static final String DEVICE_MAIN_TEST_CLASS_NAME =
+ MAIN_TEST_APP_PKG + ".RestoreSessionTest";
+ private static final String MAIN_TEST_APK = "CtsRestoreSessionApp.apk";
+
+ private static final String TEST_APP_PKG_PREFIX = "android.cts.backup.restoresessionapp";
+ private static final String TEST_APP_APK_PREFIX = "CtsRestoreSessionApp";
+ private static final int TEST_APPS_COUNT = 3;
+
+ private Optional<String> mOldTransport = Optional.empty();
+ private BackupUtils mBackupUtils;
+
+ /** Switch to local transport. */
+ @Before
+ public void setUp() throws Exception {
+ mBackupUtils = getBackupUtils();
+ mOldTransport = Optional.of(setBackupTransport(mBackupUtils.getLocalTransportName()));
+ installPackage(MAIN_TEST_APK);
+ }
+
+ /** Restore transport settings to original values. */
+ @After
+ public void tearDown() throws Exception {
+ if (mOldTransport.isPresent()) {
+ setBackupTransport(mOldTransport.get());
+ mOldTransport = Optional.empty();
+
+ uninstallPackage(MAIN_TEST_APK);
+ }
+ }
+
+ /** Test {@link RestoreSession#restorePackages(long, RestoreObserver, Set)} */
+ @Test
+ public void testRestorePackages() throws Exception {
+ testRestorePackagesInternal("testRestorePackages");
+ }
+
+ /**
+ * Test {@link RestoreSession#restorePackages(long, RestoreObserver, Set, BackupManagerMonitor)}
+ */
+ @Test
+ public void testRestorePackagesWithMonitorParam() throws Exception {
+ testRestorePackagesInternal("testRestorePackagesWithMonitorParam");
+ }
+
+ /**
+ *
+ *
+ * <ol>
+ * <li>Install 3 test packages on the device
+ * <li>Write dummy values to shared preferences for each package
+ * <li>Backup each package (adb shell bmgr backupnow)
+ * <li>Clear shared preferences for each package
+ * <li>Run restore for 2 of the packages and verify that only they were restored
+ * <li>Verify that shared preferences for the 2 packages are restored correctly
+ * </ol>
+ */
+ private void testRestorePackagesInternal(String deviceTestName) throws Exception {
+ installPackage(getApkNameForTestApp(1));
+ installPackage(getApkNameForTestApp(2));
+ installPackage(getApkNameForTestApp(3));
+ //
+ // Write dummy value to shared preferences for all test packages.
+ checkRestoreSessionDeviceTestForAllApps("testSaveValuesToSharedPrefs");
+ checkRestoreSessionDeviceTestForAllApps("testCheckSharedPrefsExist");
+
+ // Backup all test packages.
+ mBackupUtils.backupNowAndAssertSuccess(getPackageNameForTestApp(1));
+ mBackupUtils.backupNowAndAssertSuccess(getPackageNameForTestApp(2));
+ mBackupUtils.backupNowAndAssertSuccess(getPackageNameForTestApp(3));
+
+ // Clear shared preferences for all test packages.
+ checkRestoreSessionDeviceTestForAllApps("testClearSharedPrefs");
+ checkRestoreSessionDeviceTestForAllApps("testCheckSharedPrefsDontExist");
+
+ runRestoreSessionDeviceTestAndAssertSuccess(
+ MAIN_TEST_APP_PKG, DEVICE_MAIN_TEST_CLASS_NAME, deviceTestName);
+
+ // Check that shared prefs are only restored (and restored correctly) for the first 2
+ // packages.
+ checkRestoreSessionDeviceTest(1, "testCheckSharedPrefsExist");
+ checkRestoreSessionDeviceTest(2, "testCheckSharedPrefsExist");
+ checkRestoreSessionDeviceTest(3, "testCheckSharedPrefsDontExist");
+
+ uninstallPackage(getPackageNameForTestApp(1));
+ uninstallPackage(getPackageNameForTestApp(2));
+ uninstallPackage(getPackageNameForTestApp(3));
+ }
+
+ /** Run the given device test for all test apps. */
+ private void checkRestoreSessionDeviceTestForAllApps(String testName)
+ throws DeviceNotAvailableException {
+ for (int appNumber = 1; appNumber <= TEST_APPS_COUNT; appNumber++) {
+ checkRestoreSessionDeviceTest(appNumber, testName);
+ }
+ }
+
+ /** Run device test with the given test name and test app number. */
+ private void checkRestoreSessionDeviceTest(int testAppNumber, String testName)
+ throws DeviceNotAvailableException {
+ String packageName = getPackageNameForTestApp(testAppNumber);
+ runRestoreSessionDeviceTestAndAssertSuccess(
+ packageName, packageName + ".RestoreSessionAppTest", testName);
+ }
+
+ private void runRestoreSessionDeviceTestAndAssertSuccess(
+ String packageName, String fullClassName, String testName)
+ throws DeviceNotAvailableException {
+ boolean result = runDeviceTests(packageName, fullClassName, testName);
+ assertTrue("Device test failed: " + testName, result);
+ }
+
+ private String getPackageNameForTestApp(int appNumber) {
+ return TEST_APP_PKG_PREFIX + appNumber;
+ }
+
+ private String getApkNameForTestApp(int appNumber) {
+ return TEST_APP_APK_PREFIX + appNumber + ".apk";
+ }
+
+ private String setBackupTransport(String transport) throws IOException {
+ return mBackupUtils.setBackupTransportForUser(transport, USER_SYSTEM);
+ }
+}
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 e7f2e68..8f77b2b 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..bd4fecc
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractRootViewActivity.java
@@ -0,0 +1,121 @@
+/*
+ * 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.os.Bundle;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.DoubleVisitor;
+
+/**
+ * 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..6775d56
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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 com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
+
+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..c36e036
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankWithTitleActivityTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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 com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
+import android.content.Intent;
+import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
+
+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..9f453b8
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
@@ -0,0 +1,1261 @@
+/*
+ * 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 com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static com.android.compatibility.common.util.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.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 com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
+import com.android.compatibility.common.util.ActivityLauncher;
+
+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..349ff36 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
@@ -25,6 +25,7 @@
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;
@@ -103,7 +104,7 @@
// but that would make the tests flaker.
/**
- * Used for testing onDataRemovalRequest.
+ * Used for testing onUserDataRemovalRequest.
*/
private DataRemovalRequest mRemovalRequest;
@@ -281,7 +282,7 @@
@Override
public void onDataRemovalRequest(DataRemovalRequest request) {
- Log.i(TAG, "onDataRemovalRequest(id=" + mId + ",req=" + request + ")");
+ Log.i(TAG, "onUserDataRemovalRequest(id=" + mId + ",req=" + request + ")");
mRemovalRequest = request;
}
@@ -292,7 +293,7 @@
}
/**
- * Gets the cached DataRemovalRequest for testing.
+ * Gets the cached UserDataRemovalRequest for testing.
*/
public DataRemovalRequest 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 1da0502..10b054b 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomView.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomView.java
@@ -21,14 +21,14 @@
import android.util.Log;
import android.view.View;
import android.view.ViewStructure;
-import android.view.contentcapture.ContentCaptureSession;
import androidx.annotation.NonNull;
import com.android.compatibility.common.util.Visitor;
/**
- * 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 {
@@ -38,37 +38,29 @@
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
+ setImportantForContentCapture(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES);
}
- public void onProvideContentCaptureStructure(@NonNull ViewStructure structure) {
- Log.v(TAG, "onProvideContentCaptureStructure(): delegate=" + mDelegate);
+ @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");
- }
- else {
- Log.d(TAG, "onProvideContentCaptureStructure(): explicitly setting class name");
- structure.setClassName(getAccessibilityClassName().toString());
- }
- }
-
- @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);
}
}
@Override
public CharSequence getAccessibilityClassName() {
- final String name = CustomView.class.getName();
- Log.d(TAG, "getAccessibilityClassName(): " + name);
- return name;
+ 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) {
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivity.java
index ad1051c..bae47e5 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;
@@ -50,13 +54,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;
@@ -126,7 +130,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 f96c3d8..e01ed72 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;
@@ -160,15 +164,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 8e97aac..ed5340c 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Helper.java
@@ -25,6 +25,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;
@@ -118,6 +119,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..ea22140
--- /dev/null
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -0,0 +1,783 @@
+/*
+ * 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.view.contentcapture.DataRemovalRequest.FLAG_IS_PREFIX;
+
+import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.RESUMED;
+
+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.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.DataRemovalRequest;
+import android.view.contentcapture.DataRemovalRequest.LocusIdRequest;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
+import com.android.compatibility.common.util.DoubleVisitor;
+
+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()
+ .removeData(new DataRemovalRequest.Builder().forEverything()
+ .build()));
+
+ final LoginActivity activity = launchActivity();
+ watcher.waitFor(RESUMED);
+
+ activity.finish();
+ watcher.waitFor(DESTROYED);
+
+ DataRemovalRequest 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()
+ .removeData(new DataRemovalRequest.Builder()
+ .addLocusId(locusId, NO_FLAGS)
+ .build()));
+
+ final LoginActivity activity = launchActivity();
+ watcher.waitFor(RESUMED);
+
+ activity.finish();
+ watcher.waitFor(DESTROYED);
+
+ DataRemovalRequest 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()
+ .removeData(new DataRemovalRequest.Builder()
+ .addLocusId(locusId1, NO_FLAGS)
+ .addLocusId(locusId2, FLAG_IS_PREFIX)
+ .build()));
+
+ final LoginActivity activity = launchActivity();
+ watcher.waitFor(RESUMED);
+
+ activity.finish();
+ watcher.waitFor(DESTROYED);
+
+ final DataRemovalRequest 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/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java b/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
index 04e6395..2101a2d 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
@@ -60,7 +60,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import java.util.List;
@@ -160,7 +159,6 @@
APP_A_FOREGROUND_ACTIVITY);
}
- @Ignore // test temporarily disabled due to bg activity start grace period introduction
@Test
public void testActivityNotBlockedwhenForegroundActivityLaunchInSameTask() throws Exception {
// Start foreground activity, and foreground activity able to launch background activity
@@ -191,7 +189,6 @@
APP_A_FOREGROUND_ACTIVITY);
}
- @Ignore // test temporarily disabled due to bg activity start grace period introduction
@Test
public void testActivityNotBlockedWhenForegroundActivityLaunchInDifferentTask()
throws Exception {
@@ -224,7 +221,6 @@
assertTaskStack(null, APP_A_BACKGROUND_ACTIVITY);
}
- @Ignore // test temporarily disabled due to bg activity start grace period introduction
@Test
@FlakyTest(bugId = 130800326)
public void testActivityBlockedWhenForegroundActivityRestartsItself() throws Exception {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
index f73d5a6..2ec92bb 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
@@ -168,7 +168,7 @@
insets.getMandatorySystemGestureInsets(),
insetsLessThanOrEqualTo(insets.getSystemGestureInsets()));
- Insets stableAndSystem = Insets.max(insets.getSystemGestureInsets(),
+ Insets stableAndSystem = Insets.max(insets.getSystemWindowInsets(),
insets.getStableInsets());
assertThat("mandatory system gesture insets must include intersection between "
+ "stable and system window insets",
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index 8601d35..a1c12be 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -929,7 +929,9 @@
"doze_always_on",
"doze_pulse_on_pick_up",
"doze_pulse_on_long_press",
- "doze_pulse_on_double_tap"
+ "doze_pulse_on_double_tap",
+ "doze_wake_screen_gesture",
+ "doze_tap_gesture"
};
private String get(String key) {
diff --git a/tests/tests/location/src/android/location/cts/ScanningSettingsTest.java b/tests/tests/location/src/android/location/cts/ScanningSettingsTest.java
new file mode 100644
index 0000000..9e35d7e
--- /dev/null
+++ b/tests/tests/location/src/android/location/cts/ScanningSettingsTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.location.cts;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.AndroidTestCase;
+
+import com.android.compatibility.common.util.CddTest;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests if system settings app provides scanning settings.
+ */
+public class ScanningSettingsTest extends AndroidTestCase {
+ private static final int TIMEOUT = 8_000; // 8 seconds
+ private static final String SETTINGS_PACKAGE = "com.android.settings";
+
+ private static final String WIFI_SCANNING_TITLE_RES =
+ "location_scanning_wifi_always_scanning_title";
+ private static final String BLUETOOTH_SCANNING_TITLE_RES =
+ "location_scanning_bluetooth_always_scanning_title";
+
+ private UiDevice mDevice;
+ private Context mContext;
+ private String mLauncherPackage;
+ private PackageManager mPackageManager;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ mPackageManager = mContext.getPackageManager();
+ final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
+ launcherIntent.addCategory(Intent.CATEGORY_HOME);
+ mLauncherPackage = mPackageManager.resolveActivity(launcherIntent,
+ PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+ }
+
+ @Test
+ @CddTest(requirement = "7.4.2/C-2-1")
+ public void testWifiScanningSettings() throws PackageManager.NameNotFoundException {
+ launchScanningSettings();
+ toggleSettingAndVerify(WIFI_SCANNING_TITLE_RES, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE);
+ }
+
+ @Test
+ @CddTest(requirement = "7.4.3/C-4-1")
+ public void testBleScanningSettings() throws PackageManager.NameNotFoundException {
+ launchScanningSettings();
+ toggleSettingAndVerify(BLUETOOTH_SCANNING_TITLE_RES,
+ Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE);
+ }
+
+ private void launchScanningSettings() {
+ // Start from the home screen
+ mDevice.pressHome();
+ mDevice.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+
+ final Intent intent = new Intent(Settings.ACTION_LOCATION_SCANNING_SETTINGS);
+ // Clear out any previous instances
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+
+ // Wait for the app to appear
+ mDevice.wait(Until.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0)), TIMEOUT);
+ }
+
+ private void toggleSettingAndVerify(String prefTitleRes, String settingKey)
+ throws PackageManager.NameNotFoundException {
+ final Resources res = mPackageManager.getResourcesForApplication(SETTINGS_PACKAGE);
+ final int resId = res.getIdentifier(prefTitleRes, "string", SETTINGS_PACKAGE);
+ final UiObject2 pref = mDevice.findObject(By.text(res.getString(resId)));
+ final ContentResolver resolver = mContext.getContentResolver();
+ final boolean checked = Settings.Global.getInt(resolver, settingKey, 0) == 1;
+
+ // Click the preference to toggle the setting.
+ pref.click();
+ mDevice.waitForIdle();
+ assertEquals(!checked, Settings.Global.getInt(resolver, settingKey, 0) == 1);
+
+ // Click the preference again to toggle the setting back.
+ pref.click();
+ mDevice.waitForIdle();
+ assertEquals(checked, Settings.Global.getInt(resolver, settingKey, 0) == 1);
+ }
+}