Merge "Measure baseline IMF latency (1/n)"
This commit is contained in:
30
apct-tests/perftests/inputmethod/Android.bp
Normal file
30
apct-tests/perftests/inputmethod/Android.bp
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2020 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.
|
||||
|
||||
android_test {
|
||||
name: "ImePerfTests",
|
||||
srcs: ["src/**/*.java"],
|
||||
static_libs: [
|
||||
"androidx.test.rules",
|
||||
"androidx.annotation_annotation",
|
||||
"apct-perftests-utils",
|
||||
"collector-device-lib",
|
||||
"compatibility-device-util-axt",
|
||||
"platform-test-annotations",
|
||||
],
|
||||
test_suites: ["device-tests"],
|
||||
data: [":perfetto_artifacts"],
|
||||
platform_apis: true,
|
||||
certificate: "platform",
|
||||
}
|
||||
44
apct-tests/perftests/inputmethod/AndroidManifest.xml
Normal file
44
apct-tests/perftests/inputmethod/AndroidManifest.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2020 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.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.perftests.inputmethod">
|
||||
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
<activity android:name="android.perftests.utils.PerfTestActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.perftests.core.PERFTEST" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service android:name="android.inputmethod.ImePerfTest$BaselineIme"
|
||||
android:process=":BaselineIME"
|
||||
android:label="Baseline IME"
|
||||
android:permission="android.permission.BIND_INPUT_METHOD"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.view.InputMethod"/>
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.view.im"
|
||||
android:resource="@xml/simple_method"/>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.android.perftests.inputmethod">
|
||||
<meta-data android:name="listener" android:value="android.inputmethod.ImePerfRunPrecondition" />
|
||||
</instrumentation>
|
||||
</manifest>
|
||||
73
apct-tests/perftests/inputmethod/AndroidTest.xml
Normal file
73
apct-tests/perftests/inputmethod/AndroidTest.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2020 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.
|
||||
-->
|
||||
<configuration description="Runs ImePerfTests metric instrumentation.">
|
||||
<option name="test-suite-tag" value="apct" />
|
||||
<option name="test-suite-tag" value="apct-metric-instrumentation" />
|
||||
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
||||
<option name="cleanup-apks" value="true" />
|
||||
<option name="test-file-name" value="ImePerfTests.apk" />
|
||||
</target_preparer>
|
||||
|
||||
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
|
||||
<option name="force-skip-system-props" value="true" />
|
||||
<option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
|
||||
<option name="run-command" value="cmd window dismiss-keyguard" />
|
||||
<option name="run-command" value="cmd package compile -m speed com.android.perftests.inputmethod" />
|
||||
</target_preparer>
|
||||
|
||||
<!-- Needed for pushing the trace config file -->
|
||||
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
|
||||
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
|
||||
<option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
|
||||
<!--Install the content provider automatically when we push some file in sdcard folder.-->
|
||||
<!--Needed to avoid the installation during the test suite.-->
|
||||
<option name="push-file" key="trace_config_detailed.textproto" value="/sdcard/sample.textproto" />
|
||||
</target_preparer>
|
||||
|
||||
<!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
|
||||
<option name="isolated-storage" value="false" />
|
||||
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
|
||||
<option name="package" value="com.android.perftests.inputmethod" />
|
||||
<option name="hidden-api-checks" value="false"/>
|
||||
|
||||
<!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
|
||||
<option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
|
||||
|
||||
<!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
|
||||
<option name="instrumentation-arg" key="newRunListenerMode" value="true" />
|
||||
|
||||
<!-- Kill background operations -->
|
||||
<option name="instrumentation-arg" key="kill-bg" value="true" />
|
||||
|
||||
<!-- ProcLoadListener related arguments -->
|
||||
<!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
|
||||
<option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
|
||||
<option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
|
||||
<option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
|
||||
<option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
|
||||
|
||||
<!-- PerfettoListener related arguments -->
|
||||
<option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
|
||||
<option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
|
||||
</test>
|
||||
|
||||
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
|
||||
<option name="directory-keys" value="/data/local/tmp/ImePerfTests" />
|
||||
<!-- Needed for pulling the collected trace config on to the host -->
|
||||
<option name="pull-pattern-keys" value="perfetto_file_path" />
|
||||
</metrics_collector>
|
||||
</configuration>
|
||||
40
apct-tests/perftests/inputmethod/README.md
Normal file
40
apct-tests/perftests/inputmethod/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
## IMF performance tests
|
||||
|
||||
These tests are adaptation of Window Manager perf tests (apct-tests/perftests/windowmanager).
|
||||
|
||||
### Precondition
|
||||
To reduce the variance of the test, if `perf-setup` (platform_testing/scripts/perf-setup)
|
||||
is available, it is better to use the following instructions to lock CPU and GPU frequencies.
|
||||
```
|
||||
m perf-setup
|
||||
PERF_SETUP_PATH=/data/local/tmp/perf-setup.sh
|
||||
adb push $OUT/$PERF_SETUP_PATH $PERF_SETUP_PATH
|
||||
adb shell chmod +x $PERF_SETUP_PATH
|
||||
adb shell $PERF_SETUP_PATH
|
||||
```
|
||||
|
||||
### Example to run
|
||||
Use `atest`
|
||||
```
|
||||
atest ImePerfTests:ImePerfTest -- \
|
||||
--module-arg ImePerfTests:instrumentation-arg:profiling-iterations:=20
|
||||
|
||||
```
|
||||
Note: `instrumentation-arg:kill-bg:=true` is already defined in the AndroidText.xml
|
||||
|
||||
Use `am instrument`
|
||||
```
|
||||
adb shell am instrument -w -r -e class android.inputmethod.ImePerfTest \
|
||||
-e listener android.inputmethod.ImePerfRunPrecondition \
|
||||
-e kill-bg true \
|
||||
com.android.perftests.inputmethod/androidx.test.runner.AndroidJUnitRunner
|
||||
```
|
||||
* `kill-bg` is optional.
|
||||
|
||||
Test arguments
|
||||
- kill-bg
|
||||
* boolean: Kill background process before running test.
|
||||
- profiling-iterations
|
||||
* int: Run the extra iterations with enabling method profiling.
|
||||
- profiling-sampling
|
||||
* int: The interval (0=trace each method, default is 10) of sample profiling in microseconds.
|
||||
19
apct-tests/perftests/inputmethod/res/xml/simple_method.xml
Normal file
19
apct-tests/perftests/inputmethod/res/xml/simple_method.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2020 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.
|
||||
-->
|
||||
|
||||
<!-- Configuration info for an input method -->
|
||||
<input-method xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.inputmethod;
|
||||
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
|
||||
import static android.inputmethod.ImePerfTestBase.executeShellCommand;
|
||||
import static android.inputmethod.ImePerfTestBase.runWithShellPermissionIdentity;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.RunningAppProcessInfo;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.content.Context;
|
||||
import android.inputmethod.ImePerfTestBase.SettingsSession;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManagerPolicyConstants;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.android.internal.policy.PhoneWindow;
|
||||
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runner.Result;
|
||||
import org.junit.runner.notification.RunListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Prepare the preconditions before running performance test. */
|
||||
public class ImePerfRunPrecondition extends RunListener {
|
||||
private static final String TAG = ImePerfRunPrecondition.class.getSimpleName();
|
||||
|
||||
private static final String ARGUMENT_LOG_ONLY = "log";
|
||||
private static final String ARGUMENT_KILL_BACKGROUND = "kill-bg";
|
||||
private static final String ARGUMENT_PROFILING_ITERATIONS = "profiling-iterations";
|
||||
private static final String ARGUMENT_PROFILING_SAMPLING = "profiling-sampling";
|
||||
private static final String DEFAULT_PROFILING_ITERATIONS = "10";
|
||||
private static final String DEFAULT_PROFILING_SAMPLING_US = "10";
|
||||
private static final long KILL_BACKGROUND_WAIT_MS = 3000;
|
||||
|
||||
/** The requested iterations to run with method profiling. */
|
||||
static int sProfilingIterations;
|
||||
|
||||
/** The interval of sample profiling in microseconds. */
|
||||
static int sSamplingIntervalUs;
|
||||
|
||||
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
private long mWaitPreconditionDoneMs = 500;
|
||||
|
||||
private final SettingsSession<Integer> mStayOnWhilePluggedInSetting = new SettingsSession<>(
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0),
|
||||
value -> executeShellCommand(String.format("settings put global %s %d",
|
||||
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value)));
|
||||
|
||||
private final SettingsSession<Integer> mNavigationModeSetting = new SettingsSession<>(
|
||||
mContext.getResources().getInteger(
|
||||
com.android.internal.R.integer.config_navBarInteractionMode),
|
||||
value -> {
|
||||
final String navOverlay;
|
||||
switch (value) {
|
||||
case WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL:
|
||||
default:
|
||||
navOverlay = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
|
||||
break;
|
||||
}
|
||||
executeShellCommand("cmd overlay enable-exclusive " + navOverlay);
|
||||
});
|
||||
|
||||
/** It only executes once before all tests. */
|
||||
@Override
|
||||
public void testRunStarted(Description description) {
|
||||
final Bundle arguments = InstrumentationRegistry.getArguments();
|
||||
// If true, it only logs the method names without running.
|
||||
final boolean skip = Boolean.parseBoolean(arguments.getString(ARGUMENT_LOG_ONLY, "false"));
|
||||
Log.i(TAG, "arguments=" + arguments);
|
||||
if (skip) {
|
||||
return;
|
||||
}
|
||||
sProfilingIterations = Integer.parseInt(
|
||||
arguments.getString(ARGUMENT_PROFILING_ITERATIONS, DEFAULT_PROFILING_ITERATIONS));
|
||||
sSamplingIntervalUs = Integer.parseInt(
|
||||
arguments.getString(ARGUMENT_PROFILING_SAMPLING, DEFAULT_PROFILING_SAMPLING_US));
|
||||
|
||||
// Use same navigation mode (gesture navigation) across all devices and tests
|
||||
// for consistency.
|
||||
mNavigationModeSetting.set(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL);
|
||||
// Keep the device awake during testing.
|
||||
mStayOnWhilePluggedInSetting.set(BatteryManager.BATTERY_PLUGGED_ANY);
|
||||
|
||||
runWithShellPermissionIdentity(() -> {
|
||||
final ActivityTaskManager atm = mContext.getSystemService(ActivityTaskManager.class);
|
||||
atm.removeAllVisibleRecentTasks();
|
||||
atm.removeRootTasksWithActivityTypes(new int[] { ACTIVITY_TYPE_STANDARD,
|
||||
ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS, ACTIVITY_TYPE_UNDEFINED });
|
||||
});
|
||||
PhoneWindow.sendCloseSystemWindows(mContext, "ImePerfTests");
|
||||
|
||||
if (Boolean.parseBoolean(arguments.getString(ARGUMENT_KILL_BACKGROUND))) {
|
||||
runWithShellPermissionIdentity(this::killBackgroundProcesses);
|
||||
mWaitPreconditionDoneMs = KILL_BACKGROUND_WAIT_MS;
|
||||
}
|
||||
// Wait a while for the precondition setup to complete.
|
||||
SystemClock.sleep(mWaitPreconditionDoneMs);
|
||||
}
|
||||
|
||||
private void killBackgroundProcesses() {
|
||||
Log.i(TAG, "Killing background processes...");
|
||||
final ActivityManager am = mContext.getSystemService(ActivityManager.class);
|
||||
final List<RunningAppProcessInfo> processes = am.getRunningAppProcesses();
|
||||
if (processes == null) {
|
||||
return;
|
||||
}
|
||||
for (RunningAppProcessInfo processInfo : processes) {
|
||||
if (processInfo.importanceReasonCode == RunningAppProcessInfo.REASON_UNKNOWN
|
||||
&& processInfo.importance > RunningAppProcessInfo.IMPORTANCE_SERVICE) {
|
||||
for (String pkg : processInfo.pkgList) {
|
||||
am.forceStopPackage(pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** It only executes once after all tests. */
|
||||
@Override
|
||||
public void testRunFinished(Result result) {
|
||||
mNavigationModeSetting.close();
|
||||
mStayOnWhilePluggedInSetting.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.inputmethod;
|
||||
|
||||
import static android.perftests.utils.ManualBenchmarkState.StatsReport;
|
||||
import static android.perftests.utils.PerfTestActivity.ID_EDITOR;
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.SystemClock;
|
||||
import android.perftests.utils.ManualBenchmarkState;
|
||||
import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
|
||||
import android.perftests.utils.PerfManualStatusReporter;
|
||||
import android.perftests.utils.TraceMarkParser;
|
||||
import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsAnimation;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
||||
import com.android.compatibility.common.util.PollingCheck;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/** Measure the performance of internal methods in Input Method framework by trace tag. */
|
||||
@LargeTest
|
||||
public class ImePerfTest extends ImePerfTestBase
|
||||
implements ManualBenchmarkState.CustomizedIterationListener {
|
||||
private static final String TAG = ImePerfTest.class.getSimpleName();
|
||||
|
||||
@Rule
|
||||
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
|
||||
|
||||
@Rule
|
||||
public final PerfTestActivityRule mActivityRule = new PerfTestActivityRule();
|
||||
|
||||
/**
|
||||
* IMF common methods to log for show/hide in trace.
|
||||
*/
|
||||
private String[] mCommonMethods = {
|
||||
"IC.pendingAnim",
|
||||
"IMMS.applyImeVisibility",
|
||||
"applyPostLayoutPolicy",
|
||||
"applyWindowSurfaceChanges",
|
||||
"ISC.onPostLayout"
|
||||
};
|
||||
|
||||
/** IMF show methods to log in trace. */
|
||||
private String[] mShowMethods = {
|
||||
"IC.showRequestFromIme",
|
||||
"IC.showRequestFromApi",
|
||||
"IC.showRequestFromApiToImeReady",
|
||||
"IC.pendingAnim",
|
||||
"IMMS.applyImeVisibility",
|
||||
"IMMS.showMySoftInput",
|
||||
"IMMS.showSoftInput",
|
||||
"IMS.showSoftInput",
|
||||
"IMS.startInput",
|
||||
"WMS.showImePostLayout"
|
||||
};
|
||||
|
||||
/** IMF hide lifecycle methods to log in trace. */
|
||||
private String[] mHideMethods = {
|
||||
"IC.hideRequestFromIme",
|
||||
"IC.hideRequestFromApi",
|
||||
"IMMS.hideMySoftInput",
|
||||
"IMMS.hideSoftInput",
|
||||
"IMS.hideSoftInput",
|
||||
"WMS.hideIme"
|
||||
};
|
||||
|
||||
/**
|
||||
* IMF methods to log in trace.
|
||||
*/
|
||||
private TraceMarkParser mTraceMethods;
|
||||
|
||||
private boolean mIsTraceStarted;
|
||||
|
||||
/**
|
||||
* Ime Session for {@link BaselineIme}.
|
||||
*/
|
||||
private static class ImeSession implements AutoCloseable {
|
||||
|
||||
private static final long TIMEOUT = 2000;
|
||||
private final ComponentName mImeName;
|
||||
private Context mContext = getInstrumentation().getContext();
|
||||
|
||||
ImeSession(ComponentName ime) throws Exception {
|
||||
mImeName = ime;
|
||||
// using adb, enable and set Baseline IME.
|
||||
executeShellCommand("ime reset");
|
||||
executeShellCommand("ime enable " + ime.flattenToShortString());
|
||||
executeShellCommand("ime set " + ime.flattenToShortString());
|
||||
PollingCheck.check("Make sure that BaselineIme becomes available "
|
||||
+ getCurrentInputMethodId(), TIMEOUT,
|
||||
() -> ime.equals(getCurrentInputMethodId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
executeShellCommand("ime reset");
|
||||
PollingCheck.check("Make sure that Baseline IME becomes unavailable", TIMEOUT, () ->
|
||||
mContext.getSystemService(InputMethodManager.class)
|
||||
.getEnabledInputMethodList()
|
||||
.stream()
|
||||
.noneMatch(info -> mImeName.equals(info.getComponent())));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ComponentName getCurrentInputMethodId() {
|
||||
return ComponentName.unflattenFromString(
|
||||
Settings.Secure.getString(mContext.getContentResolver(),
|
||||
Settings.Secure.DEFAULT_INPUT_METHOD));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A minimal baseline IME (that has a single static view) used to measure IMF latency.
|
||||
*/
|
||||
public static class BaselineIme extends InputMethodService {
|
||||
|
||||
public static final int HEIGHT_DP = 100;
|
||||
|
||||
@Override
|
||||
public View onCreateInputView() {
|
||||
final ViewGroup view = new FrameLayout(this);
|
||||
final View inner = new View(this);
|
||||
final float density = getResources().getDisplayMetrics().density;
|
||||
final int height = (int) (HEIGHT_DP * density);
|
||||
view.setPadding(0, 0, 0, 0);
|
||||
view.addView(inner, new FrameLayout.LayoutParams(MATCH_PARENT, height));
|
||||
inner.setBackgroundColor(0xff01fe10); // green
|
||||
return view;
|
||||
}
|
||||
|
||||
static ComponentName getName(Context context) {
|
||||
return new ComponentName(context, BaselineIme.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ManualBenchmarkTest(
|
||||
targetTestDurationNs = 10 * TIME_1_S_IN_NS,
|
||||
statsReport = @StatsReport(
|
||||
flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
|
||||
| StatsReport.FLAG_MIN | StatsReport.FLAG_MAX
|
||||
| StatsReport.FLAG_COEFFICIENT_VAR))
|
||||
public void testShowIme() throws Throwable {
|
||||
testShowOrHideIme(true /* show */);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ManualBenchmarkTest(
|
||||
targetTestDurationNs = 10 * TIME_1_S_IN_NS,
|
||||
statsReport = @StatsReport(
|
||||
flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
|
||||
| StatsReport.FLAG_MIN | StatsReport.FLAG_MAX
|
||||
| StatsReport.FLAG_COEFFICIENT_VAR))
|
||||
public void testHideIme() throws Throwable {
|
||||
testShowOrHideIme(false /* show */);
|
||||
}
|
||||
|
||||
private void testShowOrHideIme(final boolean show) throws Throwable {
|
||||
mTraceMethods = new TraceMarkParser(buildArray(
|
||||
mCommonMethods, show ? mShowMethods : mHideMethods));
|
||||
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
state.setCustomizedIterations(getProfilingIterations(), this);
|
||||
long measuredTimeNs = 0;
|
||||
try (ImeSession imeSession = new ImeSession(BaselineIme.getName(
|
||||
getInstrumentation().getContext()))) {
|
||||
final AtomicReference<CountDownLatch> latchStart = new AtomicReference<>();
|
||||
final AtomicReference<CountDownLatch> latchEnd = new AtomicReference<>();
|
||||
final Activity activity = getActivityWithFocus();
|
||||
|
||||
// call IME show/hide
|
||||
final WindowInsetsController controller =
|
||||
activity.getWindow().getDecorView().getWindowInsetsController();
|
||||
|
||||
while (state.keepRunning(measuredTimeNs)) {
|
||||
setImeListener(activity, latchStart, latchEnd);
|
||||
latchStart.set(new CountDownLatch(show ? 1 : 2));
|
||||
latchEnd.set(new CountDownLatch(2));
|
||||
// For measuring hide, lets show IME first.
|
||||
if (!show) {
|
||||
activity.runOnUiThread(() -> {
|
||||
controller.show(WindowInsets.Type.ime());
|
||||
});
|
||||
PollingCheck.check("IME show animation should finish ", TIMEOUT_1_S_IN_MS,
|
||||
() -> latchStart.get().getCount() == 1
|
||||
&& latchEnd.get().getCount() == 1);
|
||||
}
|
||||
if (!mIsTraceStarted && !state.isWarmingUp()) {
|
||||
startAsyncAtrace();
|
||||
mIsTraceStarted = true;
|
||||
}
|
||||
|
||||
AtomicLong startTime = new AtomicLong();
|
||||
activity.runOnUiThread(() -> {
|
||||
startTime.set(SystemClock.elapsedRealtimeNanos());
|
||||
if (show) {
|
||||
controller.show(WindowInsets.Type.ime());
|
||||
} else {
|
||||
controller.hide(WindowInsets.Type.ime());
|
||||
}
|
||||
});
|
||||
|
||||
measuredTimeNs = waitForAnimationStart(latchStart, startTime);
|
||||
|
||||
// hide IME before next iteration.
|
||||
if (show) {
|
||||
activity.runOnUiThread(() -> controller.hide(WindowInsets.Type.ime()));
|
||||
try {
|
||||
latchEnd.get().await(TIMEOUT_1_S_IN_MS * 5, TimeUnit.MILLISECONDS);
|
||||
if (latchEnd.get().getCount() != 0) {
|
||||
Assert.fail("IME hide animation should finish.");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (mIsTraceStarted) {
|
||||
stopAsyncAtrace();
|
||||
}
|
||||
}
|
||||
mActivityRule.finishActivity();
|
||||
|
||||
addResultToState(state);
|
||||
}
|
||||
|
||||
private long waitForAnimationStart(
|
||||
AtomicReference<CountDownLatch> latchStart, AtomicLong startTime) {
|
||||
try {
|
||||
latchStart.get().await(TIMEOUT_1_S_IN_MS * 5, TimeUnit.MILLISECONDS);
|
||||
if (latchStart.get().getCount() != 0) {
|
||||
Assert.fail("IME animation should start " + latchStart.get().getCount());
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
|
||||
return SystemClock.elapsedRealtimeNanos() - startTime.get();
|
||||
}
|
||||
|
||||
private void addResultToState(ManualBenchmarkState state) {
|
||||
mTraceMethods.forAllSlices((key, slices) -> {
|
||||
for (TraceMarkSlice slice : slices) {
|
||||
state.addExtraResult(key, (long) (slice.getDurationInSeconds() * NANOS_PER_S));
|
||||
}
|
||||
});
|
||||
Log.i(TAG, String.valueOf(mTraceMethods));
|
||||
}
|
||||
|
||||
private Activity getActivityWithFocus() throws Exception {
|
||||
final Activity activity = mActivityRule.launchActivity();
|
||||
PollingCheck.check("Activity onResume()", TIMEOUT_1_S_IN_MS,
|
||||
() -> activity.isResumed());
|
||||
|
||||
View editor = activity.findViewById(ID_EDITOR);
|
||||
editor.requestFocus();
|
||||
|
||||
// wait till editor is focused so we don't count activity/view latency.
|
||||
PollingCheck.check("Editor is focused", TIMEOUT_1_S_IN_MS,
|
||||
() -> editor.isFocused());
|
||||
getInstrumentation().waitForIdleSync();
|
||||
|
||||
return activity;
|
||||
}
|
||||
|
||||
private void setImeListener(Activity activity,
|
||||
@NonNull AtomicReference<CountDownLatch> latchStart,
|
||||
@Nullable AtomicReference<CountDownLatch> latchEnd) {
|
||||
// set IME animation listener
|
||||
activity.getWindow().getDecorView().setWindowInsetsAnimationCallback(
|
||||
new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
|
||||
@NonNull
|
||||
@Override
|
||||
public WindowInsetsAnimation.Bounds onStart(
|
||||
@NonNull WindowInsetsAnimation animation,
|
||||
@NonNull WindowInsetsAnimation.Bounds bounds) {
|
||||
latchStart.get().countDown();
|
||||
return super.onStart(animation, bounds);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public WindowInsets onProgress(@NonNull WindowInsets insets,
|
||||
@NonNull List<WindowInsetsAnimation> runningAnimations) {
|
||||
return insets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd(@NonNull WindowInsetsAnimation animation) {
|
||||
super.onEnd(animation);
|
||||
if (latchEnd != null) {
|
||||
latchEnd.get().countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startAsyncAtrace() throws IOException {
|
||||
mIsTraceStarted = true;
|
||||
// IMF uses 'wm' component for trace in InputMethodService, InputMethodManagerService,
|
||||
// WindowManagerService and 'view' for client window (InsetsController).
|
||||
// TODO(b/167947940): Consider a separate input_method atrace
|
||||
UI_AUTOMATION.executeShellCommand("atrace -b 32768 --async_start wm view");
|
||||
// Avoid atrace isn't ready immediately.
|
||||
SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
|
||||
}
|
||||
|
||||
private void stopAsyncAtrace() {
|
||||
if (!mIsTraceStarted) {
|
||||
return;
|
||||
}
|
||||
final ParcelFileDescriptor pfd = UI_AUTOMATION.executeShellCommand("atrace --async_stop");
|
||||
mIsTraceStarted = false;
|
||||
final InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
mTraceMethods.visit(line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to read the result of stopped atrace", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(int iteration) {
|
||||
// Do not capture trace when profiling because the result will be much slower.
|
||||
stopAsyncAtrace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished(int iteration) {
|
||||
// do nothing.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.inputmethod;
|
||||
|
||||
import static android.perftests.utils.PerfTestActivity.INTENT_EXTRA_ADD_EDIT_TEXT;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.UiAutomation;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.PowerManager;
|
||||
import android.perftests.utils.PerfTestActivity;
|
||||
|
||||
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ImePerfTestBase {
|
||||
static final UiAutomation UI_AUTOMATION = getInstrumentation().getUiAutomation();
|
||||
static final long NANOS_PER_S = 1000L * 1000 * 1000;
|
||||
static final long TIME_1_S_IN_NS = 1 * NANOS_PER_S;
|
||||
static final long TIMEOUT_1_S_IN_MS = 1 * 1000L;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpOnce() {
|
||||
final Context context = getInstrumentation().getContext();
|
||||
|
||||
if (!context.getSystemService(PowerManager.class).isInteractive()
|
||||
|| context.getSystemService(KeyguardManager.class).isKeyguardLocked()) {
|
||||
executeShellCommand("input keyevent KEYCODE_WAKEUP");
|
||||
executeShellCommand("wm dismiss-keyguard");
|
||||
}
|
||||
context.startActivity(new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes shell command with reading the output. It may also used to block until the current
|
||||
* command is completed.
|
||||
*/
|
||||
static ByteArrayOutputStream executeShellCommand(String command) {
|
||||
final ParcelFileDescriptor pfd = UI_AUTOMATION.executeShellCommand(command);
|
||||
final byte[] buf = new byte[512];
|
||||
final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
int bytesRead;
|
||||
try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
|
||||
while ((bytesRead = fis.read(buf)) != -1) {
|
||||
bytes.write(buf, 0, bytesRead);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/** Returns how many iterations should run with method tracing. */
|
||||
static int getProfilingIterations() {
|
||||
return ImePerfRunPrecondition.sProfilingIterations;
|
||||
}
|
||||
|
||||
static void runWithShellPermissionIdentity(Runnable runnable) {
|
||||
UI_AUTOMATION.adoptShellPermissionIdentity();
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
UI_AUTOMATION.dropShellPermissionIdentity();
|
||||
}
|
||||
}
|
||||
|
||||
static class SettingsSession<T> implements AutoCloseable {
|
||||
private final Consumer<T> mSetter;
|
||||
private final T mOriginalValue;
|
||||
private boolean mChanged;
|
||||
|
||||
SettingsSession(T originalValue, Consumer<T> setter) {
|
||||
mOriginalValue = originalValue;
|
||||
mSetter = setter;
|
||||
}
|
||||
|
||||
void set(T value) {
|
||||
if (Objects.equals(value, mOriginalValue)) {
|
||||
mChanged = false;
|
||||
return;
|
||||
}
|
||||
mSetter.accept(value);
|
||||
mChanged = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (mChanged) {
|
||||
mSetter.accept(mOriginalValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an activity that keeps screen on and is able to wait for a stable lifecycle stage.
|
||||
*/
|
||||
static class PerfTestActivityRule extends ActivityTestRule<PerfTestActivity> {
|
||||
private final Intent mStartIntent =
|
||||
new Intent(getInstrumentation().getTargetContext(), PerfTestActivity.class);
|
||||
|
||||
PerfTestActivityRule() {
|
||||
this(false /* launchActivity */);
|
||||
}
|
||||
|
||||
PerfTestActivityRule(boolean launchActivity) {
|
||||
super(PerfTestActivity.class, false /* initialTouchMode */, launchActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent getActivityIntent() {
|
||||
return mStartIntent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PerfTestActivity launchActivity(Intent intent) {
|
||||
intent.putExtra(INTENT_EXTRA_ADD_EDIT_TEXT, true);
|
||||
return super.launchActivity(intent);
|
||||
}
|
||||
|
||||
PerfTestActivity launchActivity() {
|
||||
return launchActivity(mStartIntent);
|
||||
}
|
||||
}
|
||||
|
||||
static String[] buildArray(String[]... arrays) {
|
||||
int length = 0;
|
||||
for (String[] array : arrays) {
|
||||
length += array.length;
|
||||
}
|
||||
String[] newArray = new String[length];
|
||||
int offset = 0;
|
||||
for (String[] array : arrays) {
|
||||
System.arraycopy(array, 0, newArray, offset, array.length);
|
||||
offset += array.length;
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
/**
|
||||
* A simple activity used for testing, e.g. performance of activity switching, or as a base
|
||||
@@ -28,6 +30,8 @@ import android.view.WindowManager;
|
||||
*/
|
||||
public class PerfTestActivity extends Activity {
|
||||
public static final String INTENT_EXTRA_KEEP_SCREEN_ON = "keep_screen_on";
|
||||
public static final String INTENT_EXTRA_ADD_EDIT_TEXT = "add_edit_text";
|
||||
public static final int ID_EDITOR = 3252356;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -35,6 +39,15 @@ public class PerfTestActivity extends Activity {
|
||||
if (getIntent().getBooleanExtra(INTENT_EXTRA_KEEP_SCREEN_ON, false)) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
if (getIntent().getBooleanExtra(INTENT_EXTRA_ADD_EDIT_TEXT, false)) {
|
||||
final LinearLayout layout = new LinearLayout(this);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
final EditText editText = new EditText(this);
|
||||
editText.setId(ID_EDITOR);
|
||||
layout.addView(editText);
|
||||
setContentView(layout);
|
||||
}
|
||||
}
|
||||
|
||||
public static Intent createLaunchIntent(Context context) {
|
||||
|
||||
Reference in New Issue
Block a user