blob: 4e2b281da1b415c465e8658b26862e2cec2aea23 [file] [log] [blame]
/*
* Copyright (C) 2019 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 android.perftests.utils.ManualBenchmarkState.STATS_REPORT_COEFFICIENT_VAR;
import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_ITERATION;
import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_MEAN;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.hamcrest.core.AnyOf.anyOf;
import static org.hamcrest.core.Is.is;
import android.app.Activity;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
import android.perftests.utils.PerfManualStatusReporter;
import android.perftests.utils.StubActivity;
import android.util.Pair;
import android.view.IRecentsAnimationController;
import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.WindowManager;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@RunWith(Parameterized.class)
@LargeTest
public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
private static Intent sRecentsIntent;
@Rule
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@Rule
public final ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule<>(
StubActivity.class, false /* initialTouchMode */, false /* launchActivity */);
private long mMeasuredTimeNs;
private LifecycleListener mLifecycleListener;
@Parameterized.Parameter(0)
public int intervalBetweenOperations;
@Parameterized.Parameters(name = "interval{0}ms")
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][] {
{ 0 },
{ 100 },
{ 300 },
});
}
@BeforeClass
public static void setUpClass() {
// Get the permission to invoke startRecentsActivity.
sUiAutomation.adoptShellPermissionIdentity();
final Context context = getInstrumentation().getContext();
final PackageManager pm = context.getPackageManager();
final ComponentName defaultHome = pm.getHomeActivities(new ArrayList<>());
try {
final ComponentName recentsComponent =
ComponentName.unflattenFromString(context.getResources().getString(
com.android.internal.R.string.config_recentsComponentName));
final int enabledState = pm.getComponentEnabledSetting(recentsComponent);
Assume.assumeThat(enabledState, anyOf(
is(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT),
is(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)));
final boolean homeIsRecents =
recentsComponent.getPackageName().equals(defaultHome.getPackageName());
sRecentsIntent =
new Intent().setComponent(homeIsRecents ? defaultHome : recentsComponent);
} catch (Exception e) {
Assume.assumeNoException(e);
}
}
@AfterClass
public static void tearDownClass() {
sUiAutomation.dropShellPermissionIdentity();
}
@Before
@Override
public void setUp() {
super.setUp();
final Activity testActivity = mActivityRule.launchActivity(null /* intent */);
try {
mActivityRule.runOnUiThread(() -> testActivity.getWindow()
.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
} catch (Throwable ignored) { }
mLifecycleListener = new LifecycleListener(testActivity);
ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(mLifecycleListener);
}
@After
public void tearDown() {
ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(mLifecycleListener);
}
/** Simulate the timing of touch. */
private void makeInterval() {
SystemClock.sleep(intervalBetweenOperations);
}
/**
* <pre>
* Steps:
* (1) Start recents activity (only make it visible).
* (2) Finish animation, take turns to execute (a), (b).
* (a) Move recents activity to top.
* ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_TOP})
* Move test app to top by startActivityFromRecents.
* (b) Cancel (it is similar to swipe a little distance and give up to enter recents).
* ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_ORIGINAL_POSITION})
* (3) Loop (1).
* </pre>
*/
@Test
@ManualBenchmarkTest(
warmupDurationNs = TIME_1_S_IN_NS,
targetTestDurationNs = TIME_5_S_IN_NS,
statsReportFlags =
STATS_REPORT_ITERATION | STATS_REPORT_MEAN | STATS_REPORT_COEFFICIENT_VAR)
public void testRecentsAnimation() throws Throwable {
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final IActivityTaskManager atm = ActivityTaskManager.getService();
final ArrayList<Pair<String, Boolean>> finishCases = new ArrayList<>();
// Real launch the recents activity.
finishCases.add(new Pair<>("finishMoveToTop", true));
// Return to the original top.
finishCases.add(new Pair<>("finishCancel", false));
// Ensure startRecentsActivity won't be called before finishing the animation.
final Semaphore recentsSemaphore = new Semaphore(1);
final int testActivityTaskId = mActivityRule.getActivity().getTaskId();
final IRecentsAnimationRunner.Stub anim = new IRecentsAnimationRunner.Stub() {
int mIteration;
@Override
public void onAnimationStart(IRecentsAnimationController controller,
RemoteAnimationTarget[] apps, Rect homeContentInsets,
Rect minimizedHomeBounds) throws RemoteException {
final Pair<String, Boolean> finishCase = finishCases.get(mIteration++ % 2);
final boolean moveRecentsToTop = finishCase.second;
makeInterval();
long startTime = SystemClock.elapsedRealtimeNanos();
controller.finish(moveRecentsToTop, false /* sendUserLeaveHint */);
final long elapsedTimeNsOfFinish = SystemClock.elapsedRealtimeNanos() - startTime;
mMeasuredTimeNs += elapsedTimeNsOfFinish;
state.addExtraResult(finishCase.first, elapsedTimeNsOfFinish);
if (moveRecentsToTop) {
mLifecycleListener.waitForIdleSync(Stage.STOPPED);
startTime = SystemClock.elapsedRealtimeNanos();
atm.startActivityFromRecents(testActivityTaskId, null /* options */);
final long elapsedTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
mMeasuredTimeNs += elapsedTimeNs;
state.addExtraResult("startFromRecents", elapsedTimeNs);
mLifecycleListener.waitForIdleSync(Stage.RESUMED);
}
makeInterval();
recentsSemaphore.release();
}
@Override
public void onAnimationCanceled(boolean deferredWithScreenshot) throws RemoteException {
Assume.assumeNoException(
new AssertionError("onAnimationCanceled should not be called"));
}
};
while (state.keepRunning(mMeasuredTimeNs)) {
Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
final long startTime = SystemClock.elapsedRealtimeNanos();
atm.startRecentsActivity(sRecentsIntent, null /* unused */, anim);
final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime;
mMeasuredTimeNs += elapsedTimeNsOfStart;
state.addExtraResult("start", elapsedTimeNsOfStart);
}
// Ensure the last round of animation callback is done.
recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS);
recentsSemaphore.release();
}
private static class LifecycleListener implements ActivityLifecycleCallback {
private final Activity mTargetActivity;
private Stage mWaitingStage;
private Stage mReceivedStage;
LifecycleListener(Activity activity) {
mTargetActivity = activity;
}
void waitForIdleSync(Stage state) {
synchronized (this) {
if (state != mReceivedStage) {
mWaitingStage = state;
try {
wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
} catch (InterruptedException impossible) { }
}
mWaitingStage = mReceivedStage = null;
}
getInstrumentation().waitForIdleSync();
}
@Override
public void onActivityLifecycleChanged(Activity activity, Stage stage) {
if (mTargetActivity != activity) {
return;
}
synchronized (this) {
mReceivedStage = stage;
if (mWaitingStage == mReceivedStage) {
notifyAll();
}
}
}
}
}