Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 84f6ed44 authored by Riddle Hsu's avatar Riddle Hsu Committed by Android (Google) Code Review
Browse files

Merge "Add transition latency test for activity switch in the same task"

parents 01958ea8 84c66416
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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"
+140 −0
Original line number Diff line number Diff line
/*
 * 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));
            }
        }
    }
}
+46 −0
Original line number Diff line number Diff line
@@ -18,10 +18,17 @@ package android.wm;

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.runner.Description;
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 @@ public class WindowManagerPerfTestBase extends WindowPerfTestBase {
            }
        }
    }

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