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();
+        }
+    }
 }