diff options
author | TYM Tsai <tymtsai@google.com> | 2020-07-03 21:27:14 +0800 |
---|---|---|
committer | TYM Tsai <tymtsai@google.com> | 2020-07-24 21:09:28 +0800 |
commit | f2a8b43aa0959a3065afa81bfad75e4e6e0b6e87 (patch) | |
tree | ce9679b38816585c273e21f3d7515b9d1a3069d7 | |
parent | f3fcee4f23a3e94ea5b170b2fd23eac9ad123c53 (diff) |
Add content capture performance test cases
Bug: 153770130
Test: atest ContentCapturePerfTests
Change-Id: I6b0226ece380733b364cf4ebd0ee80daf70c56f2
9 files changed, 868 insertions, 0 deletions
diff --git a/apct-tests/perftests/contentcapture/Android.bp b/apct-tests/perftests/contentcapture/Android.bp new file mode 100644 index 000000000000..66d7348008ab --- /dev/null +++ b/apct-tests/perftests/contentcapture/Android.bp @@ -0,0 +1,28 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "ContentCapturePerfTests", + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + static_libs: [ + "androidx.test.rules", + "androidx.annotation_annotation", + "apct-perftests-utils", + "collector-device-lib-platform", + "compatibility-device-util-axt", + ], + platform_apis: true, + test_suites: ["device-tests"], +} diff --git a/apct-tests/perftests/contentcapture/AndroidManifest.xml b/apct-tests/perftests/contentcapture/AndroidManifest.xml new file mode 100644 index 000000000000..ee5577f265fa --- /dev/null +++ b/apct-tests/perftests/contentcapture/AndroidManifest.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.perftests.contentcapture"> + + <uses-sdk android:targetSdkVersion="28" /> + + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.REAL_GET_TASKS" /> + + <application> + <uses-library android:name="android.test.runner" /> + <activity android:name="android.view.contentcapture.CustomTestActivity" + android:exported="true"> + </activity> + + <service + android:name="android.view.contentcapture.MyContentCaptureService" + android:label="PERF ContentCaptureService" + android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE" + android:exported="true"> + <intent-filter> + <action android:name="android.service.contentcapture.ContentCaptureService" /> + </intent-filter> + </service> + + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.perftests.contentcapture" /> +</manifest> diff --git a/apct-tests/perftests/contentcapture/AndroidTest.xml b/apct-tests/perftests/contentcapture/AndroidTest.xml new file mode 100644 index 000000000000..d2386bb1efea --- /dev/null +++ b/apct-tests/perftests/contentcapture/AndroidTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 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. +--> +<configuration description="Runs ContentCapturePerfTests metric instrumentation."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-metric-instrumentation" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="ContentCapturePerfTests.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.perftests.contentcapture" /> + </test> +</configuration> diff --git a/apct-tests/perftests/contentcapture/res/layout/test_container_activity.xml b/apct-tests/perftests/contentcapture/res/layout/test_container_activity.xml new file mode 100644 index 000000000000..ca1a11a25e62 --- /dev/null +++ b/apct-tests/perftests/contentcapture/res/layout/test_container_activity.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/root_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:focusable="true" + android:focusableInTouchMode="true" + android:orientation="vertical" > +</LinearLayout> diff --git a/apct-tests/perftests/contentcapture/res/layout/test_login_activity.xml b/apct-tests/perftests/contentcapture/res/layout/test_login_activity.xml new file mode 100644 index 000000000000..9bab32ca2264 --- /dev/null +++ b/apct-tests/perftests/contentcapture/res/layout/test_login_activity.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/root_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:focusable="true" + android:focusableInTouchMode="true" + android:orientation="vertical" > + + <TextView + android:id="@+id/username_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Username" /> + + <EditText + android:id="@+id/username" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/password_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Password" /> + + <EditText + android:id="@+id/password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword" /> + +</LinearLayout> diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java new file mode 100644 index 000000000000..f02c96d74ac9 --- /dev/null +++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2020 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.view.contentcapture; + +import static android.view.contentcapture.CustomTestActivity.INTENT_EXTRA_CUSTOM_VIEWS; +import static android.view.contentcapture.CustomTestActivity.INTENT_EXTRA_LAYOUT_ID; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.compatibility.common.util.ShellUtils.runShellCommand; + +import android.app.Application; +import android.content.ContentCaptureOptions; +import android.content.Context; +import android.content.Intent; +import android.os.BatteryManager; +import android.os.UserHandle; +import android.perftests.utils.PerfStatusReporter; +import android.provider.Settings; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; + +import com.android.compatibility.common.util.ActivitiesWatcher; +import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher; +import com.android.perftests.contentcapture.R; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.junit.runners.model.Statement; + +/** + * Base class for all content capture tests. + */ +@LargeTest +public abstract class AbstractContentCapturePerfTestCase { + + private static final String TAG = AbstractContentCapturePerfTestCase.class.getSimpleName(); + private static final long GENERIC_TIMEOUT_MS = 10_000; + + private static int sOriginalStayOnWhilePluggedIn; + private static Context sContext = getInstrumentation().getTargetContext(); + + protected ActivitiesWatcher mActivitiesWatcher; + + private MyContentCaptureService.ServiceWatcher mServiceWatcher; + + @Rule + public ActivityTestRule<CustomTestActivity> mActivityRule = + new ActivityTestRule<>(CustomTestActivity.class, false, false); + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Rule + public TestRule mServiceDisablerRule = (base, description) -> { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + base.evaluate(); + } finally { + Log.v(TAG, "@mServiceDisablerRule: safelyDisableService()"); + safelyDisableService(); + } + } + }; + }; + + private void safelyDisableService() { + try { + resetService(); + MyContentCaptureService.resetStaticState(); + + if (mServiceWatcher != null) { + mServiceWatcher.waitOnDestroy(); + } + } catch (Throwable t) { + Log.e(TAG, "error disabling service", t); + } + } + + /** + * Sets the content capture service. + */ + private static void setService(@NonNull String service) { + final int userId = getCurrentUserId(); + Log.d(TAG, "Setting service for user " + userId + " to " + service); + // TODO(b/123540602): use @TestingAPI to get max duration constant + runShellCommand("cmd content_capture set temporary-service %d %s 119000", userId, service); + } + + /** + * Resets the content capture service. + */ + private static void resetService() { + final int userId = getCurrentUserId(); + Log.d(TAG, "Resetting back user " + userId + " to default service"); + runShellCommand("cmd content_capture set temporary-service %d", userId); + } + + private static int getCurrentUserId() { + return UserHandle.myUserId(); + } + + @BeforeClass + public static void setStayAwake() { + Log.v(TAG, "@BeforeClass: setStayAwake()"); + // Some test cases will restart the activity, and stay awake is necessary to ensure that + // the test will not screen off during the test. + // Keeping the activity screen on is not enough, screen off may occur between the activity + // finished and the next start + final int stayOnWhilePluggedIn = Settings.Global.getInt(sContext.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); + sOriginalStayOnWhilePluggedIn = -1; + if (stayOnWhilePluggedIn != BatteryManager.BATTERY_PLUGGED_ANY) { + sOriginalStayOnWhilePluggedIn = stayOnWhilePluggedIn; + // Keep the device awake during testing. + setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_ANY); + } + } + + @AfterClass + public static void resetStayAwake() { + Log.v(TAG, "@AfterClass: resetStayAwake()"); + if (sOriginalStayOnWhilePluggedIn != -1) { + setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn); + } + } + + private static void setStayOnWhilePluggedIn(int value) { + runShellCommand(String.format("settings put global %s %d", + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value)); + } + + @BeforeClass + public static void setAllowSelf() { + final ContentCaptureOptions options = new ContentCaptureOptions(null); + Log.v(TAG, "@BeforeClass: setAllowSelf(): options=" + options); + sContext.getApplicationContext().setContentCaptureOptions(options); + } + + @AfterClass + public static void unsetAllowSelf() { + Log.v(TAG, "@AfterClass: unsetAllowSelf()"); + clearOptions(); + } + + protected static void clearOptions() { + sContext.getApplicationContext().setContentCaptureOptions(null); + } + + @BeforeClass + public static void disableDefaultService() { + Log.v(TAG, "@BeforeClass: disableDefaultService()"); + setDefaultServiceEnabled(false); + } + + @AfterClass + public static void enableDefaultService() { + Log.v(TAG, "@AfterClass: enableDefaultService()"); + setDefaultServiceEnabled(true); + } + + /** + * Enables / disables the default service. + */ + private static void setDefaultServiceEnabled(boolean enabled) { + final int userId = getCurrentUserId(); + Log.d(TAG, "setDefaultServiceEnabled(user=" + userId + ", enabled= " + enabled + ")"); + runShellCommand("cmd content_capture set default-service-enabled %d %s", userId, + Boolean.toString(enabled)); + } + + @Before + public void prepareDevice() throws Exception { + Log.v(TAG, "@Before: prepareDevice()"); + + // Unlock screen. + runShellCommand("input keyevent KEYCODE_WAKEUP"); + + // Dismiss keyguard, in case it's set as "Swipe to unlock". + runShellCommand("wm dismiss-keyguard"); + + // Collapse notifications. + runShellCommand("cmd statusbar collapse"); + } + + @Before + public void registerLifecycleCallback() { + Log.v(TAG, "@Before: Registering lifecycle callback"); + final Application app = (Application) sContext.getApplicationContext(); + mActivitiesWatcher = new ActivitiesWatcher(GENERIC_TIMEOUT_MS); + app.registerActivityLifecycleCallbacks(mActivitiesWatcher); + } + + @After + public void unregisterLifecycleCallback() { + Log.d(TAG, "@After: Unregistering lifecycle callback: " + mActivitiesWatcher); + if (mActivitiesWatcher != null) { + final Application app = (Application) sContext.getApplicationContext(); + app.unregisterActivityLifecycleCallbacks(mActivitiesWatcher); + } + } + + /** + * Sets {@link MyContentCaptureService} as the service for the current user and waits until + * its created, then add the perf test package into allow list. + */ + public MyContentCaptureService enableService() throws InterruptedException { + if (mServiceWatcher != null) { + throw new IllegalStateException("There Can Be Only One!"); + } + + mServiceWatcher = MyContentCaptureService.setServiceWatcher(); + setService(MyContentCaptureService.SERVICE_NAME); + mServiceWatcher.setAllowSelf(); + return mServiceWatcher.waitOnCreate(); + } + + @NonNull + protected ActivityWatcher startWatcher() { + return mActivitiesWatcher.watch(CustomTestActivity.class); + } + + /** + * Launch test activity with default login layout + */ + protected CustomTestActivity launchActivity() { + return launchActivity(R.layout.test_login_activity, 0); + } + + /** + * Launch test activity with give layout and parameter + */ + protected CustomTestActivity launchActivity(int layoutId, int numViews) { + final Intent intent = new Intent(sContext, CustomTestActivity.class); + intent.putExtra(INTENT_EXTRA_LAYOUT_ID, layoutId); + intent.putExtra(INTENT_EXTRA_CUSTOM_VIEWS, numViews); + return mActivityRule.launchActivity(intent); + } + + protected void finishActivity() { + try { + mActivityRule.finishActivity(); + } catch (IllegalStateException e) { + // no op + } + } +} diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java new file mode 100644 index 000000000000..e509837f441a --- /dev/null +++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 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.view.contentcapture; + +import android.app.Activity; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.perftests.contentcapture.R; + +/** + * A simple activity used for testing, e.g. performance of activity switching, or as a base + * container of testing view. + */ +public class CustomTestActivity extends Activity { + public static final String INTENT_EXTRA_LAYOUT_ID = "layout_id"; + public static final String INTENT_EXTRA_CUSTOM_VIEWS = "custom_view_number"; + public static final int MAX_VIEWS = 500; + private static final int CUSTOM_CONTAINER_LAYOUT_ID = R.layout.test_container_activity; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getIntent().hasExtra(INTENT_EXTRA_LAYOUT_ID)) { + final int layoutId = getIntent().getIntExtra(INTENT_EXTRA_LAYOUT_ID, + /* defaultValue= */0); + setContentView(layoutId); + if (layoutId == CUSTOM_CONTAINER_LAYOUT_ID) { + createCustomViews(findViewById(R.id.root_view), + getIntent().getIntExtra(INTENT_EXTRA_CUSTOM_VIEWS, MAX_VIEWS)); + } + } + } + + private void createCustomViews(LinearLayout root, int number) { + LinearLayout horizontalLayout = null; + for (int i = 0; i < number; i++) { + final int j = i % 8; + if (horizontalLayout != null && j == 0) { + root.addView(horizontalLayout); + horizontalLayout = null; + } + if (horizontalLayout == null) { + horizontalLayout = createHorizontalLayout(); + } + horizontalLayout.addView(createItem(null, i)); + } + if (horizontalLayout != null) { + root.addView(horizontalLayout); + } + } + + private LinearLayout createHorizontalLayout() { + final LinearLayout layout = new LinearLayout(getApplicationContext()); + layout.setOrientation(LinearLayout.HORIZONTAL); + return layout; + } + + private LinearLayout createItem(Drawable drawable, int index) { + final LinearLayout group = new LinearLayout(getApplicationContext()); + group.setOrientation(LinearLayout.VERTICAL); + group.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT, /* weight= */ 1.0f)); + + final TextView text = new TextView(this); + text.setText("i = " + index); + group.addView(text); + + return group; + } +} diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java new file mode 100644 index 000000000000..750d3b407a12 --- /dev/null +++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2020 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.view.contentcapture; + +import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.CREATED; +import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED; + +import android.perftests.utils.BenchmarkState; +import android.view.View; + +import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher; +import com.android.perftests.contentcapture.R; + +import org.junit.Test; + +public class LoginTest extends AbstractContentCapturePerfTestCase { + + @Test + public void testLaunchActivity() throws Throwable { + enableService(); + + testActivityLaunchTime(R.layout.test_login_activity, 0); + } + + @Test + public void testLaunchActivity_contain100Views() throws Throwable { + enableService(); + + testActivityLaunchTime(R.layout.test_container_activity, 100); + } + + @Test + public void testLaunchActivity_contain300Views() throws Throwable { + enableService(); + + testActivityLaunchTime(R.layout.test_container_activity, 300); + } + + @Test + public void testLaunchActivity_contain500Views() throws Throwable { + enableService(); + + testActivityLaunchTime(R.layout.test_container_activity, 500); + } + + @Test + public void testLaunchActivity_noService() throws Throwable { + testActivityLaunchTime(R.layout.test_login_activity, 0); + } + + @Test + public void testLaunchActivity_noService_contain100Views() throws Throwable { + testActivityLaunchTime(R.layout.test_container_activity, 100); + } + + @Test + public void testLaunchActivity_noService_contain300Views() throws Throwable { + testActivityLaunchTime(R.layout.test_container_activity, 300); + } + + @Test + public void testLaunchActivity_noService_contain500Views() throws Throwable { + testActivityLaunchTime(R.layout.test_container_activity, 500); + } + + private void testActivityLaunchTime(int layoutId, int numViews) throws Throwable { + final ActivityWatcher watcher = startWatcher(); + + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + launchActivity(layoutId, numViews); + + // Ignore the time to finish the activity + state.pauseTiming(); + watcher.waitFor(CREATED); + finishActivity(); + watcher.waitFor(DESTROYED); + state.resumeTiming(); + } + } + + @Test + public void testOnVisibilityAggregated_visibleChanged() throws Throwable { + enableService(); + final CustomTestActivity activity = launchActivity(); + final View root = activity.getWindow().getDecorView(); + final View username = root.findViewById(R.id.username); + + testOnVisibilityAggregated(username); + } + + @Test + public void testOnVisibilityAggregated_visibleChanged_noService() throws Throwable { + final CustomTestActivity activity = launchActivity(); + final View root = activity.getWindow().getDecorView(); + final View username = root.findViewById(R.id.username); + + testOnVisibilityAggregated(username); + } + + @Test + public void testOnVisibilityAggregated_visibleChanged_noOptions() throws Throwable { + enableService(); + clearOptions(); + final CustomTestActivity activity = launchActivity(); + final View root = activity.getWindow().getDecorView(); + final View username = root.findViewById(R.id.username); + + testOnVisibilityAggregated(username); + } + + @Test + public void testOnVisibilityAggregated_visibleChanged_notImportant() throws Throwable { + enableService(); + final CustomTestActivity activity = launchActivity(); + final View root = activity.getWindow().getDecorView(); + final View username = root.findViewById(R.id.username); + username.setImportantForContentCapture(View.IMPORTANT_FOR_CONTENT_CAPTURE_NO); + + testOnVisibilityAggregated(username); + } + + private void testOnVisibilityAggregated(View view) throws Throwable { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + while (state.keepRunning()) { + // Only count the time of onVisibilityAggregated() + state.pauseTiming(); + mActivityRule.runOnUiThread(() -> { + state.resumeTiming(); + view.onVisibilityAggregated(false); + state.pauseTiming(); + }); + mActivityRule.runOnUiThread(() -> { + state.resumeTiming(); + view.onVisibilityAggregated(true); + state.pauseTiming(); + }); + state.resumeTiming(); + } + } +} diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java new file mode 100644 index 000000000000..b1dbb28c9501 --- /dev/null +++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2020 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.view.contentcapture; + +import android.content.ComponentName; +import android.service.contentcapture.ActivityEvent; +import android.service.contentcapture.ContentCaptureService; +import android.util.ArraySet; +import android.util.Log; +import android.util.Pair; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class MyContentCaptureService extends ContentCaptureService { + + private static final String TAG = MyContentCaptureService.class.getSimpleName(); + private static final String MY_PACKAGE = "com.android.perftests.contentcapture"; + public static final String SERVICE_NAME = MY_PACKAGE + "/" + + MyContentCaptureService.class.getName(); + + private static ServiceWatcher sServiceWatcher; + + @NonNull + public static ServiceWatcher setServiceWatcher() { + if (sServiceWatcher != null) { + throw new IllegalStateException("There Can Be Only One!"); + } + sServiceWatcher = new ServiceWatcher(); + return sServiceWatcher; + } + + public static void resetStaticState() { + sServiceWatcher = null; + } + + public static void clearServiceWatcher() { + if (sServiceWatcher != null) { + if (sServiceWatcher.mReadyToClear) { + sServiceWatcher.mService = null; + sServiceWatcher = null; + } else { + sServiceWatcher.mReadyToClear = true; + } + } + } + + @Override + public void onConnected() { + Log.i(TAG, "onConnected: sServiceWatcher=" + sServiceWatcher); + + if (sServiceWatcher == null) { + Log.e(TAG, "onConnected() without a watcher"); + return; + } + + if (!sServiceWatcher.mReadyToClear && sServiceWatcher.mService != null) { + Log.e(TAG, "onConnected(): already created: " + sServiceWatcher); + return; + } + + sServiceWatcher.mService = this; + sServiceWatcher.mCreated.countDown(); + sServiceWatcher.mReadyToClear = false; + } + + @Override + public void onDisconnected() { + Log.i(TAG, "onDisconnected: sServiceWatcher=" + sServiceWatcher); + + if (sServiceWatcher == null) { + Log.e(TAG, "onDisconnected() without a watcher"); + return; + } + if (sServiceWatcher.mService == null) { + Log.e(TAG, "onDisconnected(): no service on " + sServiceWatcher); + return; + } + + sServiceWatcher.mDestroyed.countDown(); + clearServiceWatcher(); + } + + @Override + public void onCreateContentCaptureSession(ContentCaptureContext context, + ContentCaptureSessionId sessionId) { + Log.i(TAG, "onCreateContentCaptureSession(ctx=" + context + ", session=" + sessionId); + } + + @Override + public void onDestroyContentCaptureSession(ContentCaptureSessionId sessionId) { + Log.i(TAG, "onDestroyContentCaptureSession(session=" + sessionId + ")"); + } + + @Override + public void onContentCaptureEvent(ContentCaptureSessionId sessionId, + ContentCaptureEvent event) { + Log.i(TAG, "onContentCaptureEventsRequest(session=" + sessionId + "): " + event); + } + + @Override + public void onActivityEvent(ActivityEvent event) { + Log.i(TAG, "onActivityEvent(): " + event); + } + + public static final class ServiceWatcher { + + private static final long GENERIC_TIMEOUT_MS = 10_000; + private final CountDownLatch mCreated = new CountDownLatch(1); + private final CountDownLatch mDestroyed = new CountDownLatch(1); + private boolean mReadyToClear = true; + private Pair<Set<String>, Set<ComponentName>> mAllowList; + + private MyContentCaptureService mService; + + @NonNull + public MyContentCaptureService waitOnCreate() throws InterruptedException { + await(mCreated, "not created"); + + if (mService == null) { + throw new IllegalStateException("not created"); + } + + if (mAllowList != null) { + Log.d(TAG, "Allow after created: " + mAllowList); + mService.setContentCaptureWhitelist(mAllowList.first, mAllowList.second); + } + + return mService; + } + + public void waitOnDestroy() throws InterruptedException { + await(mDestroyed, "not destroyed"); + } + + /** + * Allow just this package. + */ + public void setAllowSelf() { + final ArraySet<String> pkgs = new ArraySet<>(1); + pkgs.add(MY_PACKAGE); + mAllowList = new Pair<>(pkgs, null); + } + + @Override + public String toString() { + return "mService: " + mService + " created: " + (mCreated.getCount() == 0) + + " destroyed: " + (mDestroyed.getCount() == 0); + } + + /** + * Awaits for a latch to be counted down. + */ + private static void await(@NonNull CountDownLatch latch, @NonNull String fmt, + @Nullable Object... args) + throws InterruptedException { + final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS); + if (!called) { + throw new IllegalStateException(String.format(fmt, args) + + " in " + GENERIC_TIMEOUT_MS + "ms"); + } + } + } +} |