| /* |
| * 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.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.Assertions.removeUnexpectedEvents; |
| 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); |
| // The task id will be -1 if the Activity is finished |
| final int taskId1 = activity1.getTaskId(); |
| |
| // 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); |
| final int taskId2 = activity2.getTaskId(); |
| 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, taskId1) |
| .activityPaused(name1, taskId1) |
| .activityResumed(name2, taskId2) |
| .activityPaused(name2, taskId2) |
| .activityResumed(name1, taskId1) |
| .activityPaused(name1, taskId1); |
| |
| // 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 = removeUnexpectedEvents(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); |
| childRef.set(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(); |
| |
| new EventsAssertor(events) |
| .isAtLeast(12) |
| .assertSessionResumed() |
| .assertViewTreeStarted() |
| .assertDecorViewAppeared(decorView) |
| .assertViewAppeared(grandpa2, decorView.getAutofillId()) |
| .assertViewAppeared(grandpa1, grandpa2.getAutofillId()) |
| .assertViewAppeared(sessionId, rootView, grandpa1.getAutofillId()) |
| .assertViewAppeared(sessionId, child, rootId) |
| .assertViewTreeFinished() |
| .assertViewTreeStarted() |
| .assertViewDisappeared(child.getAutofillId()) |
| .assertViewTreeFinished() |
| .assertSessionPaused(); |
| |
| // 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(); |
| final View grandpa2 = (View) grandpa.getParent(); |
| final View decor = activity.getDecorView(); |
| |
| // Assert just the relevant events |
| new EventsAssertor(events) |
| .isAtLeast(9) |
| .assertSessionResumed() |
| .assertViewTreeStarted() |
| .assertDecorViewAppeared(decor) |
| .assertViewAppeared(sessionId, grandpa2, decor.getAutofillId()) |
| .assertViewAppeared(sessionId, grandpa, grandpa2.getAutofillId()) |
| .assertViewAppeared(sessionId, rootView, grandpa.getAutofillId()) |
| .assertViewAppeared(sessionId, child, rootView.getAutofillId()) |
| .assertViewTreeFinished() |
| .assertSessionPaused(); |
| } |
| |
| @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 View grandpa2 = (View) grandpa.getParent(); |
| final View decor = activity.getDecorView(); |
| |
| 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); |
| |
| new EventsAssertor(mainEvents) |
| .isAtLeast(8) |
| .assertSessionResumed() |
| .assertViewTreeStarted() |
| .assertDecorViewAppeared(decor) |
| .assertViewAppeared(mainSessionId, grandpa2, decor.getAutofillId()) |
| .assertViewAppeared(mainSessionId, grandpa, grandpa2.getAutofillId()) |
| .assertViewAppeared(mainSessionId, rootView, grandpa.getAutofillId()) |
| .assertViewTreeFinished() |
| .assertSessionPaused(); |
| |
| final Session childTestSession = service.getFinishedSession(childSessionId); |
| assertChildSessionContext(childTestSession, "child"); |
| final List<ContentCaptureEvent> childEvents = childTestSession.getEvents(); |
| Log.v(TAG, "childEvents(" + childEvents.size() + "): " + childEvents); |
| |
| new EventsAssertor(childEvents) |
| .isAtLeast(3) |
| .assertViewTreeStarted() |
| .assertViewAppeared(childSessionId, child, rootView.getAutofillId()) |
| .assertViewTreeFinished(); |
| |
| // 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(); |
| final View grandpa2 = (View) grandpa.getParent(); |
| final View decor = activity.getDecorView(); |
| |
| new EventsAssertor(mainEvents) |
| .assertSessionResumed() |
| .assertViewTreeStarted() |
| .assertDecorViewAppeared(decor) |
| .assertViewAppeared(grandpa2, decor.getAutofillId()) |
| .assertViewAppeared(grandpa, grandpa2.getAutofillId()) |
| .assertViewAppeared(rootView, grandpa.getAutofillId()) |
| .assertViewTreeFinished() |
| .assertSessionPaused(); |
| |
| new EventsAssertor(events1) |
| .isAtLeast(3) |
| .assertViewTreeStarted() |
| .assertViewAppeared(s1c1, rootId) |
| .assertViewTreeFinished(); |
| |
| new EventsAssertor(events2) |
| .isAtLeast(4) |
| .assertViewTreeStarted() |
| .assertViewAppeared(s2c1, rootId) |
| .assertViewAppeared(s2c2, rootId) |
| .assertViewTreeFinished(); |
| |
| new EventsAssertor(events3) |
| .isAtLeast(8) |
| .assertViewTreeStarted() |
| .assertViewAppeared(s3c1, rootId) |
| .assertViewAppeared(s3c2, rootId) |
| .assertViewTreeFinished() |
| .assertViewTreeStarted() |
| .assertViewDisappeared(s3c1.getAutofillId()) |
| .assertViewAppeared(s3c3, rootId) |
| .assertViewTreeFinished(); |
| |
| 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 |
| CtsContentCaptureService.clearServiceWatcher(); |
| 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)); |
| } |
| } |