Add transition latency test for activity switch in the same task
To track the window drawn time and transition start time without
task switch.
Bug: 256141667
Test: atest WmPerfTests:android.wm.InTaskTransitionTest
Change-Id: I940971e04531328f27f8f4d23fbf648ac428c718
diff --git a/apct-tests/perftests/windowmanager/AndroidManifest.xml b/apct-tests/perftests/windowmanager/AndroidManifest.xml
index 95ede34..532a0fc 100644
--- a/apct-tests/perftests/windowmanager/AndroidManifest.xml
+++ b/apct-tests/perftests/windowmanager/AndroidManifest.xml
@@ -17,6 +17,10 @@
package="com.android.perftests.wm">
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.READ_LOGS" />
+ <!-- For perfetto trace files -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
<uses-library android:name="android.test.runner" />
@@ -26,6 +30,9 @@
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
</activity>
+
+ <activity android:name="android.wm.InTaskTransitionTest$TestActivity"
+ android:process=":test" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
new file mode 100644
index 0000000..2d2cf1c8
--- /dev/null
+++ b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 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.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.PerfTestActivity;
+import android.view.WindowManagerGlobal;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Measure the performance of warm launch activity in the same task. */
+public class InTaskTransitionTest extends WindowManagerPerfTestBase
+ implements RemoteCallback.OnResultListener {
+
+ private static final long TIMEOUT_MS = 5000;
+
+ @Rule
+ public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
+
+ private final TransitionMetricsReader mMetricsReader = new TransitionMetricsReader();
+
+ @Test
+ @ManualBenchmarkState.ManualBenchmarkTest(
+ targetTestDurationNs = 20 * TIME_1_S_IN_NS,
+ statsReport = @ManualBenchmarkState.StatsReport(
+ flags = ManualBenchmarkState.StatsReport.FLAG_ITERATION
+ | ManualBenchmarkState.StatsReport.FLAG_MEAN
+ | ManualBenchmarkState.StatsReport.FLAG_MAX))
+ public void testStartActivityInSameTask() {
+ final Context context = getInstrumentation().getContext();
+ final Activity activity = getInstrumentation().startActivitySync(
+ new Intent(context, PerfTestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ final Intent next = new Intent(context, TestActivity.class);
+ next.putExtra(TestActivity.CALLBACK, new RemoteCallback(this));
+
+ final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ long measuredTimeNs = 0;
+
+ boolean readerStarted = false;
+ while (state.keepRunning(measuredTimeNs)) {
+ if (!readerStarted && !state.isWarmingUp()) {
+ mMetricsReader.setCheckpoint();
+ readerStarted = true;
+ }
+ final long startTime = SystemClock.elapsedRealtimeNanos();
+ activity.startActivity(next);
+ synchronized (mMetricsReader) {
+ try {
+ mMetricsReader.wait(TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
+ }
+
+ for (TransitionMetricsReader.TransitionMetrics metrics : mMetricsReader.getMetrics()) {
+ if (metrics.mTransitionDelayMs > 0) {
+ state.addExtraResult("transitionDelayMs", metrics.mTransitionDelayMs);
+ }
+ if (metrics.mWindowsDrawnDelayMs > 0) {
+ state.addExtraResult("windowsDrawnDelayMs", metrics.mWindowsDrawnDelayMs);
+ }
+ }
+ }
+
+ @Override
+ public void onResult(Bundle result) {
+ // The test activity is destroyed.
+ synchronized (mMetricsReader) {
+ mMetricsReader.notifyAll();
+ }
+ }
+
+ /** The test activity runs on a different process to trigger metrics logs. */
+ public static class TestActivity extends Activity implements Runnable {
+ static final String CALLBACK = "callback";
+
+ private RemoteCallback mCallback;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCallback = getIntent().getParcelableExtra(CALLBACK, RemoteCallback.class);
+ if (mCallback != null) {
+ Looper.myLooper().getQueue().addIdleHandler(() -> {
+ new Thread(this).start();
+ return false;
+ });
+ }
+ }
+
+ @Override
+ public void run() {
+ // Wait until transition animation is finished and then finish self.
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .syncInputTransactions(true /* waitForAnimations */);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ finish();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mCallback != null) {
+ getMainThreadHandler().post(() -> mCallback.sendResult(null));
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
index 4b1982f..aea0326 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
@@ -18,10 +18,17 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS;
+
import android.app.Activity;
import android.content.Intent;
+import android.metrics.LogMaker;
+import android.metrics.MetricsReader;
import android.perftests.utils.PerfTestActivity;
import android.perftests.utils.WindowPerfTestBase;
+import android.util.SparseArray;
import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
@@ -31,6 +38,7 @@
import org.junit.runners.model.Statement;
import java.io.File;
+import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
public class WindowManagerPerfTestBase extends WindowPerfTestBase {
@@ -124,4 +132,42 @@
}
}
}
+
+ static class TransitionMetricsReader {
+ final MetricsReader mMetricsReader = new MetricsReader();
+
+ static class TransitionMetrics {
+ int mTransitionDelayMs;
+ int mWindowsDrawnDelayMs;
+ }
+
+ TransitionMetrics[] getMetrics() {
+ mMetricsReader.read(0);
+ final ArrayList<LogMaker> logs = new ArrayList<>();
+ final LogMaker logTemplate = new LogMaker(APP_TRANSITION);
+ while (mMetricsReader.hasNext()) {
+ final LogMaker b = mMetricsReader.next();
+ if (logTemplate.isSubsetOf(b)) {
+ logs.add(b);
+ }
+ }
+
+ final TransitionMetrics[] infoArray = new TransitionMetrics[logs.size()];
+ for (int i = 0; i < infoArray.length; i++) {
+ final LogMaker log = logs.get(i);
+ final SparseArray<Object> data = log.getEntries();
+ final TransitionMetrics info = new TransitionMetrics();
+ infoArray[i] = info;
+ info.mTransitionDelayMs =
+ (int) data.get(APP_TRANSITION_DELAY_MS, -1);
+ info.mWindowsDrawnDelayMs =
+ (int) data.get(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS, -1);
+ }
+ return infoArray;
+ }
+
+ void setCheckpoint() {
+ mMetricsReader.checkpoint();
+ }
+ }
}