| /* |
| * 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.assertViewTreeFinished; |
| import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted; |
| import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared; |
| import static android.contentcaptureservice.cts.Assertions.assertWindowBoundsChanged; |
| 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.Context; |
| import android.content.ContextParams; |
| import android.content.LocusId; |
| import android.contentcaptureservice.cts.CtsContentCaptureService.Session; |
| import android.os.Bundle; |
| import android.os.SystemClock; |
| import android.platform.test.annotations.AppModeFull; |
| import android.text.Editable; |
| 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.view.inputmethod.BaseInputConnection; |
| import android.view.inputmethod.EditorInfo; |
| import android.view.inputmethod.InputConnection; |
| import android.widget.EditText; |
| 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); |
| final int taskId = activity.getTaskId(); |
| |
| 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, taskId) |
| .activityPaused(name, taskId); |
| } |
| |
| @Test |
| public void testContentCaptureSessionCache() 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); |
| |
| rootView.setContentCaptureSession(childSession); |
| // Already called getContentCaptureSession() earlier, use cached session (main). |
| assertThat(rootView.getContentCaptureSession()).isEqualTo(childSession); |
| |
| rootView.setContentCaptureSession(mainSession); |
| assertThat(rootView.getContentCaptureSession()).isEqualTo(mainSession); |
| |
| rootView.setContentCaptureSession(childSession); |
| assertThat(rootView.getContentCaptureSession()).isEqualTo(childSession); |
| }); |
| |
| final LoginActivity activity = launchActivity(); |
| watcher.waitFor(RESUMED); |
| |
| activity.finish(); |
| watcher.waitFor(DESTROYED); |
| |
| final ContentCaptureSessionId childSessionId = childSessionRef.get() |
| .getContentCaptureSessionId(); |
| |
| assertSessionId(childSessionId, activity.getRootView()); |
| } |
| |
| @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); |
| |
| // Initial 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); |
| |
| // Initial 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> unfilteredEvents = mainSession.getUnfilteredEvents(); |
| assertWindowBoundsChanged(unfilteredEvents); |
| |
| 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 EventsAssertor assertor = activity.assertInitialViewsAppeared(session); |
| |
| assertor.isAtLeast(LoginActivity.MIN_EVENTS + 2) |
| .assertContextUpdated(); |
| |
| final ContentCaptureEvent event1 = assertor.getLastEvent(); |
| final ContentCaptureContext actualContext = event1.getContentCaptureContext(); |
| assertContentCaptureContext(actualContext); |
| |
| assertor.assertContextUpdated(); |
| |
| final ContentCaptureEvent event2 = assertor.getLastEvent(); |
| 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); |
| |
| // Initial 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"); |
| }); |
| // wait to sent event |
| sleep(); |
| |
| activity.finish(); |
| watcher.waitFor(DESTROYED); |
| |
| final Session session = service.getOnlyFinishedSession(); |
| |
| final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); |
| |
| assertor.isAtLeast(LoginActivity.MIN_EVENTS + 2) |
| .assertViewTextChanged(activity.mUsername.getAutofillId(), "USER") |
| .assertViewTextChanged(activity.mPassword.getAutofillId(), "PASS"); |
| |
| activity.assertInitialViewsDisappeared(assertor); |
| } |
| |
| @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.mUsername.setText(""); |
| activity.mUsername.setText("abc"); |
| |
| activity.mPassword.setText("d"); |
| activity.mPassword.setText(""); |
| activity.mPassword.setText(""); |
| activity.mPassword.setText("de"); |
| activity.mPassword.setText("def"); |
| activity.mPassword.setText(""); |
| |
| activity.mUsername.setText("abc"); |
| }); |
| // wait to sent event |
| sleep(); |
| |
| activity.finish(); |
| watcher.waitFor(DESTROYED); |
| |
| final Session session = service.getOnlyFinishedSession(); |
| |
| final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); |
| |
| final AutofillId usernameId = activity.mUsername.getAutofillId(); |
| final AutofillId passwordId = activity.mPassword.getAutofillId(); |
| |
| assertor.isAtLeast(LoginActivity.MIN_EVENTS + 8) |
| .assertViewTextChanged(activity.mUsername.getAutofillId(), "a") |
| .assertViewTextChanged(usernameId, "ab") |
| .assertViewTextChanged(usernameId, "") |
| .assertViewTextChanged(usernameId, "abc") |
| .assertViewTextChanged(passwordId, "d") |
| .assertViewTextChanged(passwordId, "") |
| .assertViewTextChanged(passwordId, "") |
| .assertViewTextChanged(passwordId, "de") |
| .assertViewTextChanged(passwordId, "def") |
| .assertViewTextChanged(passwordId, "") |
| .assertViewTextChanged(usernameId, "abc"); |
| |
| activity.assertInitialViewsDisappeared(assertor); |
| } |
| |
| @Test |
| public void testComposingSpan_mergedEvent() 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(() -> { |
| // add text with composing span. |
| appendText(activity.mUsername, "A"); |
| appendText(activity.mUsername, "n"); |
| appendText(activity.mUsername, "d"); |
| appendText(activity.mUsername, "r"); |
| appendText(activity.mUsername, "o"); |
| appendText(activity.mUsername, "i"); |
| appendText(activity.mUsername, "d"); |
| }); |
| // wait to sent event |
| sleep(); |
| |
| activity.finish(); |
| watcher.waitFor(DESTROYED); |
| |
| final Session session = service.getOnlyFinishedSession(); |
| |
| final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); |
| |
| assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5) |
| .assertViewTextChanged(activity.mUsername.getAutofillId(), "Android"); |
| |
| activity.assertInitialViewsDisappeared(assertor); |
| } |
| |
| @Test |
| public void testComposingSpan_notMergedWithoutComposing() 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(() -> { |
| // add text with composing span. |
| appendText(activity.mUsername, "G"); |
| appendText(activity.mUsername, "o"); |
| appendText(activity.mUsername, "o"); |
| appendText(activity.mUsername, "d"); |
| |
| // append text without composing span |
| appendText(activity.mUsername, " ", false); |
| |
| // append text with composing span, again. |
| appendText(activity.mUsername, "m"); |
| appendText(activity.mUsername, "orning"); |
| }); |
| // wait to sent event |
| sleep(); |
| |
| activity.finish(); |
| watcher.waitFor(DESTROYED); |
| |
| final Session session = service.getOnlyFinishedSession(); |
| |
| final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); |
| |
| assertor.isAtLeast(LoginActivity.MIN_EVENTS + 4) |
| .assertViewTextChanged(activity.mUsername.getAutofillId(), "Good"); |
| |
| assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Good "); |
| |
| assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Good morning"); |
| |
| activity.assertInitialViewsDisappeared(assertor); |
| } |
| |
| @Test |
| public void testComposingSpan_differentEditText() 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(() -> { |
| // add text with composing span. |
| appendText(activity.mUsername, "Good"); |
| // add text with composing span on the different EditText. |
| appendText(activity.mPassword, "How"); |
| // switch again. |
| appendText(activity.mUsername, " morning"); |
| appendText(activity.mPassword, " are you"); |
| }); |
| // wait to sent event |
| sleep(); |
| |
| activity.finish(); |
| watcher.waitFor(DESTROYED); |
| |
| final Session session = service.getOnlyFinishedSession(); |
| |
| final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); |
| |
| assertor.isAtLeast(LoginActivity.MIN_EVENTS + 3) |
| .assertViewTextChanged(activity.mUsername.getAutofillId(), "Good morning") |
| .assertViewTextChanged(activity.mPassword.getAutofillId(), "How are you"); |
| |
| activity.assertInitialViewsDisappeared(assertor); |
| } |
| |
| @Test |
| public void testComposingSpan_eventsForSpanChanges() 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("Android"); |
| final InputConnection inputConnection = |
| activity.mUsername.onCreateInputConnection(new EditorInfo()); |
| |
| // These 2 should be merged. |
| inputConnection.setComposingRegion(1, 2); |
| inputConnection.setComposingRegion(1, 3); |
| |
| inputConnection.finishComposingText(); |
| activity.mUsername.setText("end"); |
| // TODO: Test setComposingText. |
| }); |
| // wait to sent event |
| sleep(); |
| |
| activity.finish(); |
| watcher.waitFor(DESTROYED); |
| |
| final Session session = service.getOnlyFinishedSession(); |
| |
| final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); |
| |
| assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5) |
| // TODO: The first two events should probably be merged. |
| .assertViewTextChanged(activity.mUsername.getAutofillId(), "Android"); |
| |
| assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Android"); |
| |
| assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Android"); |
| |
| assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "end"); |
| |
| activity.assertInitialViewsDisappeared(assertor); |
| } |
| |
| private void appendText(EditText editText, String text) { |
| appendText(editText, text, true); |
| } |
| |
| private void appendText(EditText editText, String text, boolean hasComposingSpan) { |
| Editable editable = editText.getText(); |
| String s = editable.toString() + text; |
| Editable newEditable = Editable.Factory.getInstance().newEditable(s); |
| if (hasComposingSpan) { |
| BaseInputConnection.setComposingSpans(newEditable); |
| } else { |
| BaseInputConnection.removeComposingSpans(editable); |
| } |
| editable.replace(0, editable.length() , newEditable); |
| } |
| |
| @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 testViewAppeared_withNewContext() throws Exception { |
| final CtsContentCaptureService service = enableService(); |
| final ActivityWatcher watcher = startWatcher(); |
| |
| final LoginActivity activity = launchActivity(); |
| watcher.waitFor(RESUMED); |
| |
| // Add View |
| final LinearLayout rootView = activity.getRootView(); |
| final Context newContext = activity.createContext(new ContextParams.Builder().build()); |
| final TextView child = newImportantView(newContext, "Important I am"); |
| activity.runOnUiThread(() -> rootView.addView(child)); |
| // wait to sent event |
| sleep(); |
| |
| activity.finish(); |
| watcher.waitFor(DESTROYED); |
| |
| final Session session = service.getOnlyFinishedSession(); |
| final ContentCaptureSessionId sessionId = session.id; |
| Log.v(TAG, "session id: " + sessionId); |
| final AutofillId rootId = activity.getRootView().getAutofillId(); |
| |
| final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); |
| |
| assertor.isAtLeast(LoginActivity.MIN_EVENTS + 3) |
| .assertViewTreeStarted() |
| .assertViewAppeared(sessionId, child, rootId) |
| .assertViewTreeFinished(); |
| } |
| |
| @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); |
| |
| // Wait to make sure the animation is complete |
| sleep(); |
| |
| activity.finish(); |
| watcher.waitFor(DESTROYED); |
| |
| final Session session = service.getOnlyFinishedSession(); |
| Log.v(TAG, "session id: " + session.id); |
| final ContentCaptureSessionId sessionId = session.id; |
| final View decorView = activity.getDecorView(); |
| final View grandpa1 = activity.getGrandParent(); |
| final View grandpa2 = activity.getGrandGrandParent(); |
| final AutofillId rootId = activity.getRootView().getAutofillId(); |
| |
| final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); |
| |
| assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5) |
| .assertViewTreeStarted() |
| .assertViewAppeared(sessionId, children[0], rootId) |
| .assertViewAppeared(sessionId, children[1], rootId) |
| .assertViewTreeFinished() |
| .assertViewDisappeared( |
| decorView.getAutofillId(), |
| grandpa1.getAutofillId(), grandpa2.getAutofillId(), |
| rootId, |
| activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(), |
| activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId(), |
| children[0].getAutofillId(), children[1].getAutofillId()); |
| } |
| |
| private void sleep() { |
| Log.d(TAG, "sleeping 0.5s "); |
| SystemClock.sleep(500); |
| } |
| |
| @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. |
| |
| } |