From 69a9eb0aa133bb0d242f864b42792532f78690b7 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Mon, 12 Dec 2022 14:22:51 +0000 Subject: [PATCH] Eliminate non interested parts of content capture perf test The ActivityTestRule#launchActivity uses startActivitySync, that will wait until the enter animation is finished. So the metrics are usually dominated by the animation duration. And because the test rapidly loops launch and finish, the animation style may be different. Then the result may not be meaningful. Because the original purpose of the test is to ensure the frame time, the timestamp at CALLBACK_COMMIT is more accurate. Also skip the the animation so the test can run faster about 80%. Bug: 258883870 Test: atest android.view.contentcapture.LoginTest Change-Id: I529672e7a60bca8420e96d6c900d95848f94108e --- .../contentcapture/AndroidManifest.xml | 2 + .../AbstractContentCapturePerfTestCase.java | 55 ++++++++++++------- .../contentcapture/CustomTestActivity.java | 34 ++++++++++++ .../view/contentcapture/LoginTest.java | 28 ++++++++-- 4 files changed, 94 insertions(+), 25 deletions(-) diff --git a/apct-tests/perftests/contentcapture/AndroidManifest.xml b/apct-tests/perftests/contentcapture/AndroidManifest.xml index 80957c78abf3f..6b566afe284d2 100644 --- a/apct-tests/perftests/contentcapture/AndroidManifest.xml +++ b/apct-tests/perftests/contentcapture/AndroidManifest.xml @@ -18,6 +18,8 @@ + diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java index 9b853fed81d3a..0ea2dafbb047e 100644 --- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java +++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java @@ -22,18 +22,20 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.compatibility.common.util.ShellUtils.runShellCommand; +import android.app.Activity; import android.app.Application; +import android.app.Instrumentation; 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.perftests.utils.PerfTestActivity; import android.provider.Settings; import android.util.Log; import androidx.annotation.NonNull; -import androidx.test.rule.ActivityTestRule; import com.android.compatibility.common.util.ActivitiesWatcher; import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher; @@ -53,18 +55,18 @@ import org.junit.runners.model.Statement; public abstract class AbstractContentCapturePerfTestCase { private static final String TAG = AbstractContentCapturePerfTestCase.class.getSimpleName(); - private static final long GENERIC_TIMEOUT_MS = 10_000; + protected static final long GENERIC_TIMEOUT_MS = 5_000; private static int sOriginalStayOnWhilePluggedIn; - private static Context sContext = getInstrumentation().getTargetContext(); + protected static final Instrumentation sInstrumentation = getInstrumentation(); + protected static final Context sContext = sInstrumentation.getTargetContext(); protected ActivitiesWatcher mActivitiesWatcher; - private MyContentCaptureService.ServiceWatcher mServiceWatcher; + /** A simple activity as the task root to reduce the noise of pause and animation time. */ + protected Activity mEntryActivity; - @Rule - public ActivityTestRule mActivityRule = - new ActivityTestRule<>(CustomTestActivity.class, false, false); + private MyContentCaptureService.ServiceWatcher mServiceWatcher; @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); @@ -220,6 +222,17 @@ public abstract class AbstractContentCapturePerfTestCase { } } + @Before + public void setUp() { + mEntryActivity = sInstrumentation.startActivitySync( + PerfTestActivity.createLaunchIntent(sInstrumentation.getContext())); + } + + @After + public void tearDown() { + mEntryActivity.finishAndRemoveTask(); + } + /** * Sets {@link MyContentCaptureService} as the service for the current user and waits until * its created, then add the perf test package into allow list. @@ -247,21 +260,25 @@ public abstract class AbstractContentCapturePerfTestCase { return launchActivity(R.layout.test_login_activity, 0); } + /** + * Returns the intent which will launch CustomTestActivity. + */ + protected Intent getLaunchIntent(int layoutId, int numViews) { + final Intent intent = new Intent(sContext, CustomTestActivity.class) + // Use NEW_TASK because the context is not activity. It is still in the same task + // of PerfTestActivity because of the same task affinity. Use NO_ANIMATION because + // this test focuses on launch time instead of animation duration. + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION); + intent.putExtra(INTENT_EXTRA_LAYOUT_ID, layoutId); + intent.putExtra(INTENT_EXTRA_CUSTOM_VIEWS, numViews); + return intent; + } + /** * 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 - } + final Intent intent = getLaunchIntent(layoutId, numViews); + return (CustomTestActivity) sInstrumentation.startActivitySync(intent); } } diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java index e509837f441a0..c24e79f511bb0 100644 --- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java +++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java @@ -19,6 +19,10 @@ package android.view.contentcapture; import android.app.Activity; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.Looper; +import android.os.RemoteCallback; +import android.view.Choreographer; +import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; @@ -31,6 +35,8 @@ import com.android.perftests.contentcapture.R; 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"; + static final String INTENT_EXTRA_FINISH_ON_IDLE = "finish"; + static final String INTENT_EXTRA_DRAW_CALLBACK = "draw_callback"; public static final int MAX_VIEWS = 500; private static final int CUSTOM_CONTAINER_LAYOUT_ID = R.layout.test_container_activity; @@ -47,6 +53,34 @@ public class CustomTestActivity extends Activity { getIntent().getIntExtra(INTENT_EXTRA_CUSTOM_VIEWS, MAX_VIEWS)); } } + + final RemoteCallback drawCallback = getIntent().getParcelableExtra( + INTENT_EXTRA_DRAW_CALLBACK, RemoteCallback.class); + if (drawCallback != null) { + getWindow().getDecorView().addOnAttachStateChangeListener( + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + Choreographer.getInstance().postCallback( + Choreographer.CALLBACK_COMMIT, + // Report that the first frame is drawn. + () -> drawCallback.sendResult(null), null /* token */); + } + + @Override + public void onViewDetachedFromWindow(View v) { + } + }); + } + + if (getIntent().getBooleanExtra(INTENT_EXTRA_FINISH_ON_IDLE, false)) { + Looper.myQueue().addIdleHandler(() -> { + // Finish without animation. + finish(); + overridePendingTransition(0 /* enterAnim */, 0 /* exitAnim */); + return false; + }); + } } private void createCustomViews(LinearLayout root, int number) { diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java index 725750976d980..aa95dfdfdf164 100644 --- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java +++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java @@ -15,9 +15,10 @@ */ 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.content.Intent; +import android.os.RemoteCallback; import android.perftests.utils.BenchmarkState; import android.view.View; @@ -80,17 +81,32 @@ public class LoginTest extends AbstractContentCapturePerfTestCase { } private void testActivityLaunchTime(int layoutId, int numViews) throws Throwable { + final Object drawNotifier = new Object(); + final Intent intent = getLaunchIntent(layoutId, numViews); + intent.putExtra(CustomTestActivity.INTENT_EXTRA_FINISH_ON_IDLE, true); + intent.putExtra(CustomTestActivity.INTENT_EXTRA_DRAW_CALLBACK, + new RemoteCallback(result -> { + synchronized (drawNotifier) { + drawNotifier.notifyAll(); + } + })); final ActivityWatcher watcher = startWatcher(); final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { - launchActivity(layoutId, numViews); + mEntryActivity.startActivity(intent); + synchronized (drawNotifier) { + try { + drawNotifier.wait(GENERIC_TIMEOUT_MS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } // Ignore the time to finish the activity state.pauseTiming(); - watcher.waitFor(CREATED); - finishActivity(); watcher.waitFor(DESTROYED); + sInstrumentation.waitForIdleSync(); state.resumeTiming(); } } @@ -142,12 +158,12 @@ public class LoginTest extends AbstractContentCapturePerfTestCase { while (state.keepRunning()) { // Only count the time of onVisibilityAggregated() state.pauseTiming(); - mActivityRule.runOnUiThread(() -> { + sInstrumentation.runOnMainSync(() -> { state.resumeTiming(); view.onVisibilityAggregated(false); state.pauseTiming(); }); - mActivityRule.runOnUiThread(() -> { + sInstrumentation.runOnMainSync(() -> { state.resumeTiming(); view.onVisibilityAggregated(true); state.pauseTiming();