Merge "Merge Android 12"

This commit is contained in:
Xin Li
2021-10-07 23:50:15 +00:00
committed by Gerrit Code Review
12286 changed files with 1154033 additions and 491865 deletions

4
.gitignore vendored
View File

@@ -2,4 +2,6 @@
.project
*.iml
*.sw*
gen/
gen/
.vscode/
*.code-workspace

View File

@@ -1,6 +1,6 @@
drops {
android_build_drop {
build_id: "7396576"
build_id: "7552332"
target: "CtsShim"
source_file: "aosp_arm64/CtsShimPriv.apk"
}
@@ -10,4 +10,6 @@ drops {
git_project: "platform/frameworks/base"
git_branch: "sc-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@@ -1,6 +1,6 @@
drops {
android_build_drop {
build_id: "7396576"
build_id: "7552332"
target: "CtsShim"
source_file: "aosp_arm64/CtsShim.apk"
}
@@ -10,4 +10,6 @@ drops {
git_project: "platform/frameworks/base"
git_branch: "sc-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@@ -1,6 +1,6 @@
drops {
android_build_drop {
build_id: "7396576"
build_id: "7552332"
target: "CtsShim"
source_file: "aosp_x86_64/CtsShimPriv.apk"
}
@@ -10,4 +10,6 @@ drops {
git_project: "platform/frameworks/base"
git_branch: "sc-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@@ -1,6 +1,6 @@
drops {
android_build_drop {
build_id: "7396576"
build_id: "7552332"
target: "CtsShim"
source_file: "aosp_x86_64/CtsShim.apk"
}
@@ -10,4 +10,6 @@ drops {
git_project: "platform/frameworks/base"
git_branch: "sc-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@@ -105,13 +105,17 @@ filegroup {
":android.security.authorization-java-source",
":android.security.legacykeystore-java-source",
":android.security.maintenance-java-source",
":android.security.metrics-java-source",
":android.system.keystore2-V1-java-source",
":credstore_aidl",
":dumpstate_aidl",
":framework_native_aidl",
":gatekeeper_aidl",
":gsiservice_aidl",
":idmap2_aidl",
":idmap2_core_aidl",
":incidentcompanion_aidl",
":inputconstants_aidl",
":installd_aidl",
":libaudioclient_aidl",
":libbinder_aidl",
@@ -122,6 +126,7 @@ filegroup {
":resourcemanager_aidl",
":storaged_aidl",
":vold_aidl",
":deviceproductinfoconstants_aidl",
// For the generated R.java and Manifest.java
":framework-res{.aapt.srcjar}",
@@ -137,9 +142,14 @@ java_library {
name: "framework-updatable-stubs-module_libs_api",
static_libs: [
"android.net.ipsec.ike.stubs.module_lib",
"framework-appsearch.stubs.module_lib",
"framework-connectivity.stubs.module_lib",
"framework-graphics.stubs.module_lib",
"framework-media.stubs.module_lib",
"framework-mediaprovider.stubs.module_lib",
"framework-permission.stubs.module_lib",
"framework-permission-s.stubs.module_lib",
"framework-scheduling.stubs.module_lib",
"framework-sdkextensions.stubs.module_lib",
"framework-statsd.stubs.module_lib",
"framework-tethering.stubs.module_lib",
@@ -155,8 +165,13 @@ java_library {
static_libs: [
"android.net.ipsec.ike.impl",
"framework-minus-apex",
"framework-appsearch.impl",
"framework-connectivity.impl",
"framework-graphics.impl",
"framework-mediaprovider.impl",
"framework-permission.impl",
"framework-permission-s.impl",
"framework-scheduling.impl",
"framework-sdkextensions.impl",
"framework-statsd.impl",
"framework-tethering.impl",
@@ -218,6 +233,7 @@ java_library {
"android.hardware.cas-V1.2-java",
"android.hardware.contexthub-V1.0-java",
"android.hardware.contexthub-V1.1-java",
"android.hardware.contexthub-V1.2-java",
"android.hardware.gnss-V1.0-java",
"android.hardware.gnss-V2.1-java",
"android.hardware.health-V1.0-java-constants",
@@ -240,14 +256,18 @@ java_library {
"android.hardware.thermal-V2.0-java",
"android.hardware.tv.input-V1.0-java-constants",
"android.hardware.tv.tuner-V1.0-java-constants",
"android.hardware.tv.tuner-V1.1-java-constants",
"android.hardware.usb-V1.0-java-constants",
"android.hardware.usb-V1.1-java-constants",
"android.hardware.usb-V1.2-java-constants",
"android.hardware.usb.gadget-V1.0-java",
"android.hardware.usb.gadget-V1.1-java",
"android.hardware.usb.gadget-V1.2-java",
"android.hardware.vibrator-V1.0-java",
"android.hardware.vibrator-V1.1-java",
"android.hardware.vibrator-V1.2-java",
"android.hardware.vibrator-V1.3-java",
"android.hardware.vibrator-V2-java",
"android.system.suspend.control.internal-java",
"devicepolicyprotosnano",
@@ -268,27 +288,34 @@ filegroup {
visibility: ["//visibility:public"],
}
java_library {
name: "framework-minus-apex",
java_defaults {
name: "framework-minus-apex-defaults",
defaults: ["framework-aidl-export-defaults"],
srcs: [
":framework-non-updatable-sources",
":framework-connectivity-sources",
"core/java/**/*.logtags",
],
aidl: {
generate_get_transaction_name: true,
local_include_dirs: [
"media/aidl",
],
include_dirs: [
"frameworks/av/aidl",
"frameworks/native/libs/permission/aidl",
"packages/modules/Connectivity/framework/aidl-export",
],
},
dxflags: [
"--core-library",
"--multi-dex",
],
installable: true,
jarjar_rules: ":framework-jarjar-rules",
javac_shard_size: 150,
plugins: [
"view-inspector-annotation-processor",
"staledataclass-annotation-processor",
"error_prone_android_framework",
],
required: [
"framework-platform-compat-config",
@@ -299,6 +326,7 @@ java_library {
"services-platform-compat-config",
"TeleService-platform-compat-config",
"documents-ui-compat-config",
"calendar-provider-compat-config",
],
libs: [
"app-compat-annotations",
@@ -313,21 +341,44 @@ java_library {
// If MimeMap ever becomes its own APEX, then this dependency would need to be removed
// in favor of an API stubs dependency in java_library "framework" below.
"mimemap",
"av-types-aidl-java",
"tv_tuner_resource_manager_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
"modules-utils-preconditions",
"modules-utils-os",
"framework-permission-aidl-java",
],
}
java_library {
name: "framework-minus-apex",
defaults: ["framework-minus-apex-defaults"],
installable: true,
// For backwards compatibility.
stem: "framework",
apex_available: ["//apex_available:platform"],
visibility: [
"//frameworks/base",
// TODO: remove when framework-connectivity can build against API
"//frameworks/base/packages/Connectivity/framework",
// TODO(b/147128803) remove the below lines
"//frameworks/base/apex/appsearch/framework",
"//frameworks/base/apex/blobstore/framework",
"//frameworks/base/apex/jobscheduler/framework",
"//frameworks/base/packages/Tethering/tests/unit",
"//packages/modules/Connectivity/Tethering/tests/unit",
],
errorprone: {
javacflags: [
"-Xep:AndroidFrameworkBinderIdentity:ERROR",
"-Xep:AndroidFrameworkCompatChange:ERROR",
"-Xep:AndroidFrameworkUid:ERROR",
],
},
}
java_library {
name: "framework-minus-apex-intdefs",
defaults: ["framework-minus-apex-defaults"],
plugins: ["intdef-annotation-processor"],
}
// This "framework" module is NOT installed to the device. It's
@@ -346,6 +397,7 @@ java_library {
static_libs: [
"app-compat-annotations",
"framework-minus-apex",
"framework-appsearch.impl", // TODO(b/146218515): should be removed
"framework-updatable-stubs-module_libs_api",
],
sdk_version: "core_platform",
@@ -374,13 +426,14 @@ filegroup {
srcs: [
// TODO: remove these annotations as soon as we can use andoid.support.annotations.*
":framework-annotations",
":modules-utils-preconditions-srcs",
"core/java/android/net/DhcpResults.java",
"core/java/android/util/IndentingPrintWriter.java",
"core/java/android/util/LocalLog.java",
"core/java/com/android/internal/util/HexDump.java",
"core/java/com/android/internal/util/IndentingPrintWriter.java",
"core/java/com/android/internal/util/IState.java",
"core/java/com/android/internal/util/MessageUtils.java",
"core/java/com/android/internal/util/Preconditions.java",
"core/java/com/android/internal/util/RingBufferIndices.java",
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
@@ -408,9 +461,8 @@ java_library {
java_library {
name: "framework-wifi-util-lib",
sdk_version: "module_current",
min_sdk_version: "30",
srcs: [
"core/java/android/content/pm/BaseParceledListSlice.java",
"core/java/android/content/pm/ParceledListSlice.java",
"core/java/com/android/internal/util/AsyncChannel.java",
"core/java/com/android/internal/util/AsyncService.java",
"core/java/com/android/internal/util/Protocol.java",
@@ -484,9 +536,12 @@ stubs_defaults {
aidl: {
local_include_dirs: [
"apex/media/aidl/stable",
"media/aidl",
"telephony/java",
],
include_dirs: [
"frameworks/av/aidl",
"frameworks/native/libs/permission/aidl",
"packages/modules/Connectivity/framework/aidl-export",
],
},
@@ -503,6 +558,7 @@ stubs_defaults {
"android.hardware.thermal-V2.0-java",
"android.hardware.tv.input-V1.0-java-constants",
"android.hardware.tv.tuner-V1.0-java-constants",
"android.hardware.tv.tuner-V1.1-java-constants",
"android.hardware.usb-V1.0-java-constants",
"android.hardware.usb-V1.1-java-constants",
"android.hardware.usb.gadget-V1.0-java",

View File

@@ -101,12 +101,14 @@ stubs_defaults {
"metalava-manual",
],
write_sdk_values: true,
}
droidstubs {
name: "android-non-updatable-doc-stubs",
defaults: ["android-non-updatable-doc-stubs-defaults"],
args: metalava_framework_docs_args,
// TODO(b/169090544): remove below aidl includes.
aidl: {
local_include_dirs: ["media/aidl"],
include_dirs: [
"frameworks/av/aidl",
"frameworks/native/libs/permission/aidl",
],
},
}
// Defaults module for doc-stubs targets that use module source code as input.
@@ -115,9 +117,13 @@ stubs_defaults {
name: "framework-doc-stubs-sources-default",
defaults: ["framework-doc-stubs-default"],
srcs: [
":framework-appsearch-sources",
":framework-connectivity-sources",
":framework-graphics-srcs",
":framework-mediaprovider-sources",
":framework-permission-sources",
":framework-permission-s-sources",
":framework-scheduling-sources",
":framework-sdkextensions-sources",
":framework-statsd-sources",
":framework-tethering-srcs",
@@ -127,6 +133,12 @@ stubs_defaults {
],
}
droidstubs {
name: "android-non-updatable-doc-stubs",
defaults: ["android-non-updatable-doc-stubs-defaults"],
args: metalava_framework_docs_args,
}
droidstubs {
name: "android-non-updatable-doc-stubs-system",
defaults: ["android-non-updatable-doc-stubs-defaults"],
@@ -157,10 +169,14 @@ droidstubs {
args: metalava_framework_docs_args,
srcs: [
":android.net.ipsec.ike{.public.stubs.source}",
":framework-appsearch{.public.stubs.source}",
":framework-connectivity{.public.stubs.source}",
":framework-graphics{.public.stubs.source}",
":framework-media{.public.stubs.source}",
":framework-mediaprovider{.public.stubs.source}",
":framework-permission{.public.stubs.source}",
":framework-permission-s{.public.stubs.source}",
":framework-scheduling{.public.stubs.source}",
":framework-sdkextensions{.public.stubs.source}",
":framework-statsd{.public.stubs.source}",
":framework-tethering{.public.stubs.source}",
@@ -221,6 +237,14 @@ doc_defaults {
":current-support-api",
":current-androidx-api",
],
// TODO(b/169090544): remove below aidl includes.
aidl: {
local_include_dirs: ["media/aidl"],
include_dirs: [
"frameworks/av/aidl",
"frameworks/native/libs/permission/aidl",
],
},
}
doc_defaults {

View File

@@ -7,11 +7,16 @@ bpfmt = true
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
cmds/hid/
cmds/input/
cmds/uinput/
core/jni/
libs/hwui/
libs/input/
native/
services/core/jni/
services/incremental/
tests/
tools/
bpfmt = -d
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}

View File

@@ -35,9 +35,11 @@ gensrcs {
srcs: [
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
":libtombstone_proto-src",
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
":service-permission-protos",
],
output_extension: "srcjar",
}
@@ -63,8 +65,10 @@ gensrcs {
srcs: [
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
":service-permission-protos",
],
output_extension: "proto.h",
@@ -76,12 +80,14 @@ java_library_host {
srcs: [
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
":libstats_internal_protos",
":statsd_internal_protos",
"cmds/am/proto/instrumentation_data.proto",
"cmds/statsd/src/**/*.proto",
"core/proto/**/*.proto",
"libs/incident/proto/**/*.proto",
":service-permission-protos",
],
proto: {
include_dirs: [
@@ -90,8 +96,9 @@ java_library_host {
],
type: "full",
},
// Protos have lots of MissingOverride and similar.
errorprone: {
javacflags: ["-Xep:MissingOverride:OFF"], // b/72714520
javacflags: ["-XepDisableAllChecks"],
},
}
@@ -106,13 +113,16 @@ java_library {
exclude_srcs: [
"core/proto/android/privacy.proto",
"core/proto/android/section.proto",
"core/proto/android/typedef.proto",
],
sdk_version: "9",
srcs: [
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
"core/proto/**/*.proto",
"libs/incident/proto/android/os/**/*.proto",
":service-permission-protos",
],
}
@@ -127,12 +137,15 @@ java_library {
srcs: [
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
"core/proto/**/*.proto",
"libs/incident/proto/android/os/**/*.proto",
":service-permission-protos",
],
exclude_srcs: [
"core/proto/android/privacy.proto",
"core/proto/android/section.proto",
"core/proto/android/typedef.proto",
],
sdk_version: "core_current",
// Protos have lots of MissingOverride and similar.
@@ -161,7 +174,9 @@ cc_defaults {
srcs: [
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
"core/proto/**/*.proto",
":service-permission-protos",
],
}

View File

@@ -205,6 +205,7 @@ droidstubs {
api_lint: {
enabled: true,
new_since: ":android.api.module-lib.latest",
baseline_file: "core/api/module-lib-lint-baseline.txt",
},
},
dists: [
@@ -238,10 +239,14 @@ modules_public_stubs = [
"android.net.ipsec.ike.stubs",
"art.module.public.api.stubs",
"conscrypt.module.public.api.stubs",
"framework-appsearch.stubs",
"framework-connectivity.stubs",
"framework-graphics.stubs",
"framework-media.stubs",
"framework-mediaprovider.stubs",
"framework-permission.stubs",
"framework-permission-s.stubs",
"framework-scheduling.stubs",
"framework-sdkextensions.stubs",
"framework-statsd.stubs",
"framework-tethering.stubs",
@@ -253,10 +258,14 @@ modules_system_stubs = [
"android.net.ipsec.ike.stubs.system",
"art.module.public.api.stubs.system",
"conscrypt.module.public.api.stubs", // Only has public stubs
"framework-appsearch.stubs.system",
"framework-connectivity.stubs.system",
"framework-graphics.stubs.system",
"framework-media.stubs.system",
"framework-mediaprovider.stubs.system",
"framework-permission.stubs.system",
"framework-permission-s.stubs.system",
"framework-scheduling.stubs.system",
"framework-sdkextensions.stubs.system",
"framework-statsd.stubs.system",
"framework-tethering.stubs.system",

View File

@@ -1,4 +1,20 @@
{
"presubmit-large": [
{
"name": "FrameworksServicesTests",
"options": [
{
"include-annotation": "android.platform.test.annotations.Presubmit"
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"exclude-annotation": "org.junit.Ignore"
}
]
}
],
"presubmit": [
{
"name": "FrameworksUiServicesTests",
@@ -39,7 +55,7 @@
]
},
{
"name": "FrameworksServicesTests",
"name": "FrameworkPermissionTests",
"options": [
{
"include-annotation": "android.platform.test.annotations.Presubmit"
@@ -51,6 +67,17 @@
"exclude-annotation": "org.junit.Ignore"
}
]
},
{
"name": "FrameworksInProcessTests",
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"exclude-annotation": "org.junit.Ignore"
}
]
}
],
"postsubmit-managedprofile-stress": [

View File

@@ -16,6 +16,7 @@ java_library_host {
name: "platformtestprotos",
srcs: [
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
":libstats_internal_protos",
":statsd_internal_protos",
],

View File

@@ -29,7 +29,10 @@ android_test {
"androidx.test.rules",
"androidx.annotation_annotation",
"apct-perftests-utils",
"compatibility-device-util-axt",
"collector-device-lib",
],
platform_apis: true,
test_suites: ["device-tests"],
data: [":perfetto_artifacts"],
}

View File

@@ -18,7 +18,8 @@
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.PerfTestActivity">
<activity android:name="android.perftests.utils.PerfTestActivity"
android:exported="true">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
@@ -27,7 +28,8 @@
<service
android:name="android.view.autofill.MyAutofillService"
android:label="PERF AutofillService"
android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
android:permission="android.permission.BIND_AUTOFILL_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>

View File

@@ -21,8 +21,40 @@
<option name="test-file-name" value="AutofillPerfTests.apk" />
</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" />
</target_preparer>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="perfetto_file_path" />
</metrics_collector>
<!-- 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.autofill" />
<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" />
<!-- 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" />
<option name="instrumentation-arg" key="newRunListenerMode" value="true" />
</test>
</configuration>

View File

@@ -11,46 +11,54 @@
* 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
* limitations under the License.
*/
package android.view.autofill;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static org.junit.Assert.assertTrue;
import android.os.Looper;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.PerfTestActivity;
import android.perftests.utils.SettingsHelper;
import android.perftests.utils.SettingsStateKeeperRule;
import android.provider.Settings;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.rules.RuleChain;
/**
* Base class for all autofill tests.
*/
@LargeTest
public abstract class AbstractAutofillPerfTestCase {
private static final String TAG = "AbstractAutofillPerfTestCase";
@ClassRule
public static final SettingsStateKeeperRule mServiceSettingsKeeper =
new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
Settings.Secure.AUTOFILL_SERVICE);
@Rule
public ActivityTestRule<PerfTestActivity> mActivityRule =
protected final AutofillTestWatcher mTestWatcher = MyAutofillService.getTestWatcher();
protected ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
protected PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
public final RuleChain mAllRules = RuleChain
.outerRule(mTestWatcher)
.around(mPerfStatusReporter)
.around(mActivityRule);
private final int mLayoutId;
@@ -58,6 +66,27 @@ public abstract class AbstractAutofillPerfTestCase {
mLayoutId = layoutId;
}
@BeforeClass
public static void disableDefaultAugmentedService() {
Log.v(TAG, "@BeforeClass: disableDefaultAugmentedService()");
setDefaultAugmentedAutofillServiceEnabled(false);
}
@AfterClass
public static void enableDefaultAugmentedService() {
Log.v(TAG, "@AfterClass: enableDefaultAugmentedService()");
setDefaultAugmentedAutofillServiceEnabled(true);
}
/**
* Enables / disables the default augmented autofill service.
*/
private static void setDefaultAugmentedAutofillServiceEnabled(boolean enabled) {
Log.d(TAG, "setDefaultAugmentedAutofillServiceEnabled(): " + enabled);
runShellCommand("cmd autofill set default-augmented-service-enabled 0 %s",
Boolean.toString(enabled));
}
/**
* Prepares the activity so that by the time the test is run it has reference to its fields.
*/
@@ -74,41 +103,8 @@ public abstract class AbstractAutofillPerfTestCase {
});
}
@Before
public void enableService() {
MyAutofillService.resetStaticState();
MyAutofillService.setEnabled(true);
}
@After
public void disableService() {
// Must disable service so calls are ignored in case of errors during the test case;
// otherwise, other tests will fail because these calls are made in the UI thread (as both
// the service, the tests, and the app run in the same process).
MyAutofillService.setEnabled(false);
}
/**
* Initializes the {@link PerfTestActivity} after it was launched.
*/
protected abstract void onCreate(PerfTestActivity activity);
/**
* Uses the {@code settings} binary to set the autofill service.
*/
protected void setService() {
SettingsHelper.syncSet(InstrumentationRegistry.getTargetContext(),
SettingsHelper.NAMESPACE_SECURE,
Settings.Secure.AUTOFILL_SERVICE,
MyAutofillService.COMPONENT_NAME);
}
/**
* Uses the {@code settings} binary to reset the autofill service.
*/
protected void resetService() {
SettingsHelper.syncDelete(InstrumentationRegistry.getTargetContext(),
SettingsHelper.NAMESPACE_SECURE,
Settings.Secure.AUTOFILL_SERVICE);
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.view.autofill;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
import android.service.autofill.FillContext;
import android.util.Log;
import java.util.List;
/**
* Helper for common funcionalities.
*/
public class AutofillTestHelper {
private static final String TAG = "AutofillTestHelper";
/**
* Gets a node given its Android resource id, or {@code null} if not found.
*/
public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
for (FillContext context : contexts) {
ViewNode node = findNodeByResourceId(context.getStructure(), resourceId);
if (node != null) {
return node;
}
}
return null;
}
/**
* Gets a node if it matches the filter criteria for the given id.
*/
private static ViewNode findNodeByResourceId(AssistStructure structure, String id) {
Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
final int nodes = structure.getWindowNodeCount();
for (int i = 0; i < nodes; i++) {
final WindowNode windowNode = structure.getWindowNodeAt(i);
final ViewNode rootNode = windowNode.getRootViewNode();
final ViewNode node = findNodeByResourceId(rootNode, id);
if (node != null) {
return node;
}
}
return null;
}
/**
* Gets a node if it matches the filter criteria for the given id.
*/
private static ViewNode findNodeByResourceId(ViewNode node, String id) {
if (id.equals(node.getIdEntry())) {
return node;
}
final int childrenSize = node.getChildCount();
if (childrenSize > 0) {
for (int i = 0; i < childrenSize; i++) {
final ViewNode found = findNodeByResourceId(node.getChildAt(i), id);
if (found != null) {
return found;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,196 @@
/*
* 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.view.autofill;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import android.perftests.utils.SettingsHelper;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.Timeout;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Custom {@link TestWatcher} that does the setup and reset tasks for the tests.
*/
final class AutofillTestWatcher extends TestWatcher {
private static final String TAG = "AutofillTestWatcher";
private static final long GENERIC_TIMEOUT_MS = 10_000;
private static ServiceWatcher sServiceWatcher;
private String mOriginalLogLevel;
@Override
protected void starting(Description description) {
super.starting(description);
final String testName = description.getDisplayName();
Log.i(TAG, "Starting " + testName);
prepareDevice();
enableVerboseLog();
// Prepare the service before each test.
// Disable the current AutofillService.
resetAutofillService();
// Set MyAutofillService status enable, it can start to accept the calls.
enableMyAutofillService();
setServiceWatcher();
}
@Override
protected void finished(Description description) {
super.finished(description);
final String testName = description.getDisplayName();
Log.i(TAG, "Finished " + testName);
restoreLogLevel();
// Set MyAutofillService status disable, so the calls are ignored.
disableMyAutofillService();
clearServiceWatcher();
}
void waitServiceConnect() throws InterruptedException {
if (sServiceWatcher != null) {
Log.d(TAG, "waitServiceConnect()");
sServiceWatcher.waitOnConnected();
}
}
/**
* Uses the {@code settings} binary to set the autofill service.
*/
void setAutofillService() {
String serviceName = MyAutofillService.COMPONENT_NAME;
SettingsHelper.syncSet(InstrumentationRegistry.getTargetContext(),
SettingsHelper.NAMESPACE_SECURE,
Settings.Secure.AUTOFILL_SERVICE,
serviceName);
// Waits until the service is actually enabled.
Timeout timeout = new Timeout("CONNECTION_TIMEOUT", GENERIC_TIMEOUT_MS, 2F,
GENERIC_TIMEOUT_MS);
try {
timeout.run("Enabling Autofill service", () -> {
return isAutofillServiceEnabled(serviceName) ? serviceName : null;
});
} catch (Exception e) {
throw new AssertionError("Enabling Autofill service failed.");
}
}
/**
* Uses the {@code settings} binary to reset the autofill service.
*/
void resetAutofillService() {
SettingsHelper.syncDelete(InstrumentationRegistry.getTargetContext(),
SettingsHelper.NAMESPACE_SECURE,
Settings.Secure.AUTOFILL_SERVICE);
}
/**
* Checks whether the given service is set as the autofill service for the default user.
*/
private boolean isAutofillServiceEnabled(String serviceName) {
String actualName = SettingsHelper.get(SettingsHelper.NAMESPACE_SECURE,
Settings.Secure.AUTOFILL_SERVICE);
return serviceName.equals(actualName);
}
private void prepareDevice() {
// Unlock screen.
runShellCommand("input keyevent KEYCODE_WAKEUP");
// Dismiss keyguard, in case it's set as "Swipe to unlock".
runShellCommand("wm dismiss-keyguard");
// Collapse notifications.
runShellCommand("cmd statusbar collapse");
}
private void enableMyAutofillService() {
MyAutofillService.resetStaticState();
MyAutofillService.setEnabled(true);
}
private void disableMyAutofillService() {
// Must disable service so calls are ignored in case of errors during the test case;
// otherwise, other tests will fail because these calls are made in the UI thread (as both
// the service, the tests, and the app run in the same process).
MyAutofillService.setEnabled(false);
}
private void enableVerboseLog() {
mOriginalLogLevel = runShellCommand("cmd autofill get log_level");
Log.d(TAG, "enableVerboseLog(), mOriginalLogLevel=" + mOriginalLogLevel);
if (!mOriginalLogLevel.equals("verbose")) {
runShellCommand("cmd autofill set log_level verbose");
}
}
private void restoreLogLevel() {
Log.d(TAG, "restoreLogLevel to " + mOriginalLogLevel);
if (!mOriginalLogLevel.equals("verbose")) {
runShellCommand("cmd autofill set log_level %s", mOriginalLogLevel);
}
}
private static void setServiceWatcher() {
if (sServiceWatcher == null) {
sServiceWatcher = new ServiceWatcher();
}
}
private static void clearServiceWatcher() {
if (sServiceWatcher != null) {
sServiceWatcher = null;
}
}
public static final class ServiceWatcher {
private final CountDownLatch mConnected = new CountDownLatch(1);
public static void onConnected() {
Log.i(TAG, "onConnected: sServiceWatcher=" + sServiceWatcher);
sServiceWatcher.mConnected.countDown();
}
@NonNull
public void waitOnConnected() throws InterruptedException {
await(mConnected, "not connected");
}
private void await(@NonNull CountDownLatch latch, @NonNull String fmt,
@Nullable Object... args)
throws InterruptedException {
final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (!called) {
throw new IllegalStateException(String.format(fmt, args)
+ " in " + GENERIC_TIMEOUT_MS + "ms");
}
}
}
}

View File

@@ -11,7 +11,7 @@
* 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
* limitations under the License.
*/
package android.view.autofill;
@@ -24,12 +24,18 @@ import android.perftests.utils.PerfTestActivity;
import android.view.View;
import android.widget.EditText;
import androidx.test.filters.LargeTest;
import com.android.perftests.autofill.R;
import org.junit.Test;
@LargeTest
public class LoginTest extends AbstractAutofillPerfTestCase {
public static final String ID_USERNAME = "username";
public static final String ID_PASSWORD = "password";
private EditText mUsername;
private EditText mPassword;
private AutofillManager mAfm;
@@ -51,15 +57,15 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
*/
@Test
public void testFocus_noService() throws Throwable {
resetService();
mTestWatcher.resetAutofillService();
mActivityRule.runOnUiThread(() -> {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mActivityRule.runOnUiThread(() -> {
mUsername.requestFocus();
mPassword.requestFocus();
}
});
});
}
}
/**
@@ -69,22 +75,23 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
@Test
public void testFocus_serviceDoesNotAutofill() throws Throwable {
MyAutofillService.newCannedResponse().reply();
setService();
mTestWatcher.setAutofillService();
// Must first focus in a field to trigger autofill and wait for service response
// outside the loop
mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
mTestWatcher.waitServiceConnect();
MyAutofillService.getLastFillRequest();
// Then focus on password so loop start with focus away from username
mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
mActivityRule.runOnUiThread(() -> {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mActivityRule.runOnUiThread(() -> {
mUsername.requestFocus();
mPassword.requestFocus();
}
});
});
}
}
/**
@@ -93,10 +100,10 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
@Test
public void testFocus_autofillBothFields() throws Throwable {
MyAutofillService.newCannedResponse()
.setUsername(mUsername.getAutofillId(), "user")
.setPassword(mPassword.getAutofillId(), "pass")
.setUsername(ID_USERNAME, "user")
.setPassword(ID_PASSWORD, "pass")
.reply();
setService();
mTestWatcher.setAutofillService();
// Callback is used to slow down the calls made to the autofill server so the
// app is not crashed due to binder exhaustion. But the time spent waiting for the callbacks
@@ -106,6 +113,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
// Must first trigger autofill and wait for service response outside the loop
mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
mTestWatcher.waitServiceConnect();
MyAutofillService.getLastFillRequest();
callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
@@ -146,10 +154,10 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
public void testFocus_autofillUsernameOnly() throws Throwable {
// Must set ignored ids so focus on password does not trigger new requests
MyAutofillService.newCannedResponse()
.setUsername(mUsername.getAutofillId(), "user")
.setIgnored(mPassword.getAutofillId())
.setUsername(ID_USERNAME, "user")
.setIgnored(ID_PASSWORD)
.reply();
setService();
mTestWatcher.setAutofillService();
// Callback is used to slow down the calls made to the autofill server so the
// app is not crashed due to binder exhaustion. But the time spent waiting for the callbacks
@@ -159,6 +167,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
// Must first trigger autofill and wait for service response outside the loop
mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
mTestWatcher.waitServiceConnect();
MyAutofillService.getLastFillRequest();
callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
@@ -193,7 +202,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
*/
@Test
public void testChange_noService() throws Throwable {
resetService();
mTestWatcher.resetAutofillService();
changeTest(false);
}
@@ -205,7 +214,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
@Test
public void testChange_serviceDoesNotAutofill() throws Throwable {
MyAutofillService.newCannedResponse().reply();
setService();
mTestWatcher.setAutofillService();
changeTest(true);
}
@@ -216,10 +225,10 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
@Test
public void testChange_autofillBothFields() throws Throwable {
MyAutofillService.newCannedResponse()
.setUsername(mUsername.getAutofillId(), "user")
.setPassword(mPassword.getAutofillId(), "pass")
.setUsername(ID_USERNAME, "user")
.setPassword(ID_PASSWORD, "pass")
.reply();
setService();
mTestWatcher.setAutofillService();
changeTest(true);
}
@@ -231,10 +240,10 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
public void testChange_autofillUsernameOnly() throws Throwable {
// Must set ignored ids so focus on password does not trigger new requests
MyAutofillService.newCannedResponse()
.setUsername(mUsername.getAutofillId(), "user")
.setIgnored(mPassword.getAutofillId())
.setUsername(ID_USERNAME, "user")
.setIgnored(ID_PASSWORD)
.reply();
setService();
mTestWatcher.setAutofillService();
changeTest(true);
}
@@ -244,27 +253,27 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
// outside the loop
mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
if (waitForService) {
mTestWatcher.waitServiceConnect();
MyAutofillService.getLastFillRequest();
}
mActivityRule.runOnUiThread(() -> {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mActivityRule.runOnUiThread(() -> {
mUsername.setText("");
mUsername.setText("a");
mPassword.setText("");
mPassword.setText("x");
}
});
});
}
}
@Test
public void testCallbacks() throws Throwable {
MyAutofillService.newCannedResponse()
.setUsername(mUsername.getAutofillId(), "user")
.setPassword(mPassword.getAutofillId(), "pass")
.setUsername(ID_USERNAME, "user")
.setPassword(ID_PASSWORD, "pass")
.reply();
setService();
mTestWatcher.setAutofillService();
MyAutofillCallback callback = new MyAutofillCallback();
mAfm.registerCallback(callback);
@@ -272,6 +281,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
// Must first focus in a field to trigger autofill and wait for service response
// outside the loop
mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
mTestWatcher.waitServiceConnect();
MyAutofillService.getLastFillRequest();
callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);

View File

@@ -11,10 +11,11 @@
* 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
* limitations under the License.
*/
package android.view.autofill;
import android.app.assist.AssistStructure.ViewNode;
import android.os.CancellationSignal;
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
@@ -28,12 +29,9 @@ import android.util.Pair;
import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.perftests.autofill.R;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -44,7 +42,7 @@ import java.util.concurrent.TimeUnit;
public class MyAutofillService extends AutofillService {
private static final String TAG = "MyAutofillService";
private static final int TIMEOUT_MS = 5000;
private static final int TIMEOUT_MS = 5_000;
private static final String PACKAGE_NAME = "com.android.perftests.autofill";
static final String COMPONENT_NAME = PACKAGE_NAME + "/android.view.autofill.MyAutofillService";
@@ -55,6 +53,14 @@ public class MyAutofillService extends AutofillService {
private static boolean sEnabled;
/**
* Returns the TestWatcher that was used for the testing.
*/
@NonNull
public static AutofillTestWatcher getTestWatcher() {
return new AutofillTestWatcher();
}
/**
* Resets the static state associated with the service.
*/
@@ -92,6 +98,11 @@ public class MyAutofillService extends AutofillService {
return request;
}
@Override
public void onConnected() {
AutofillTestWatcher.ServiceWatcher.onConnected();
}
@Override
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
FillCallback callback) {
@@ -120,20 +131,27 @@ public class MyAutofillService extends AutofillService {
boolean hasData = false;
if (response.mUsername != null) {
hasData = true;
dataset.setValue(response.mUsername.first,
AutofillValue.forText(response.mUsername.second));
AutofillId autofillId = getAutofillIdByResourceId(request, response.mUsername.first);
AutofillValue value = AutofillValue.forText(response.mUsername.second);
dataset.setValue(autofillId, value, newDatasetPresentation("dataset"));
}
if (response.mPassword != null) {
hasData = true;
dataset.setValue(response.mPassword.first,
AutofillValue.forText(response.mPassword.second));
AutofillId autofillId = getAutofillIdByResourceId(request, response.mPassword.first);
AutofillValue value = AutofillValue.forText(response.mPassword.second);
dataset.setValue(autofillId, value, newDatasetPresentation("dataset"));
}
if (hasData) {
FillResponse.Builder fillResponse = new FillResponse.Builder();
if (response.mIgnoredIds != null) {
fillResponse.setIgnoredIds(response.mIgnoredIds);
int length = response.mIgnoredIds.length;
AutofillId[] requiredIds = new AutofillId[length];
for (int i = 0; i < length; i++) {
String resourceId = response.mIgnoredIds[i];
requiredIds[i] = getAutofillIdByResourceId(request, resourceId);
}
fillResponse.setIgnoredIds(requiredIds);
}
callback.onSuccess(fillResponse.addDataset(dataset.build()).build());
} else {
callback.onSuccess(null);
@@ -143,6 +161,16 @@ public class MyAutofillService extends AutofillService {
}
}
private AutofillId getAutofillIdByResourceId(FillRequest request, String resourceId)
throws Exception {
ViewNode node = AutofillTestHelper.findNodeByResourceId(request.getFillContexts(),
resourceId);
if (node == null) {
throw new AssertionError("No node with resource id " + resourceId);
}
return node.getAutofillId();
}
@Override
public void onSaveRequest(SaveRequest request, SaveCallback callback) {
// No current test should have triggered it...
@@ -151,9 +179,9 @@ public class MyAutofillService extends AutofillService {
}
static final class CannedResponse {
private final Pair<AutofillId, String> mUsername;
private final Pair<AutofillId, String> mPassword;
private final AutofillId[] mIgnoredIds;
private final Pair<String, String> mUsername;
private final Pair<String, String> mPassword;
private final String[] mIgnoredIds;
private CannedResponse(@NonNull Builder builder) {
mUsername = builder.mUsername;
@@ -162,24 +190,24 @@ public class MyAutofillService extends AutofillService {
}
static class Builder {
private Pair<AutofillId, String> mUsername;
private Pair<AutofillId, String> mPassword;
private AutofillId[] mIgnoredIds;
private Pair<String, String> mUsername;
private Pair<String, String> mPassword;
private String[] mIgnoredIds;
@NonNull
Builder setUsername(@NonNull AutofillId id, @NonNull String value) {
Builder setUsername(@NonNull String id, @NonNull String value) {
mUsername = new Pair<>(id, value);
return this;
}
@NonNull
Builder setPassword(@NonNull AutofillId id, @NonNull String value) {
Builder setPassword(@NonNull String id, @NonNull String value) {
mPassword = new Pair<>(id, value);
return this;
}
@NonNull
Builder setIgnored(AutofillId... ids) {
Builder setIgnored(String... ids) {
mIgnoredIds = ids;
return this;
}

View File

@@ -30,6 +30,7 @@ android_test {
"androidx.annotation_annotation",
"apct-perftests-utils",
"ub-uiautomator",
"collector-device-lib-platform",
],
platform_apis: true,
test_suites: ["device-tests"],

View File

@@ -24,5 +24,22 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.perftests.blob" />
<option name="hidden-api-checks" value="false"/>
<!-- TODO: Add PerfettoListener to automatically capture perfetto traces for each test-->
<!-- Listener related args for collecting the traces and waiting for the device
to stabilize. -->
<option name="device-listeners"
value="android.device.collectors.ProcLoadListener" />
<!-- 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" />
<!-- 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" />
</test>
</configuration>

View File

@@ -0,0 +1,38 @@
// 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 {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
android_test {
name: "ContentCapturePerfTests",
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
static_libs: [
"androidx.test.rules",
"androidx.annotation_annotation",
"apct-perftests-utils",
"collector-device-lib",
"compatibility-device-util-axt",
],
platform_apis: true,
test_suites: ["device-tests"],
data: [":perfetto_artifacts"],
}

View File

@@ -0,0 +1,39 @@
<?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.contentcapture">
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.view.contentcapture.CustomTestActivity"
android:exported="true">
</activity>
<service
android:name="android.view.contentcapture.MyContentCaptureService"
android:label="PERF ContentCaptureService"
android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.service.contentcapture.ContentCaptureService" />
</intent-filter>
</service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.perftests.contentcapture" />
</manifest>

View File

@@ -0,0 +1,59 @@
<?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 ContentCapturePerfTests 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="ContentCapturePerfTests.apk" />
</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" />
</target_preparer>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="perfetto_file_path" />
</metrics_collector>
<!-- 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.contentcapture" />
<!-- 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" />
<!-- 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" />
<option name="instrumentation-arg" key="newRunListenerMode" value="true" />
</test>
</configuration>

View File

@@ -0,0 +1,26 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical" >
</LinearLayout>

View File

@@ -0,0 +1,50 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical" >
<TextView
android:id="@+id/username_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Username" />
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/password_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Password" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</LinearLayout>

View File

@@ -0,0 +1,267 @@
/*
* 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.view.contentcapture;
import static android.view.contentcapture.CustomTestActivity.INTENT_EXTRA_CUSTOM_VIEWS;
import static android.view.contentcapture.CustomTestActivity.INTENT_EXTRA_LAYOUT_ID;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import android.app.Application;
import android.content.ContentCaptureOptions;
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import android.os.UserHandle;
import android.perftests.utils.PerfStatusReporter;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.ActivitiesWatcher;
import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
import com.android.perftests.contentcapture.R;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;
/**
* Base class for all content capture tests.
*/
public abstract class AbstractContentCapturePerfTestCase {
private static final String TAG = AbstractContentCapturePerfTestCase.class.getSimpleName();
private static final long GENERIC_TIMEOUT_MS = 10_000;
private static int sOriginalStayOnWhilePluggedIn;
private static Context sContext = getInstrumentation().getTargetContext();
protected ActivitiesWatcher mActivitiesWatcher;
private MyContentCaptureService.ServiceWatcher mServiceWatcher;
@Rule
public ActivityTestRule<CustomTestActivity> mActivityRule =
new ActivityTestRule<>(CustomTestActivity.class, false, false);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Rule
public TestRule mServiceDisablerRule = (base, description) -> {
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
base.evaluate();
} finally {
Log.v(TAG, "@mServiceDisablerRule: safelyDisableService()");
safelyDisableService();
}
}
};
};
private void safelyDisableService() {
try {
resetService();
MyContentCaptureService.resetStaticState();
if (mServiceWatcher != null) {
mServiceWatcher.waitOnDestroy();
}
} catch (Throwable t) {
Log.e(TAG, "error disabling service", t);
}
}
/**
* Sets the content capture service.
*/
private static void setService(@NonNull String service) {
final int userId = getCurrentUserId();
Log.d(TAG, "Setting service for user " + userId + " to " + service);
// TODO(b/123540602): use @TestingAPI to get max duration constant
runShellCommand("cmd content_capture set temporary-service %d %s 119000", userId, service);
}
/**
* Resets the content capture service.
*/
private static void resetService() {
final int userId = getCurrentUserId();
Log.d(TAG, "Resetting back user " + userId + " to default service");
runShellCommand("cmd content_capture set temporary-service %d", userId);
}
private static int getCurrentUserId() {
return UserHandle.myUserId();
}
@BeforeClass
public static void setStayAwake() {
Log.v(TAG, "@BeforeClass: setStayAwake()");
// Some test cases will restart the activity, and stay awake is necessary to ensure that
// the test will not screen off during the test.
// Keeping the activity screen on is not enough, screen off may occur between the activity
// finished and the next start
final int stayOnWhilePluggedIn = Settings.Global.getInt(sContext.getContentResolver(),
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
sOriginalStayOnWhilePluggedIn = -1;
if (stayOnWhilePluggedIn != BatteryManager.BATTERY_PLUGGED_ANY) {
sOriginalStayOnWhilePluggedIn = stayOnWhilePluggedIn;
// Keep the device awake during testing.
setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_ANY);
}
}
@AfterClass
public static void resetStayAwake() {
Log.v(TAG, "@AfterClass: resetStayAwake()");
if (sOriginalStayOnWhilePluggedIn != -1) {
setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn);
}
}
private static void setStayOnWhilePluggedIn(int value) {
runShellCommand(String.format("settings put global %s %d",
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value));
}
@BeforeClass
public static void setAllowSelf() {
final ContentCaptureOptions options = new ContentCaptureOptions(null);
Log.v(TAG, "@BeforeClass: setAllowSelf(): options=" + options);
sContext.getApplicationContext().setContentCaptureOptions(options);
}
@AfterClass
public static void unsetAllowSelf() {
Log.v(TAG, "@AfterClass: unsetAllowSelf()");
clearOptions();
}
protected static void clearOptions() {
sContext.getApplicationContext().setContentCaptureOptions(null);
}
@BeforeClass
public static void disableDefaultService() {
Log.v(TAG, "@BeforeClass: disableDefaultService()");
setDefaultServiceEnabled(false);
}
@AfterClass
public static void enableDefaultService() {
Log.v(TAG, "@AfterClass: enableDefaultService()");
setDefaultServiceEnabled(true);
}
/**
* Enables / disables the default service.
*/
private static void setDefaultServiceEnabled(boolean enabled) {
final int userId = getCurrentUserId();
Log.d(TAG, "setDefaultServiceEnabled(user=" + userId + ", enabled= " + enabled + ")");
runShellCommand("cmd content_capture set default-service-enabled %d %s", userId,
Boolean.toString(enabled));
}
@Before
public void prepareDevice() throws Exception {
Log.v(TAG, "@Before: prepareDevice()");
// Unlock screen.
runShellCommand("input keyevent KEYCODE_WAKEUP");
// Dismiss keyguard, in case it's set as "Swipe to unlock".
runShellCommand("wm dismiss-keyguard");
// Collapse notifications.
runShellCommand("cmd statusbar collapse");
}
@Before
public void registerLifecycleCallback() {
Log.v(TAG, "@Before: Registering lifecycle callback");
final Application app = (Application) sContext.getApplicationContext();
mActivitiesWatcher = new ActivitiesWatcher(GENERIC_TIMEOUT_MS);
app.registerActivityLifecycleCallbacks(mActivitiesWatcher);
}
@After
public void unregisterLifecycleCallback() {
Log.d(TAG, "@After: Unregistering lifecycle callback: " + mActivitiesWatcher);
if (mActivitiesWatcher != null) {
final Application app = (Application) sContext.getApplicationContext();
app.unregisterActivityLifecycleCallbacks(mActivitiesWatcher);
}
}
/**
* Sets {@link MyContentCaptureService} as the service for the current user and waits until
* its created, then add the perf test package into allow list.
*/
public MyContentCaptureService enableService() throws InterruptedException {
if (mServiceWatcher != null) {
throw new IllegalStateException("There Can Be Only One!");
}
mServiceWatcher = MyContentCaptureService.setServiceWatcher();
setService(MyContentCaptureService.SERVICE_NAME);
mServiceWatcher.setAllowSelf();
return mServiceWatcher.waitOnCreate();
}
@NonNull
protected ActivityWatcher startWatcher() {
return mActivitiesWatcher.watch(CustomTestActivity.class);
}
/**
* Launch test activity with default login layout
*/
protected CustomTestActivity launchActivity() {
return launchActivity(R.layout.test_login_activity, 0);
}
/**
* Launch test activity with give layout and parameter
*/
protected CustomTestActivity launchActivity(int layoutId, int numViews) {
final Intent intent = new Intent(sContext, CustomTestActivity.class);
intent.putExtra(INTENT_EXTRA_LAYOUT_ID, layoutId);
intent.putExtra(INTENT_EXTRA_CUSTOM_VIEWS, numViews);
return mActivityRule.launchActivity(intent);
}
protected void finishActivity() {
try {
mActivityRule.finishActivity();
} catch (IllegalStateException e) {
// no op
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2016 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.view.contentcapture;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.perftests.contentcapture.R;
/**
* A simple activity used for testing, e.g. performance of activity switching, or as a base
* container of testing view.
*/
public class CustomTestActivity extends Activity {
public static final String INTENT_EXTRA_LAYOUT_ID = "layout_id";
public static final String INTENT_EXTRA_CUSTOM_VIEWS = "custom_view_number";
public static final int MAX_VIEWS = 500;
private static final int CUSTOM_CONTAINER_LAYOUT_ID = R.layout.test_container_activity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().hasExtra(INTENT_EXTRA_LAYOUT_ID)) {
final int layoutId = getIntent().getIntExtra(INTENT_EXTRA_LAYOUT_ID,
/* defaultValue= */0);
setContentView(layoutId);
if (layoutId == CUSTOM_CONTAINER_LAYOUT_ID) {
createCustomViews(findViewById(R.id.root_view),
getIntent().getIntExtra(INTENT_EXTRA_CUSTOM_VIEWS, MAX_VIEWS));
}
}
}
private void createCustomViews(LinearLayout root, int number) {
LinearLayout horizontalLayout = null;
for (int i = 0; i < number; i++) {
final int j = i % 8;
if (horizontalLayout != null && j == 0) {
root.addView(horizontalLayout);
horizontalLayout = null;
}
if (horizontalLayout == null) {
horizontalLayout = createHorizontalLayout();
}
horizontalLayout.addView(createItem(null, i));
}
if (horizontalLayout != null) {
root.addView(horizontalLayout);
}
}
private LinearLayout createHorizontalLayout() {
final LinearLayout layout = new LinearLayout(getApplicationContext());
layout.setOrientation(LinearLayout.HORIZONTAL);
return layout;
}
private LinearLayout createItem(Drawable drawable, int index) {
final LinearLayout group = new LinearLayout(getApplicationContext());
group.setOrientation(LinearLayout.VERTICAL);
group.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, /* weight= */ 1.0f));
final TextView text = new TextView(this);
text.setText("i = " + index);
group.addView(text);
return group;
}
}

View File

@@ -0,0 +1,158 @@
/*
* 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.view.contentcapture;
import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.CREATED;
import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
import android.perftests.utils.BenchmarkState;
import android.view.View;
import androidx.test.filters.LargeTest;
import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
import com.android.perftests.contentcapture.R;
import org.junit.Test;
@LargeTest
public class LoginTest extends AbstractContentCapturePerfTestCase {
@Test
public void testLaunchActivity() throws Throwable {
enableService();
testActivityLaunchTime(R.layout.test_login_activity, 0);
}
@Test
public void testLaunchActivity_contain100Views() throws Throwable {
enableService();
testActivityLaunchTime(R.layout.test_container_activity, 100);
}
@Test
public void testLaunchActivity_contain300Views() throws Throwable {
enableService();
testActivityLaunchTime(R.layout.test_container_activity, 300);
}
@Test
public void testLaunchActivity_contain500Views() throws Throwable {
enableService();
testActivityLaunchTime(R.layout.test_container_activity, 500);
}
@Test
public void testLaunchActivity_noService() throws Throwable {
testActivityLaunchTime(R.layout.test_login_activity, 0);
}
@Test
public void testLaunchActivity_noService_contain100Views() throws Throwable {
testActivityLaunchTime(R.layout.test_container_activity, 100);
}
@Test
public void testLaunchActivity_noService_contain300Views() throws Throwable {
testActivityLaunchTime(R.layout.test_container_activity, 300);
}
@Test
public void testLaunchActivity_noService_contain500Views() throws Throwable {
testActivityLaunchTime(R.layout.test_container_activity, 500);
}
private void testActivityLaunchTime(int layoutId, int numViews) throws Throwable {
final ActivityWatcher watcher = startWatcher();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
launchActivity(layoutId, numViews);
// Ignore the time to finish the activity
state.pauseTiming();
watcher.waitFor(CREATED);
finishActivity();
watcher.waitFor(DESTROYED);
state.resumeTiming();
}
}
@Test
public void testOnVisibilityAggregated_visibleChanged() throws Throwable {
enableService();
final CustomTestActivity activity = launchActivity();
final View root = activity.getWindow().getDecorView();
final View username = root.findViewById(R.id.username);
testOnVisibilityAggregated(username);
}
@Test
public void testOnVisibilityAggregated_visibleChanged_noService() throws Throwable {
final CustomTestActivity activity = launchActivity();
final View root = activity.getWindow().getDecorView();
final View username = root.findViewById(R.id.username);
testOnVisibilityAggregated(username);
}
@Test
public void testOnVisibilityAggregated_visibleChanged_noOptions() throws Throwable {
enableService();
clearOptions();
final CustomTestActivity activity = launchActivity();
final View root = activity.getWindow().getDecorView();
final View username = root.findViewById(R.id.username);
testOnVisibilityAggregated(username);
}
@Test
public void testOnVisibilityAggregated_visibleChanged_notImportant() throws Throwable {
enableService();
final CustomTestActivity activity = launchActivity();
final View root = activity.getWindow().getDecorView();
final View username = root.findViewById(R.id.username);
username.setImportantForContentCapture(View.IMPORTANT_FOR_CONTENT_CAPTURE_NO);
testOnVisibilityAggregated(username);
}
private void testOnVisibilityAggregated(View view) throws Throwable {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
// Only count the time of onVisibilityAggregated()
state.pauseTiming();
mActivityRule.runOnUiThread(() -> {
state.resumeTiming();
view.onVisibilityAggregated(false);
state.pauseTiming();
});
mActivityRule.runOnUiThread(() -> {
state.resumeTiming();
view.onVisibilityAggregated(true);
state.pauseTiming();
});
state.resumeTiming();
}
}
}

View File

@@ -0,0 +1,182 @@
/*
* 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.view.contentcapture;
import android.content.ComponentName;
import android.service.contentcapture.ActivityEvent;
import android.service.contentcapture.ContentCaptureService;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class MyContentCaptureService extends ContentCaptureService {
private static final String TAG = MyContentCaptureService.class.getSimpleName();
private static final String MY_PACKAGE = "com.android.perftests.contentcapture";
public static final String SERVICE_NAME = MY_PACKAGE + "/"
+ MyContentCaptureService.class.getName();
private static ServiceWatcher sServiceWatcher;
@NonNull
public static ServiceWatcher setServiceWatcher() {
if (sServiceWatcher != null) {
throw new IllegalStateException("There Can Be Only One!");
}
sServiceWatcher = new ServiceWatcher();
return sServiceWatcher;
}
public static void resetStaticState() {
sServiceWatcher = null;
}
private static void clearServiceWatcher() {
final ServiceWatcher sw = sServiceWatcher;
if (sw != null) {
if (sw.mReadyToClear) {
sw.mService = null;
sServiceWatcher = null;
} else {
sw.mReadyToClear = true;
}
}
}
@Override
public void onConnected() {
Log.i(TAG, "onConnected: sServiceWatcher=" + sServiceWatcher);
if (sServiceWatcher == null) {
Log.e(TAG, "onConnected() without a watcher");
return;
}
if (!sServiceWatcher.mReadyToClear && sServiceWatcher.mService != null) {
Log.e(TAG, "onConnected(): already created: " + sServiceWatcher);
return;
}
sServiceWatcher.mService = this;
sServiceWatcher.mCreated.countDown();
sServiceWatcher.mReadyToClear = false;
}
@Override
public void onDisconnected() {
Log.i(TAG, "onDisconnected: sServiceWatcher=" + sServiceWatcher);
if (sServiceWatcher == null) {
Log.e(TAG, "onDisconnected() without a watcher");
return;
}
if (sServiceWatcher.mService == null) {
Log.e(TAG, "onDisconnected(): no service on " + sServiceWatcher);
return;
}
sServiceWatcher.mDestroyed.countDown();
clearServiceWatcher();
}
@Override
public void onCreateContentCaptureSession(ContentCaptureContext context,
ContentCaptureSessionId sessionId) {
Log.i(TAG, "onCreateContentCaptureSession(ctx=" + context + ", session=" + sessionId);
}
@Override
public void onDestroyContentCaptureSession(ContentCaptureSessionId sessionId) {
Log.i(TAG, "onDestroyContentCaptureSession(session=" + sessionId + ")");
}
@Override
public void onContentCaptureEvent(ContentCaptureSessionId sessionId,
ContentCaptureEvent event) {
Log.i(TAG, "onContentCaptureEventsRequest(session=" + sessionId + "): " + event);
}
@Override
public void onActivityEvent(ActivityEvent event) {
Log.i(TAG, "onActivityEvent(): " + event);
}
public static final class ServiceWatcher {
private static final long GENERIC_TIMEOUT_MS = 10_000;
private final CountDownLatch mCreated = new CountDownLatch(1);
private final CountDownLatch mDestroyed = new CountDownLatch(1);
private boolean mReadyToClear = true;
private Pair<Set<String>, Set<ComponentName>> mAllowList;
private MyContentCaptureService mService;
@NonNull
public MyContentCaptureService waitOnCreate() throws InterruptedException {
await(mCreated, "not created");
if (mService == null) {
throw new IllegalStateException("not created");
}
if (mAllowList != null) {
Log.d(TAG, "Allow after created: " + mAllowList);
mService.setContentCaptureWhitelist(mAllowList.first, mAllowList.second);
}
return mService;
}
public void waitOnDestroy() throws InterruptedException {
await(mDestroyed, "not destroyed");
}
/**
* Allow just this package.
*/
public void setAllowSelf() {
final ArraySet<String> pkgs = new ArraySet<>(1);
pkgs.add(MY_PACKAGE);
mAllowList = new Pair<>(pkgs, null);
}
@Override
public String toString() {
return "mService: " + mService + " created: " + (mCreated.getCount() == 0)
+ " destroyed: " + (mDestroyed.getCount() == 0);
}
/**
* Awaits for a latch to be counted down.
*/
private static void await(@NonNull CountDownLatch latch, @NonNull String fmt,
@Nullable Object... args)
throws InterruptedException {
final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (!called) {
throw new IllegalStateException(String.format(fmt, args)
+ " in " + GENERIC_TIMEOUT_MS + "ms");
}
}
}
}

View File

@@ -1,3 +1,19 @@
//
// 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 {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
@@ -21,23 +37,25 @@ android_test {
"androidx.appcompat_appcompat",
"androidx.test.rules",
"androidx.annotation_annotation",
"androidx.benchmark_benchmark-common",
"androidx.benchmark_benchmark-junit4",
"apct-perftests-overlay-apps",
"apct-perftests-resources-manager-apps",
"apct-perftests-utils",
"collector-device-lib",
"guava",
],
libs: ["android.test.base"],
java_resources: [ ":GoogleFontDancingScript", ],
data: [":perfetto_artifacts"],
platform_apis: true,
jni_libs: ["libperftestscore_jni"],
// Use google-fonts/dancing-script for the performance metrics
// ANDROIDMK TRANSLATION ERROR: Only $(LOCAL_PATH)/.. values are allowed
// LOCAL_ASSET_DIR := $(TOP)/external/google-fonts/dancing-script
test_suites: ["device-tests"],
certificate: "platform",
}

View File

@@ -10,20 +10,33 @@
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.VIBRATE" />
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.PerfTestActivity">
<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.os.SomeService" android:exported="false" android:process=":some_service" />
<service
android:name="android.os.SomeService"
android:exported="false"
android:process=":some_service" />
<provider
android:name="android.os.SomeProvider"
android:authorities="android.os.SomeProvider"
android:exported="false"
android:process=":some_provider" />
<service
android:name="android.view.autofill.MyAutofillService"
android:label="PERF AutofillService"
android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
android:permission="android.permission.BIND_AUTOFILL_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>
@@ -31,7 +44,7 @@
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
<instrumentation android:name="androidx.benchmark.junit4.AndroidBenchmarkRunner"
android:targetPackage="com.android.perftests.core"/>
</manifest>

View File

@@ -16,13 +16,40 @@
<configuration description="Runs CorePerfTests 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.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" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CorePerfTests.apk" />
</target_preparer>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="perfetto_file_path" />
</metrics_collector>
<!-- Needed for storing the perfetto files in external storage-->
<option name="isolated-storage" value="false" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.perftests.core" />
<option name="hidden-api-checks" value="false"/>
<option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
<!-- 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" />
<option name="instrumentation-arg" key="newRunListenerMode" value="true" />
</test>
</configuration>

View File

@@ -62,7 +62,7 @@ public class PendingIntentPerfTest {
state.resumeTiming();
final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
0);
PendingIntent.FLAG_MUTABLE_UNAUDITED);
state.pauseTiming();
pendingIntent.cancel();
@@ -80,11 +80,11 @@ public class PendingIntentPerfTest {
while (state.keepRunning()) {
state.pauseTiming();
final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0,
mIntent, 0);
mIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
state.resumeTiming();
final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
state.pauseTiming();
pendingIntent.cancel();
@@ -102,11 +102,11 @@ public class PendingIntentPerfTest {
while (state.keepRunning()) {
state.pauseTiming();
final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0,
mIntent, 0);
mIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
state.resumeTiming();
final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
state.pauseTiming();
previousPendingIntent.cancel();
@@ -124,7 +124,7 @@ public class PendingIntentPerfTest {
while (state.keepRunning()) {
state.pauseTiming();
final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
mIntent, 0);
mIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
state.resumeTiming();
pendingIntent.cancel();

View File

@@ -17,7 +17,6 @@ package android.app;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.view.Display;
@@ -78,7 +77,7 @@ public class ResourcesManagerPerfTest {
}
private void getResourcesForPath(String path) {
ResourcesManager.getInstance().getResources(null, path, null, null, null,
ResourcesManager.getInstance().getResources(null, path, null, null, null, null,
Display.DEFAULT_DISPLAY, null, sContext.getResources().getCompatibilityInfo(),
null, null);
}
@@ -136,4 +135,22 @@ public class ResourcesManagerPerfTest {
}
}
}
@Test
public void getDisplayMetrics() {
ResourcesManager resourcesManager = ResourcesManager.getInstance();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
// Invalidate cache.
resourcesManager.applyConfigurationToResources(
resourcesManager.getConfiguration(), null);
state.resumeTiming();
// Invoke twice for testing cache.
resourcesManager.getDisplayMetrics();
resourcesManager.getDisplayMetrics();
}
}
}

View File

@@ -95,8 +95,9 @@ public class ResourcesThemePerfTest {
? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
Resources destResources = resourcesManager.getResources(null, ai.sourceDir,
ai.splitSourceDirs, ai.resourceDirs, ai.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
c, mContext.getResources().getCompatibilityInfo(), null, null);
ai.splitSourceDirs, ai.resourceDirs, ai.overlayPaths, ai.sharedLibraryFiles,
Display.DEFAULT_DISPLAY, c, mContext.getResources().getCompatibilityInfo(),
null, null);
Assert.assertNotEquals(destResources.getAssets(), mContext.getAssets());
Resources.Theme destTheme = destResources.newTheme();

View File

@@ -0,0 +1,69 @@
/*
* 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.content.pm;
import android.content.Context;
import android.os.UserHandle;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class PackageManagerBenchmark {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Test
public void createUserContextBenchmark() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
context.createContextAsUser(UserHandle.SYSTEM, /* flags */ 0);
}
}
@Test
public void getResourcesForApplication_byStarAsUser()
throws PackageManager.NameNotFoundException {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
context.getPackageManager().getResourcesForApplicationAsUser(context.getPackageName(),
UserHandle.USER_SYSTEM);
}
}
@Test
public void getResourcesApplication_byCreateContextAsUser()
throws PackageManager.NameNotFoundException {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
context.createContextAsUser(UserHandle.SYSTEM, /* flags */ 0).getPackageManager()
.getResourcesForApplication(context.getPackageName());
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.database;
import static org.junit.Assert.assertEquals;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class CrossProcessCursorPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
/**
* Measure transporting a small {@link Cursor}, roughly 1KB in size.
*/
@Test
public void timeSmall() throws Exception {
time(1);
}
/**
* Measure transporting a small {@link Cursor}, roughly 54KB in size.
*/
@Test
public void timeMedium() throws Exception {
time(100);
}
/**
* Measure transporting a small {@link Cursor}, roughly 5.4MB in size.
*/
@Test
public void timeLarge() throws Exception {
time(10_000);
}
private static final Uri TEST_URI = Uri.parse("content://android.os.SomeProvider/");
private void time(int count) throws Exception {
try (ContentProviderClient client = InstrumentationRegistry.getTargetContext()
.getContentResolver().acquireContentProviderClient(TEST_URI)) {
// Configure remote side once with data size to return
final ContentValues values = new ContentValues();
values.put(Intent.EXTRA_INDEX, count);
client.update(TEST_URI, values, null);
// Repeatedly query that data until we reach convergence
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
try (Cursor c = client.query(TEST_URI, null, null, null)) {
// Actually walk the returned values to ensure we pull all
// data from the remote side
while (c.moveToNext()) {
assertEquals(c.getPosition(), c.getInt(0));
}
}
}
}
}
}

View File

@@ -54,11 +54,11 @@ public class CanvasPerfTest {
canvas = node.start(200, 200);
int save = canvas.save();
canvas.clipRect(1, 1, 199, 199);
canvas.insertReorderBarrier();
canvas.enableZ();
for (int i = 0; i < 5; i++) {
canvas.drawRenderNode(child);
}
canvas.insertInorderBarrier();
canvas.disableZ();
canvas.restoreToCount(save);
node.end(canvas);
}

View File

@@ -27,6 +27,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.Preconditions;
import com.android.perftests.core.R;
import org.junit.Rule;
@@ -73,10 +74,31 @@ public class TypefaceCreatePerfTest {
final AssetManager am = context.getAssets();
while (state.keepRunning()) {
Typeface face = Typeface.createFromAsset(am, TEST_FONT_NAME);
Typeface face = createFromNonAsset(am, TEST_FONT_NAME);
}
}
/**
* {@link AssetManager#openNonAsset(String)} variant of
* {@link Typeface#createFromAsset(AssetManager, String)}.
*/
private static Typeface createFromNonAsset(AssetManager mgr, String path) {
Preconditions.checkNotNull(path); // for backward compatibility
Preconditions.checkNotNull(mgr);
Typeface typeface = new Typeface.Builder(mgr, path).build();
if (typeface != null) return typeface;
// check if the file exists, and throw an exception for backward compatibility
//noinspection EmptyTryBlock
try (InputStream inputStream = mgr.openNonAsset(path)) {
// Purposely empty
} catch (IOException e) {
throw new RuntimeException("Font asset not found " + path);
}
return Typeface.DEFAULT;
}
@Test
public void testCreate_fromFile() {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -90,7 +112,7 @@ public class TypefaceCreatePerfTest {
throw new RuntimeException(e);
}
try (InputStream in = am.open(TEST_FONT_NAME);
try (InputStream in = am.openNonAsset(TEST_FONT_NAME);
OutputStream out = new FileOutputStream(outFile)) {
byte[] buf = new byte[1024];
int n = 0;

View File

@@ -0,0 +1,85 @@
/*
* 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.graphics.perftests;
import android.graphics.Typeface;
import android.os.SharedMemory;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.util.ArrayMap;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Map;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TypefaceSerializationPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Test
public void testSerializeFontMap() throws Exception {
Map<String, Typeface> systemFontMap = Typeface.getSystemFontMap();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
Typeface.serializeFontMap(systemFontMap);
}
}
@Test
public void testDeserializeFontMap() throws Exception {
SharedMemory memory = Typeface.serializeFontMap(Typeface.getSystemFontMap());
ByteBuffer buffer = memory.mapReadOnly().order(ByteOrder.BIG_ENDIAN);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
ArrayMap<String, Typeface> out = new ArrayMap<>();
while (state.keepRunning()) {
buffer.position(0);
Typeface.deserializeFontMap(buffer, out);
}
}
@Test
public void testSetSystemFontMap() throws Exception {
SharedMemory memory = null;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
// Explicitly destroy lazy-loaded typefaces, so that we don't hit the mmap limit
// (max_map_count).
Typeface.destroySystemFontMap();
Typeface.loadPreinstalledSystemFontMap();
if (memory != null) {
memory.close();
}
memory = Typeface.serializeFontMap(Typeface.getSystemFontMap());
state.resumeTiming();
Typeface.setSystemFontMap(memory);
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* 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.mtp_perf;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.ProxyFileDescriptorCallback;
import android.os.storage.StorageManager;
import android.system.ErrnoException;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class AppFusePerfTest {
static final int SIZE = 10 * 1024 * 1024; // 10MB
@Test
public void testReadWriteFile() throws IOException {
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
final StorageManager storageManager = context.getSystemService(StorageManager.class);
final byte[] bytes = new byte[SIZE];
final int samples = 100;
final double[] readTime = new double[samples];
final double[] writeTime = new double[samples];
for (int i = 0; i < samples; i++) {
final ParcelFileDescriptor fd = storageManager.openProxyFileDescriptor(
ParcelFileDescriptor.MODE_READ_ONLY, new TestCallback());
try (final ParcelFileDescriptor.AutoCloseInputStream stream =
new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
final long startTime = System.nanoTime();
stream.read(bytes);
readTime[i] = (System.nanoTime() - startTime) / 1000.0 / 1000.0;
}
}
for (int i = 0; i < samples; i++) {
final ParcelFileDescriptor fd = storageManager.openProxyFileDescriptor(
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE,
new TestCallback());
try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
final long startTime = System.nanoTime();
stream.write(bytes);
writeTime[i] = (System.nanoTime() - startTime) / 1000.0 / 1000.0;
}
}
double readAverage = 0;
double writeAverage = 0;
double readSquaredAverage = 0;
double writeSquaredAverage = 0;
for (int i = 0; i < samples; i++) {
readAverage += readTime[i];
writeAverage += writeTime[i];
readSquaredAverage += readTime[i] * readTime[i];
writeSquaredAverage += writeTime[i] * writeTime[i];
}
readAverage /= samples;
writeAverage /= samples;
readSquaredAverage /= samples;
writeSquaredAverage /= samples;
final Bundle results = new Bundle();
results.putDouble("readAverage", readAverage);
results.putDouble("readStandardDeviation",
Math.sqrt(readSquaredAverage - readAverage * readAverage));
results.putDouble("writeAverage", writeAverage);
results.putDouble("writeStandardDeviation",
Math.sqrt(writeSquaredAverage - writeAverage * writeAverage));
InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, results);
}
private static class TestCallback extends ProxyFileDescriptorCallback {
@Override
public long onGetSize() throws ErrnoException {
return SIZE;
}
@Override
public int onRead(long offset, int size, byte[] data) throws ErrnoException {
return size;
}
@Override
public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
return size;
}
@Override
public void onFsync() throws ErrnoException {}
@Override
public void onRelease() {}
}
}

View File

@@ -97,11 +97,21 @@ class PackageParsingPerfTest {
private val state: BenchmarkState get() = perfStatusReporter.benchmarkState
private val apks: List<File> get() = params.apks
private fun safeParse(parser: ParallelParser<*>, file: File) {
try {
parser.parse(file)
} catch (e: Exception) {
// ignore
}
}
@Test
fun sequentialNoCache() {
params.cacheDirToParser(null).use { parser ->
while (state.keepRunning()) {
apks.forEach { parser.parse(it) }
apks.forEach {
safeParse(parser, it)
}
}
}
}
@@ -110,10 +120,10 @@ class PackageParsingPerfTest {
fun sequentialCached() {
params.cacheDirToParser(testFolder.newFolder()).use { parser ->
// Fill the cache
apks.forEach { parser.parse(it) }
apks.forEach { safeParse(parser, it) }
while (state.keepRunning()) {
apks.forEach { parser.parse(it) }
apks.forEach { safeParse(parser, it) }
}
}
}
@@ -132,7 +142,7 @@ class PackageParsingPerfTest {
fun parallelCached() {
params.cacheDirToParser(testFolder.newFolder()).use { parser ->
// Fill the cache
apks.forEach { parser.parse(it) }
apks.forEach { safeParse(parser, it) }
while (state.keepRunning()) {
apks.forEach { parser.submit(it) }
@@ -149,7 +159,15 @@ class PackageParsingPerfTest {
PARALLEL_MAX_THREADS, "package-parsing-test",
Process.THREAD_PRIORITY_FOREGROUND)
fun submit(file: File) = service.submit { queue.put(parse(file)) }
fun submit(file: File) {
service.submit {
try {
queue.put(parse(file))
} catch (e: Exception) {
queue.put(e)
}
}
}
fun take() = queue.poll(QUEUE_POLL_TIMEOUT_SECONDS, TimeUnit.SECONDS)
@@ -178,17 +196,17 @@ class PackageParsingPerfTest {
// For testing, just disable enforcement to avoid hooking up to compat framework
ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
}
val parser = ParsingPackageUtils(false, null, null,
val parser = ParsingPackageUtils(false, null, null, emptyList(),
object : ParsingPackageUtils.Callback {
override fun hasFeature(feature: String) = true
override fun startParsingPackage(
packageName: String,
baseCodePath: String,
codePath: String,
baseApkPath: String,
path: String,
manifestArray: TypedArray,
isCoreApp: Boolean
) = ParsingPackageImpl(packageName, baseCodePath, codePath, manifestArray)
) = ParsingPackageImpl(packageName, baseApkPath, path, manifestArray)
})
override fun parseImpl(file: File) =

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2016 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.os;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ParcelObtainPerfTest {
private static final int ITERATIONS = 1_000_000;
@Test
public void timeContention_01() throws Exception {
timeContention(1);
}
@Test
public void timeContention_04() throws Exception {
timeContention(4);
}
@Test
public void timeContention_16() throws Exception {
timeContention(16);
}
private static void timeContention(int numThreads) throws Exception {
final long start = SystemClock.elapsedRealtime();
{
final ObtainThread[] threads = new ObtainThread[numThreads];
for (int i = 0; i < numThreads; i++) {
final ObtainThread thread = new ObtainThread(ITERATIONS / numThreads);
thread.start();
threads[i] = thread;
}
for (int i = 0; i < numThreads; i++) {
threads[i].join();
}
}
final long duration = SystemClock.elapsedRealtime() - start;
final Bundle results = new Bundle();
results.putLong("duration", duration);
InstrumentationRegistry.getInstrumentation().sendStatus(0, results);
}
public static class ObtainThread extends Thread {
public int iterations;
public ObtainThread(int iterations) {
this.iterations = iterations;
}
@Override
public void run() {
while (iterations-- > 0) {
final Parcel data = Parcel.obtain();
final Parcel reply = Parcel.obtain();
try {
data.writeInt(32);
reply.writeInt(32);
} finally {
reply.recycle();
data.recycle();
}
}
}
}
}

View File

@@ -158,21 +158,6 @@ public class ParcelPerfTest {
}
}
@Test
public void timeObtainRecycle() {
// Use up the pooled instances.
// A lot bigger than the actual size but in case someone increased it.
final int POOL_SIZE = 100;
for (int i = 0; i < POOL_SIZE; i++) {
Parcel.obtain();
}
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
Parcel.obtain().recycle();
}
}
@Test
public void timeWriteException() {
timeWriteException(false);

View File

@@ -0,0 +1,70 @@
/*
* 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.os;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import androidx.test.filters.LargeTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
@LargeTest
@RunWith(Parameterized.class)
public class ParcelStringPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Parameterized.Parameter(0)
public String mName;
@Parameterized.Parameter(1)
public String mValue;
@Parameterized.Parameters(name = "{0}")
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][] {
{ "simple", "com.example.typical_package_name" },
{ "complex", "從不喜歡孤單一個 - 蘇永康/吳雨霏" },
});
}
@Test
public void timeWriteString8() {
final Parcel parcel = Parcel.obtain();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
parcel.setDataPosition(0);
parcel.writeString8(mValue);
}
}
@Test
public void timeWriteString16() {
final Parcel parcel = Parcel.obtain();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
parcel.setDataPosition(0);
parcel.writeString16(mValue);
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.os;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import java.util.Arrays;
public class SomeProvider extends ContentProvider {
private Cursor mCursor;
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
return mCursor;
}
@Override
public String getType(Uri uri) {
throw new UnsupportedOperationException();
}
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
final char[] valueRaw = new char[512];
Arrays.fill(valueRaw, '!');
final String value = new String(valueRaw);
final int count = values.getAsInteger(Intent.EXTRA_INDEX);
final MatrixCursor cursor = new MatrixCursor(new String[] { "_id", "value" });
for (int i = 0; i < count; i++) {
MatrixCursor.RowBuilder row = cursor.newRow();
row.add(0, i);
row.add(1, value);
}
mCursor = cursor;
return 1;
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.os;
import static java.util.concurrent.TimeUnit.SECONDS;
import android.content.Context;
import androidx.benchmark.BenchmarkState;
import androidx.benchmark.junit4.BenchmarkRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@LargeTest
public class VibratorPerfTest {
@Rule
public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
private Vibrator mVibrator;
@Before
public void setUp() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
mVibrator = context.getSystemService(Vibrator.class);
}
@Test
public void testEffectClick() {
final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
mVibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
}
}
@Test
public void testOneShot() {
final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
mVibrator.vibrate(VibrationEffect.createOneShot(SECONDS.toMillis(2),
VibrationEffect.DEFAULT_AMPLITUDE));
}
}
@Test
public void testWaveform() {
final BenchmarkState state = mBenchmarkRule.getState();
long[] timings = new long[]{SECONDS.toMillis(1), SECONDS.toMillis(2), SECONDS.toMillis(1)};
while (state.keepRunning()) {
mVibrator.vibrate(VibrationEffect.createWaveform(timings, -1));
}
}
@Test
public void testCompose() {
final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
mVibrator.vibrate(
VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 100)
.compose());
}
}
@Test
public void testAreEffectsSupported() {
final BenchmarkState state = mBenchmarkRule.getState();
int[] effects = new int[]{VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK};
while (state.keepRunning()) {
mVibrator.areEffectsSupported(effects);
}
}
@Test
public void testArePrimitivesSupported() {
final BenchmarkState state = mBenchmarkRule.getState();
int[] primitives = new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK,
VibrationEffect.Composition.PRIMITIVE_TICK};
while (state.keepRunning()) {
mVibrator.arePrimitivesSupported(primitives);
}
}
}

View File

@@ -53,19 +53,19 @@ public class CanvasDrawTextTest {
final String text = mTextUtil.nextRandomParagraph(
WORD_LENGTH, 4 * 1024 * 1024 /* 4mb text */).toString();
final RenderNode node = RenderNode.create("benchmark", null);
final RenderNode child = RenderNode.create("child", null);
child.setLeftTopRightBottom(50, 50, 100, 100);
RecordingCanvas canvas = node.start(100, 100);
node.end(canvas);
canvas = child.start(50, 50);
child.end(canvas);
final Random r = new Random(0);
while (state.keepRunning()) {
state.pauseTiming();
RecordingCanvas canvas = node.beginRecording();
int start = r.nextInt(text.length() - 100);
state.resumeTiming();
canvas.drawText(text, start, start + 100, 0, 0, PAINT);
state.pauseTiming();
node.endRecording();
state.resumeTiming();
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2021 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.text;
import android.graphics.fonts.SystemFonts;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class SystemFontsPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Test
public void getAvailableFonts() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
SystemFonts.resetAvailableFonts();
System.gc();
state.resumeTiming();
SystemFonts.getAvailableFonts();
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.text;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.function.Supplier;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class TextUtilsPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
public static final String TEMPLATE = "Template that combines %s and %d together";
public String mVar1 = "example";
public int mVar2 = 42;
/**
* Measure overhead of formatting a string via {@link String#format}.
*/
@Test
public void timeFormatUpstream() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
String res = String.format(TEMPLATE, mVar1, mVar2);
}
}
/**
* Measure overhead of formatting a string via
* {@link TextUtils#formatSimple}.
*/
@Test
public void timeFormatLocal() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
String res = TextUtils.formatSimple(TEMPLATE, mVar1, mVar2);
}
}
/**
* Measure overhead of formatting a string inline.
*/
@Test
public void timeFormatInline() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
String res = "Template that combines " + mVar1 + " and " + mVar2 + " together";
}
}
/**
* Measure overhead of a passing null-check that uses a lambda to
* communicate a custom error message.
*/
@Test
public void timeFormat_Skip_Lambda() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
requireNonNull(this, () -> {
return String.format(TEMPLATE, mVar1, mVar2);
});
}
}
/**
* Measure overhead of a passing null-check that uses varargs to communicate
* a custom error message.
*/
@Test
public void timeFormat_Skip_Varargs() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
requireNonNull(this, TEMPLATE, mVar1, mVar2);
}
}
private static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
return obj;
}
private static <T> T requireNonNull(T obj, String format, Object... args) {
return obj;
}
}

View File

@@ -0,0 +1,132 @@
/*
* 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.text;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.RenderNode;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Random;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class VariableFontPerfTest {
private static final int WORD_LENGTH = 9; // Random word has 9 characters.
private static final boolean NO_STYLE_TEXT = false;
private static final TextPaint PAINT = new TextPaint();
public VariableFontPerfTest() {}
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private final TextPerfUtils mTextUtil = new TextPerfUtils();
@Before
public void setUp() {
mTextUtil.resetRandom(0 /* seed */);
}
@Test
public void testDraw_SetVariationOnce() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
final Paint paint = new Paint(PAINT);
paint.setFontVariationSettings("'wght' 700");
final RenderNode node = RenderNode.create("benchmark", null);
while (state.keepRunning()) {
state.pauseTiming();
final RecordingCanvas c = node.beginRecording(1200, 200);
state.resumeTiming();
c.drawText(text, 0, text.length(), 0, 100, paint);
state.pauseTiming();
node.endRecording();
state.resumeTiming();
}
}
@Test
public void testDraw_SetVariationEachDraw() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
final Paint paint = new Paint(PAINT);
final RenderNode node = RenderNode.create("benchmark", null);
while (state.keepRunning()) {
state.pauseTiming();
final RecordingCanvas c = node.beginRecording(1200, 200);
paint.setFontVariationSettings("'wght' 700");
state.resumeTiming();
c.drawText(text, 0, text.length(), 0, 100, paint);
state.pauseTiming();
node.endRecording();
state.resumeTiming();
}
}
@Test
public void testDraw_SetDifferentVariationEachDraw() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
final Paint paint = new Paint(PAINT);
final RenderNode node = RenderNode.create("benchmark", null);
final Random random = new Random(0);
while (state.keepRunning()) {
state.pauseTiming();
final RecordingCanvas c = node.beginRecording(1200, 200);
int weight = random.nextInt(1000);
paint.setFontVariationSettings("'wght' " + weight);
state.resumeTiming();
c.drawText(text, 0, text.length(), 0, 100, paint);
state.pauseTiming();
node.endRecording();
state.resumeTiming();
}
}
@Test
public void testSetFontVariationSettings() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final Paint paint = new Paint(PAINT);
final Random random = new Random(0);
while (state.keepRunning()) {
state.pauseTiming();
int weight = random.nextInt(1000);
state.resumeTiming();
paint.setFontVariationSettings("'wght' " + weight);
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.util;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import androidx.test.filters.LargeTest;
import dalvik.system.VMRuntime;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
@LargeTest
@RunWith(Parameterized.class)
public class CharsetUtilsPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Parameterized.Parameter(0)
public String mName;
@Parameterized.Parameter(1)
public String mValue;
@Parameterized.Parameters(name = "{0}")
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][] {
{ "simple", "com.example.typical_package_name" },
{ "complex", "從不喜歡孤單一個 - 蘇永康/吳雨霏" },
});
}
@Test
public void timeUpstream() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mValue.getBytes(StandardCharsets.UTF_8);
}
}
/**
* Measure performance of writing into a small buffer where bounds checking
* requires careful measurement of encoded size.
*/
@Test
public void timeLocal_SmallBuffer() {
final byte[] dest = (byte[]) VMRuntime.getRuntime().newNonMovableArray(byte.class, 64);
final long destPtr = VMRuntime.getRuntime().addressOf(dest);
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
CharsetUtils.toModifiedUtf8Bytes(mValue, destPtr, 0, dest.length);
}
}
/**
* Measure performance of writing into a large buffer where bounds checking
* only needs a simple worst-case 4-bytes-per-char check.
*/
@Test
public void timeLocal_LargeBuffer() {
final byte[] dest = (byte[]) VMRuntime.getRuntime().newNonMovableArray(byte.class, 1024);
final long destPtr = VMRuntime.getRuntime().addressOf(dest);
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
CharsetUtils.toModifiedUtf8Bytes(mValue, destPtr, 0, dest.length);
}
}
}

View File

@@ -0,0 +1,292 @@
/*
* 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.util;
import static org.junit.Assert.assertEquals;
import android.os.Bundle;
import android.os.Debug;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.HexDump;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.function.Supplier;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class XmlPerfTest {
/**
* Since allocation measurement adds overhead, it's disabled by default for
* performance runs. It can be manually enabled to compare GC behavior.
*/
private static final boolean MEASURE_ALLOC = false;
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Test
public void timeWrite_Fast() throws Exception {
doWrite(() -> Xml.newFastSerializer());
}
@Test
public void timeWrite_Binary() throws Exception {
doWrite(() -> Xml.newBinarySerializer());
}
private void doWrite(Supplier<TypedXmlSerializer> outFactory) throws Exception {
if (MEASURE_ALLOC) {
Debug.startAllocCounting();
}
int iterations = 0;
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
iterations++;
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
final TypedXmlSerializer out = outFactory.get();
out.setOutput(os, StandardCharsets.UTF_8.name());
write(out);
}
}
if (MEASURE_ALLOC) {
Debug.stopAllocCounting();
final Bundle results = new Bundle();
results.putLong("threadAllocCount_mean", Debug.getThreadAllocCount() / iterations);
results.putLong("threadAllocSize_mean", Debug.getThreadAllocSize() / iterations);
InstrumentationRegistry.getInstrumentation().sendStatus(0, results);
}
}
@Test
public void timeRead_Fast() throws Exception {
doRead(() -> Xml.newFastSerializer(), () -> Xml.newFastPullParser());
}
@Test
public void timeRead_Binary() throws Exception {
doRead(() -> Xml.newBinarySerializer(), () -> Xml.newBinaryPullParser());
}
private void doRead(Supplier<TypedXmlSerializer> outFactory,
Supplier<TypedXmlPullParser> inFactory) throws Exception {
final byte[] raw;
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
TypedXmlSerializer out = outFactory.get();
out.setOutput(os, StandardCharsets.UTF_8.name());
write(out);
raw = os.toByteArray();
}
if (MEASURE_ALLOC) {
Debug.startAllocCounting();
}
int iterations = 0;
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
iterations++;
try (ByteArrayInputStream is = new ByteArrayInputStream(raw)) {
TypedXmlPullParser xml = inFactory.get();
xml.setInput(is, StandardCharsets.UTF_8.name());
read(xml);
}
}
if (MEASURE_ALLOC) {
Debug.stopAllocCounting();
final Bundle results = new Bundle();
results.putLong("sizeBytes", raw.length);
results.putLong("threadAllocCount_mean", Debug.getThreadAllocCount() / iterations);
results.putLong("threadAllocSize_mean", Debug.getThreadAllocSize() / iterations);
InstrumentationRegistry.getInstrumentation().sendStatus(0, results);
} else {
final Bundle results = new Bundle();
results.putLong("sizeBytes", raw.length);
InstrumentationRegistry.getInstrumentation().sendStatus(0, results);
}
}
/**
* Not even joking, this is a typical public key blob stored in
* {@code packages.xml}.
*/
private static final byte[] KEY_BLOB = HexDump.hexStringToByteArray(""
+ "308204a830820390a003020102020900a1573d0f45bea193300d06092a864886f70d010105050030819"
+ "4310b3009060355040613025553311330110603550408130a43616c69666f726e696131163014060355"
+ "0407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e06035"
+ "5040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d"
+ "0109011613616e64726f696440616e64726f69642e636f6d301e170d3131303931393138343232355a1"
+ "70d3339303230343138343232355a308194310b3009060355040613025553311330110603550408130a"
+ "43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e0603550"
+ "40a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e"
+ "64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d3"
+ "0820120300d06092a864886f70d01010105000382010d00308201080282010100de1b51336afc909d8b"
+ "cca5920fcdc8940578ec5c253898930e985481cfdea75ba6fc54b1f7bb492a03d98db471ab4200103a8"
+ "314e60ee25fef6c8b83bc1b2b45b084874cffef148fa2001bb25c672b6beba50b7ac026b546da762ea2"
+ "23829a22b80ef286131f059d2c9b4ca71d54e515a8a3fd6bf5f12a2493dfc2619b337b032a7cf8bbd34"
+ "b833f2b93aeab3d325549a93272093943bb59dfc0197ae4861ff514e019b73f5cf10023ad1a032adb4b"
+ "9bbaeb4debecb4941d6a02381f1165e1ac884c1fca9525c5854dce2ad8ec839b8ce78442c16367efc07"
+ "778a337d3ca2cdf9792ac722b95d67c345f1c00976ec372f02bfcbef0262cc512a6845e71cfea0d0201"
+ "03a381fc3081f9301d0603551d0e0416041478a0fc4517fb70ff52210df33c8d32290a44b2bb3081c90"
+ "603551d230481c13081be801478a0fc4517fb70ff52210df33c8d32290a44b2bba1819aa48197308194"
+ "310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550"
+ "407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355"
+ "040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0"
+ "109011613616e64726f696440616e64726f69642e636f6d820900a1573d0f45bea193300c0603551d13"
+ "040530030101ff300d06092a864886f70d01010505000382010100977302dfbf668d7c61841c9c78d25"
+ "63bcda1b199e95e6275a799939981416909722713531157f3cdcfea94eea7bb79ca3ca972bd8058a36a"
+ "d1919291df42d7190678d4ea47a4b9552c9dfb260e6d0d9129b44615cd641c1080580e8a990dd768c6a"
+ "b500c3b964e185874e4105109d94c5bd8c405deb3cf0f7960a563bfab58169a956372167a7e2674a04c"
+ "4f80015d8f7869a7a4139aecbbdca2abc294144ee01e4109f0e47a518363cf6e9bf41f7560e94bdd4a5"
+ "d085234796b05c7a1389adfd489feec2a107955129d7991daa49afb3d327dc0dc4fe959789372b093a8"
+ "9c8dbfa41554f771c18015a6cb242a17e04d19d55d3b4664eae12caf2a11cd2b836e");
/**
* Typical list of permissions referenced in {@code packages.xml}.
*/
private static final String[] PERMS = new String[] {
"android.permission.ACCESS_CACHE_FILESYSTEM",
"android.permission.WRITE_SETTINGS",
"android.permission.MANAGE_EXTERNAL_STORAGE",
"android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS",
"android.permission.FOREGROUND_SERVICE",
"android.permission.RECEIVE_BOOT_COMPLETED",
"android.permission.WRITE_MEDIA_STORAGE",
"android.permission.INTERNET",
"android.permission.UPDATE_DEVICE_STATS",
"android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY",
"android.permission.MANAGE_USB",
"android.permission.ACCESS_ALL_DOWNLOADS",
"android.permission.ACCESS_DOWNLOAD_MANAGER",
"android.permission.MANAGE_USERS",
"android.permission.ACCESS_NETWORK_STATE",
"android.permission.ACCESS_MTP",
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS",
"android.permission.CLEAR_APP_CACHE",
"android.permission.CONNECTIVITY_INTERNAL",
"android.permission.START_ACTIVITIES_FROM_BACKGROUND",
"android.permission.QUERY_ALL_PACKAGES",
"android.permission.WAKE_LOCK",
"android.permission.UPDATE_APP_OPS_STATS",
};
/**
* Write a typical {@code packages.xml} file containing 100 applications,
* each of which defines signing key and permission information.
*/
private static void write(TypedXmlSerializer out) throws IOException {
out.startDocument(null, true);
out.startTag(null, "packages");
for (int i = 0; i < 100; i++) {
out.startTag(null, "package");
out.attribute(null, "name", "com.android.providers.media");
out.attribute(null, "codePath", "/system/priv-app/MediaProviderLegacy");
out.attribute(null, "nativeLibraryPath", "/system/priv-app/MediaProviderLegacy/lib");
out.attributeLong(null, "publicFlags", 944258629L);
out.attributeLong(null, "privateFlags", -1946152952L);
out.attributeLong(null, "ft", 1603899064000L);
out.attributeLong(null, "it", 1603899064000L);
out.attributeLong(null, "ut", 1603899064000L);
out.attributeInt(null, "version", 1024);
out.attributeInt(null, "sharedUserId", 10100);
out.attributeBoolean(null, "isOrphaned", true);
out.startTag(null, "sigs");
out.startTag(null, "cert");
out.attributeInt(null, "index", 10);
out.attributeBytesHex(null, "key", KEY_BLOB);
out.endTag(null, "cert");
out.endTag(null, "sigs");
out.startTag(null, "perms");
for (String perm : PERMS) {
out.startTag(null, "item");
out.attributeInterned(null, "name", perm);
out.attributeBoolean(null, "granted", true);
out.attributeInt(null, "flags", 0);
out.endTag(null, "item");
}
out.endTag(null, "perms");
out.endTag(null, "package");
}
out.endTag(null, "packages");
out.endDocument();
}
/**
* Read a typical {@code packages.xml} file containing 100 applications, and
* verify that data passes smell test.
*/
private static void read(TypedXmlPullParser xml) throws Exception {
int type;
int packages = 0;
int certs = 0;
int perms = 0;
while ((type = xml.next()) != XmlPullParser.END_DOCUMENT) {
final String tag = xml.getName();
if (type == XmlPullParser.START_TAG) {
if ("package".equals(tag)) {
xml.getAttributeValue(null, "name");
xml.getAttributeValue(null, "codePath");
xml.getAttributeValue(null, "nativeLibraryPath");
xml.getAttributeLong(null, "publicFlags");
assertEquals(-1946152952L, xml.getAttributeLong(null, "privateFlags"));
xml.getAttributeLong(null, "ft");
xml.getAttributeLong(null, "it");
xml.getAttributeLong(null, "ut");
xml.getAttributeInt(null, "version");
xml.getAttributeInt(null, "sharedUserId");
xml.getAttributeBoolean(null, "isOrphaned");
packages++;
} else if ("cert".equals(tag)) {
xml.getAttributeInt(null, "index");
xml.getAttributeBytesHex(null, "key");
certs++;
} else if ("item".equals(tag)) {
xml.getAttributeValue(null, "name");
xml.getAttributeBoolean(null, "granted");
xml.getAttributeInt(null, "flags");
perms++;
}
} else if (type == XmlPullParser.TEXT) {
xml.getText();
}
}
assertEquals(100, packages);
assertEquals(packages * 1, certs);
assertEquals(packages * PERMS.length, perms);
}
}

View File

@@ -16,24 +16,20 @@
package android.view;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.PathParser;
import androidx.benchmark.BenchmarkState;
import androidx.benchmark.junit4.BenchmarkRule;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,6 +37,10 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class CutoutSpecificationBenchmark {
private static final int DISPLAY_WIDTH = 1080;
private static final int DISPLAY_HEIGHT = 1920;
private static final float DISPLAY_DENSITY = 3.5f;
private static final String TAG = "CutoutSpecificationBenchmark";
private static final String BOTTOM_MARKER = "@bottom";
@@ -67,22 +67,7 @@ public class CutoutSpecificationBenchmark {
+ "Z\n"
+ "@dp";
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private Context mContext;
private DisplayMetrics mDisplayMetrics;
/**
* Setup the necessary member field used by test methods.
*/
@Before
public void setUp() {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mDisplayMetrics = new DisplayMetrics();
mContext.getDisplay().getRealMetrics(mDisplayMetrics);
}
public BenchmarkRule mBenchmarkRule = new BenchmarkRule();
private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
final RectF rectF = new RectF();
@@ -168,19 +153,18 @@ public class CutoutSpecificationBenchmark {
@Test
public void parseByOldMethodForDoubleCutout() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
oldMethodParsingSpec(DOUBLE_CUTOUT_SPEC, mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels, mDisplayMetrics.density);
oldMethodParsingSpec(DOUBLE_CUTOUT_SPEC, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_DENSITY);
}
}
@Test
public void parseByNewMethodForDoubleCutout() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
new CutoutSpecification.Parser(mDisplayMetrics.density,
mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)
new CutoutSpecification.Parser(DISPLAY_DENSITY, DISPLAY_WIDTH, DISPLAY_HEIGHT)
.parse(DOUBLE_CUTOUT_SPEC);
}
}
@@ -209,10 +193,10 @@ public class CutoutSpecificationBenchmark {
+ "@right\n"
+ "@dp";
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
new CutoutSpecification.Parser(mDisplayMetrics.density,
mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec);
new CutoutSpecification.Parser(DISPLAY_DENSITY, DISPLAY_WIDTH, DISPLAY_HEIGHT)
.parse(spec);
}
}
@@ -231,10 +215,10 @@ public class CutoutSpecificationBenchmark {
+ "Z\n"
+ "@dp";
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
new CutoutSpecification.Parser(mDisplayMetrics.density,
mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec);
new CutoutSpecification.Parser(DISPLAY_DENSITY, DISPLAY_WIDTH, DISPLAY_HEIGHT)
.parse(spec);
}
}
}

View File

@@ -0,0 +1,237 @@
/*
* 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.view;
import static android.view.Display.DEFAULT_DISPLAY;
import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.graphics.Rect;
import android.os.SystemClock;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.PerfTestActivity;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@LargeTest
@RunWith(Parameterized.class)
public class InputStageBenchmark {
@Parameterized.Parameters(name = "mShowIme({0}), mHandlePreIme({1})")
public static Collection cases() {
return Arrays.asList(new Object[][] {
{ false /* no ime */, false /* skip preime */},
{ true /* show ime */, false /* skip preime */},
{ true /* show ime */, true /* handle preime */}
});
}
@Rule
public final ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Parameterized.Parameter(0)
public boolean mShowIme;
@Parameterized.Parameter(1)
public boolean mHandlePreIme;
private Instrumentation mInstrumentation;
private Window mWindow;
private CountDownLatch mWaitForReceiveInput;
private static final long TIMEOUT_MS = 5000;
class InstrumentedView extends View {
InstrumentedView(Context context) {
super(context);
setFocusable(true);
}
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
if (mHandlePreIme) {
mWaitForReceiveInput.countDown();
}
return mHandlePreIme;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
mWaitForReceiveInput.countDown();
return true;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
mWaitForReceiveInput.countDown();
return true;
}
}
class InstrumentedEditText extends EditText {
InstrumentedEditText(Context context) {
super(context);
setFocusable(true);
}
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
if (mHandlePreIme) {
mWaitForReceiveInput.countDown();
}
return mHandlePreIme;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
mWaitForReceiveInput.countDown();
return true;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
mWaitForReceiveInput.countDown();
return true;
}
}
private CountDownLatch showSoftKeyboard(View view) {
final CountDownLatch waitForIme = new CountDownLatch(1);
view.setOnApplyWindowInsetsListener((v, insets) -> {
if (insets.isVisible(WindowInsets.Type.ime())) {
waitForIme.countDown();
}
return insets;
});
assertTrue("Failed to request focus.", view.requestFocus());
final InputMethodManager imm =
mActivityRule.getActivity().getSystemService(InputMethodManager.class);
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
return waitForIme;
}
@Before
public void setUp() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
final Activity activity = mActivityRule.getActivity();
final CountDownLatch[] waitForIme = new CountDownLatch[1];
mInstrumentation.runOnMainSync(() -> {
mWindow = mActivityRule.getActivity().getWindow();
if (mShowIme) {
final EditText edit = new InstrumentedEditText(activity);
mWindow.setContentView(edit);
waitForIme[0] = showSoftKeyboard(edit);
} else {
final View v = new InstrumentedView(activity);
// set FLAG_LOCAL_FOCUS_MODE to prevent delivering input events to the ime
// in ImeInputStage.
mWindow.addFlags(WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE);
mWindow.setContentView(v);
assertTrue("Failed to request focus.", v.requestFocus());
}
});
if (waitForIme[0] != null) {
try {
assertTrue("Failed to show InputMethod.",
waitForIme[0].await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
mInstrumentation.waitForIdleSync();
}
private void injectInputEvent(InputEvent event) {
mWaitForReceiveInput = new CountDownLatch(1);
mInstrumentation.runOnMainSync(() -> mWindow.injectInputEvent(event));
try {
mWaitForReceiveInput.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Test
public void testKeyEvent() {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
final KeyEvent eventDown =
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACKSLASH);
injectInputEvent(eventDown);
state.pauseTiming();
final KeyEvent eventUp =
new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACKSLASH);
injectInputEvent(eventUp);
state.resumeTiming();
}
}
@Test
public void testMotionEvent() {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final Rect contentFrame = new Rect();
mInstrumentation.runOnMainSync(() ->
mWindow.getDecorView().getBoundsOnScreen(contentFrame));
final int x = contentFrame.centerX();
final int y = contentFrame.centerY();
final long eventTime = SystemClock.uptimeMillis();
while (state.keepRunning()) {
final MotionEvent eventDown = MotionEvent.obtain(eventTime, eventTime,
MotionEvent.ACTION_DOWN, x, y,
1.0f /* pressure */, 1.0f /* size */, 0 /* metaState */,
1.0f /* xPrecision */, 1.0f /* yPrecision */,
0 /* deviceId */, 0 /* edgeFlags */,
InputDevice.SOURCE_TOUCHSCREEN, DEFAULT_DISPLAY);
injectInputEvent(eventDown);
state.pauseTiming();
final MotionEvent eventUp = MotionEvent.obtain(eventTime, eventTime,
MotionEvent.ACTION_UP, x, y,
1.0f /* pressure */, 1.0f /* size */, 0 /* metaState */,
1.0f /* xPrecision */, 1.0f /* yPrecision */,
0 /* deviceId */, 0 /* edgeFlags */,
InputDevice.SOURCE_TOUCHSCREEN, DEFAULT_DISPLAY);
injectInputEvent(eventUp);
state.resumeTiming();
}
}
}

View File

@@ -16,30 +16,45 @@
package android.view;
import static junit.framework.Assert.assertTrue;
import android.content.Context;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.PerfTestActivity;
import android.widget.FrameLayout;
import androidx.benchmark.BenchmarkState;
import androidx.benchmark.junit4.BenchmarkRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import com.android.perftests.core.R;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@LargeTest
public class ViewPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
@Rule
public final ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
private Context mContext;
@Before
public void setUp() {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
}
@Test
public void testSimpleViewInflate() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
LayoutInflater inflater = LayoutInflater.from(context);
FrameLayout root = new FrameLayout(context);
final BenchmarkState state = mBenchmarkRule.getState();
LayoutInflater inflater = LayoutInflater.from(mContext);
FrameLayout root = new FrameLayout(mContext);
while (state.keepRunning()) {
inflater.inflate(R.layout.test_simple_view, root, false);
}
@@ -47,12 +62,33 @@ public class ViewPerfTest {
@Test
public void testTwelveKeyInflate() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
LayoutInflater inflater = LayoutInflater.from(context);
FrameLayout root = new FrameLayout(context);
final BenchmarkState state = mBenchmarkRule.getState();
LayoutInflater inflater = LayoutInflater.from(mContext);
FrameLayout root = new FrameLayout(mContext);
while (state.keepRunning()) {
inflater.inflate(R.layout.twelve_key_entry, root, false);
}
}
@Test
public void testPerformHapticFeedback() throws Throwable {
final BenchmarkState state = mBenchmarkRule.getState();
mActivityRule.runOnUiThread(() -> {
state.pauseTiming();
View view = new View(mContext);
mActivityRule.getActivity().setContentView(view);
assertTrue("View needs to be attached to Window to perform haptic feedback",
view.isAttachedToWindow());
state.resumeTiming();
// Disable settings so perform will never be ignored.
int flags = HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
| HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
while (state.keepRunning()) {
assertTrue("Call to performHapticFeedback was ignored",
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS, flags));
}
});
}
}

View File

@@ -21,14 +21,14 @@ import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.PerfTestActivity;
import android.view.View.MeasureSpec;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.benchmark.BenchmarkState;
import androidx.benchmark.junit4.BenchmarkRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -50,7 +50,7 @@ public class ViewShowHidePerfTest {
new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
public Context getContext() {
return InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -143,7 +143,7 @@ public class ViewShowHidePerfTest {
private void testParentWithChild(TestCallback callback) throws Throwable {
mActivityRule.runOnUiThread(() -> {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final BenchmarkState state = mBenchmarkRule.getState();
FrameLayout parent = new FrameLayout(getContext());
mActivityRule.getActivity().setContentView(parent);

View File

@@ -43,8 +43,8 @@ public class EditTextLongTextPerfTest {
@Parameters(name = "{0}")
public static Collection cases() {
return Arrays.asList(new Object[][] {
{ "10x30K", 10, 30000 },
{ "300x1K", 300, 1000 },
{ "10x3K", 10, 3000 },
{ "30x1K", 30, 1000 },
});
}

View File

@@ -0,0 +1,132 @@
/*
* 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 com.android.internal.util;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class FastDataPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private static final int OUTPUT_SIZE = 64000;
private static final int BUFFER_SIZE = 4096;
@Test
public void timeWrite_Upstream() throws IOException {
final ByteArrayOutputStream os = new ByteArrayOutputStream(OUTPUT_SIZE);
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
os.reset();
final BufferedOutputStream bos = new BufferedOutputStream(os, BUFFER_SIZE);
final DataOutput out = new DataOutputStream(bos);
doWrite(out);
bos.flush();
}
}
@Test
public void timeWrite_Local() throws IOException {
final ByteArrayOutputStream os = new ByteArrayOutputStream(OUTPUT_SIZE);
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
os.reset();
final FastDataOutput out = new FastDataOutput(os, BUFFER_SIZE);
doWrite(out);
out.flush();
}
}
@Test
public void timeRead_Upstream() throws Exception {
final ByteArrayInputStream is = new ByteArrayInputStream(doWrite());
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
is.reset();
final BufferedInputStream bis = new BufferedInputStream(is, BUFFER_SIZE);
final DataInput in = new DataInputStream(bis);
doRead(in);
}
}
@Test
public void timeRead_Local() throws Exception {
final ByteArrayInputStream is = new ByteArrayInputStream(doWrite());
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
is.reset();
final DataInput in = new FastDataInput(is, BUFFER_SIZE);
doRead(in);
}
}
/**
* Since each iteration is around 64 bytes, we need to iterate many times to
* exercise the buffer logic.
*/
private static final int REPEATS = 1000;
private static byte[] doWrite() throws IOException {
final ByteArrayOutputStream os = new ByteArrayOutputStream(OUTPUT_SIZE);
final DataOutput out = new DataOutputStream(os);
doWrite(out);
return os.toByteArray();
}
private static void doWrite(DataOutput out) throws IOException {
for (int i = 0; i < REPEATS; i++) {
out.writeByte(Byte.MAX_VALUE);
out.writeShort(Short.MAX_VALUE);
out.writeInt(Integer.MAX_VALUE);
out.writeLong(Long.MAX_VALUE);
out.writeFloat(Float.MAX_VALUE);
out.writeDouble(Double.MAX_VALUE);
out.writeUTF("com.example.typical_package_name");
}
}
private static void doRead(DataInput in) throws IOException {
for (int i = 0; i < REPEATS; i++) {
in.readByte();
in.readShort();
in.readInt();
in.readLong();
in.readFloat();
in.readDouble();
in.readUTF();
}
}
}

View File

@@ -0,0 +1,39 @@
// 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 {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_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",
}

View 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>

View 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>

View 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.

View 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" />

View File

@@ -0,0 +1,23 @@
/*
* 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 android.perftests.utils.WindowPerfRunPreconditionBase;
/** Prepare the preconditions before running performance test. */
public class ImePerfRunPrecondition extends WindowPerfRunPreconditionBase {
}

View File

@@ -0,0 +1,501 @@
/*
* 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.perftests.utils.TestUtils.getOnMainSync;
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 static org.junit.Assert.assertTrue;
import android.annotation.UiThread;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.inputmethodservice.InputMethodService;
import android.os.Process;
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.AtomicBoolean;
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();
private static final long ANIMATION_NOT_STARTED = -1;
@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",
"IMS.updateFullscreenMode",
"IMS.onComputeInsets",
"IMS.showWindow"
};
/** IMF show methods to log in trace. */
private String[] mShowMethodsCold = {
"IMS.bindInput",
"IMS.initializeInternal",
"IMS.restartInput",
"IMS.onCreate",
"IMS.initSoftInputWindow",
"IMS.resetStateForNewConfiguration",
"IMMS.onServiceConnected",
"IMMS.sessionCreated",
"IMMS.startInputOrWindowGainedFocus"
};
/** 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;
private static int sPid;
@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
sPid = Process.myPid();
return view;
}
static int getPid() {
return sPid;
}
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 testShowImeWarm() throws Throwable {
testShowOrHideImeWarm(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 {
testShowOrHideImeWarm(false /* 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 testShowImeCold() throws Throwable {
mTraceMethods = new TraceMarkParser(
buildArray(mCommonMethods, mShowMethods, mShowMethodsCold));
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
state.setCustomizedIterations(getProfilingIterations(), this);
if (state.isWarmingUp()) {
// we don't need to warmup for cold start.
return;
}
long measuredTimeNs = 0;
while (state.keepRunning(measuredTimeNs)) {
killBaselineIme();
try (ImeSession imeSession = new ImeSession(BaselineIme.getName(
getInstrumentation().getContext()))) {
final AtomicReference<CountDownLatch> latchStart = new AtomicReference<>();
final Activity activity = getActivityWithFocus();
setImeListener(activity, latchStart, null /* latchEnd */);
latchStart.set(new CountDownLatch(1));
if (!mIsTraceStarted) {
startAsyncAtrace();
}
final WindowInsetsController controller =
activity.getWindow().getDecorView().getWindowInsetsController();
AtomicLong startTime = new AtomicLong();
activity.runOnUiThread(() -> {
startTime.set(SystemClock.elapsedRealtimeNanos());
controller.show(WindowInsets.Type.ime());
});
measuredTimeNs = waitForAnimationStart(latchStart, startTime);
mActivityRule.finishActivity();
}
}
stopAsyncAtrace();
addResultToState(state);
}
private void killBaselineIme() {
assertTrue("PID of test and IME can't be same",
Process.myPid() != BaselineIme.getPid());
Process.killProcess(BaselineIme.getPid());
}
private void testShowOrHideImeWarm(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);
// For measuring hide, lets show IME first.
if (!show) {
initLatch(latchStart, latchEnd);
AtomicBoolean showCalled = new AtomicBoolean();
getInstrumentation().runOnMainSync(() -> {
if (!isImeVisible(activity)) {
controller.show(WindowInsets.Type.ime());
showCalled.set(true);
}
});
if (showCalled.get()) {
PollingCheck.check("IME show animation should finish ",
TIMEOUT_1_S_IN_MS * 3,
() -> latchStart.get().getCount() == 0
&& latchEnd.get().getCount() == 0);
}
}
if (!mIsTraceStarted && !state.isWarmingUp()) {
startAsyncAtrace();
mIsTraceStarted = true;
}
AtomicLong startTime = new AtomicLong();
AtomicBoolean unexpectedVisibility = new AtomicBoolean();
initLatch(latchStart, latchEnd);
getInstrumentation().runOnMainSync(() -> {
boolean isVisible = isImeVisible(activity);
startTime.set(SystemClock.elapsedRealtimeNanos());
if (show && !isVisible) {
controller.show(WindowInsets.Type.ime());
} else if (!show && isVisible) {
controller.hide(WindowInsets.Type.ime());
} else {
// ignore this iteration as unexpected IME visibility was encountered.
unexpectedVisibility.set(true);
}
});
if (!unexpectedVisibility.get()) {
long timeElapsed = waitForAnimationStart(latchStart, startTime);
if (timeElapsed != ANIMATION_NOT_STARTED) {
measuredTimeNs = timeElapsed;
// wait for animation to end or we may start two animations and timing
// will not be measured accurately.
waitForAnimationEnd(latchEnd);
}
}
// hide IME before next iteration.
if (show) {
initLatch(latchStart, latchEnd);
activity.runOnUiThread(() -> controller.hide(WindowInsets.Type.ime()));
try {
latchEnd.get().await(TIMEOUT_1_S_IN_MS * 5, TimeUnit.MILLISECONDS);
if (latchEnd.get().getCount() != 0
&& getOnMainSync(() -> isImeVisible(activity))) {
Assert.fail("IME hide animation should finish.");
}
} catch (InterruptedException e) {
}
}
}
} finally {
if (mIsTraceStarted) {
stopAsyncAtrace();
}
}
mActivityRule.finishActivity();
addResultToState(state);
}
private void initLatch(AtomicReference<CountDownLatch> latchStart,
AtomicReference<CountDownLatch> latchEnd) {
latchStart.set(new CountDownLatch(1));
latchEnd.set(new CountDownLatch(1));
}
@UiThread
private boolean isImeVisible(@NonNull final Activity activity) {
return activity.getWindow().getDecorView().getRootWindowInsets().isVisible(
WindowInsets.Type.ime());
}
private long waitForAnimationStart(
AtomicReference<CountDownLatch> latchStart, AtomicLong startTime) {
try {
latchStart.get().await(5, TimeUnit.SECONDS);
if (latchStart.get().getCount() != 0) {
return ANIMATION_NOT_STARTED;
}
} catch (InterruptedException e) { }
return SystemClock.elapsedRealtimeNanos() - startTime.get();
}
private void waitForAnimationEnd(AtomicReference<CountDownLatch> latchEnd) {
try {
latchEnd.get().await(3, TimeUnit.SECONDS);
} catch (InterruptedException e) { }
}
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() {
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
startAsyncAtrace("wm view");
}
private void stopAsyncAtrace() {
if (!mIsTraceStarted) {
return;
}
mIsTraceStarted = false;
final InputStream inputStream = stopAsyncAtraceWithStream();
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.
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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 android.content.Intent;
import android.perftests.utils.PerfTestActivity;
import android.perftests.utils.WindowPerfTestBase;
public class ImePerfTestBase extends WindowPerfTestBase {
static final long TIMEOUT_1_S_IN_MS = 1 * 1000L;
/** Provides an activity that contains an edit text view.*/
static class PerfTestActivityRule extends PerfTestActivityRuleBase {
@Override
public PerfTestActivity launchActivity(Intent intent) {
intent.putExtra(INTENT_EXTRA_ADD_EDIT_TEXT, true);
return super.launchActivity(intent);
}
}
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;
}
}

View File

@@ -17,16 +17,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.perftests.multiuser">
<uses-sdk android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application>
<uses-library android:name="android.test.runner" />

View File

@@ -29,7 +29,7 @@
<option name="push-file" key="trace_config_multi_user.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_multi_user.textproto" value="/sdcard/sample.textproto" />
<option name="push-file" key="trace_config_detailed.textproto" value="/sdcard/sample.textproto" />
</target_preparer>
<!-- Needed for pulling the collected trace config on to the host -->

View File

@@ -22,6 +22,9 @@ import java.util.Collections;
import java.util.concurrent.TimeUnit;
public class BenchmarkResults {
/** If the test fails, output this value as a signal of the failure. */
public static final long DECLARED_VALUE_IF_ERROR_MS = -10;
private final ArrayList<Long> mResults = new ArrayList<>();
public void addDuration(long duration) {
@@ -42,6 +45,28 @@ public class BenchmarkResults {
return stats;
}
/**
* Same as {@link #getStatsToReport()} but for failure,
* using {@link #DECLARED_VALUE_IF_ERROR_MS}.
*/
public static Bundle getFailedStatsToReport() {
final Bundle stats = new Bundle();
stats.putDouble("Mean (ms)", DECLARED_VALUE_IF_ERROR_MS);
return stats;
}
/**
* Same as {@link #getStatsToLog()} but for failure,
* using {@link #DECLARED_VALUE_IF_ERROR_MS}.
*/
public static Bundle getFailedStatsToLog() {
final Bundle stats = new Bundle();
stats.putDouble("Mean (ms)", DECLARED_VALUE_IF_ERROR_MS);
stats.putDouble("Median (ms)", DECLARED_VALUE_IF_ERROR_MS);
stats.putDouble("Sigma (ms)", DECLARED_VALUE_IF_ERROR_MS);
return stats;
}
public ArrayList<Long> getAllDurations() {
return mResults;
}

View File

@@ -15,6 +15,8 @@
*/
package android.multiuser;
import static android.multiuser.BenchmarkResults.DECLARED_VALUE_IF_ERROR_MS;
import android.app.Activity;
import android.app.Instrumentation;
import android.os.Bundle;
@@ -40,28 +42,70 @@ public class BenchmarkResultsReporter implements TestRule {
return new Statement() {
@Override
public void evaluate() throws Throwable {
base.evaluate();
final Bundle stats = mRunner.getStatsToReport();
final String summary = getSummaryString(description.getMethodName(),
mRunner.getStatsToLog());
logSummary(description.getTestClass().getSimpleName(), summary,
mRunner.getAllDurations());
stats.putString(Instrumentation.REPORT_KEY_STREAMRESULT, summary);
InstrumentationRegistry.getInstrumentation().sendStatus(
Activity.RESULT_OK, stats);
final String tag = description.getTestClass().getSimpleName();
final String methodName = description.getMethodName();
Throwable error = null;
try {
base.evaluate();
error = mRunner.getErrorOrNull();
} catch (Exception e) {
error = e;
}
if (error != null) {
Log.e(tag, "Test " + methodName + " failed.", error);
Log.d(tag, "Logcat displays the results ignoring the fact that it failed;\n"
+ "however, fake results of " + DECLARED_VALUE_IF_ERROR_MS + "ms "
+ "will be reported to the instrumentation caller to signify failure.");
}
final String summary = getSummaryString(methodName, mRunner.getStatsToLog());
logSummary(tag, summary, mRunner.getAllDurations());
Bundle stats;
if (error == null) {
stats = mRunner.getStatsToReport();
stats.putString(Instrumentation.REPORT_KEY_STREAMRESULT, summary);
} else {
stats = BenchmarkResults.getFailedStatsToReport();
final String failSummary = getSummaryString(methodName,
BenchmarkResults.getFailedStatsToLog());
stats.putString(Instrumentation.REPORT_KEY_STREAMRESULT, failSummary);
}
InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, stats);
if (error != null) {
throw error;
}
}
};
}
/**
* Prints, for example:
* UserLifecycleTests: (summary string)
* UserLifecycleTests: 1->101
* UserLifecycleTests: 2->102
* UserLifecycleTests: 3->103
* UserLifecycleTests: 4->102
*/
private void logSummary(String tag, String summary, ArrayList<Long> durations) {
final StringBuilder sb = new StringBuilder(summary);
final int size = durations.size();
for (int i = 0; i < size; ++i) {
sb.append("\n").append(i).append("->").append(durations.get(i));
sb.append("\n").append(i+1).append("->").append(durations.get(i));
}
Log.d(tag, sb.toString());
}
/**
* For example:
* testName
* Sigma (ms): 1
* Mean (ms): 2
* Median (ms): 3
*/
private String getSummaryString(String testName, Bundle stats) {
final StringBuilder sb = new StringBuilder();
sb.append("\n\n").append(getKey(testName));

View File

@@ -15,6 +15,7 @@
*/
package android.multiuser;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.SystemClock;
import android.perftests.utils.ShellHelper;
@@ -35,12 +36,14 @@ public class BenchmarkRunner {
private final BenchmarkResults mResults = new BenchmarkResults();
private int mState = NOT_STARTED; // Current benchmark state.
private int mIteration;
private int mIteration = 1;
public long mStartTimeNs;
public long mPausedDurationNs;
public long mPausedTimeNs;
private Throwable mFirstFailure = null;
public boolean keepRunning() {
switch (mState) {
case NOT_STARTED:
@@ -61,7 +64,7 @@ public class BenchmarkRunner {
private boolean startNextTestRun() {
mResults.addDuration(System.nanoTime() - mStartTimeNs - mPausedDurationNs);
if (mIteration == NUM_ITERATIONS) {
if (mIteration == NUM_ITERATIONS + 1) {
mState = FINISHED;
return false;
} else {
@@ -104,4 +107,30 @@ public class BenchmarkRunner {
public ArrayList<Long> getAllDurations() {
return mResults.getAllDurations();
}
/** Returns which iteration (starting at 1) the Runner is currently on. */
public int getIteration() {
return mIteration;
}
/**
* Marks the test run as failed, along with a message of why.
* Only the first fail message is retained.
*/
public void markAsFailed(Throwable err) {
if (mFirstFailure == null) {
mFirstFailure = err;
}
}
/** Gets the failure message if the test failed; otherwise {@code null}. */
public @Nullable Throwable getErrorOrNull() {
if (mFirstFailure != null) {
return mFirstFailure;
}
if (mState != FINISHED) {
return new AssertionError("BenchmarkRunner state is not FINISHED.");
}
return null;
}
}

View File

@@ -17,6 +17,7 @@ package android.multiuser;
import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppGlobals;
@@ -38,6 +39,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -62,7 +64,10 @@ import java.util.concurrent.TimeUnit;
/**
* Perf tests for user life cycle events.
*
* Running the tests:
* To run the tests: atest UserLifecycleTests
*
*
* Old methods for running the tests:
*
* make MultiUserPerfTests &&
* adb install -r \
@@ -85,6 +90,10 @@ public class UserLifecycleTests {
private static final int TIMEOUT_IN_SECOND = 30;
private static final int CHECK_USER_REMOVED_INTERVAL_MS = 200;
/** Name of users/profiles in the test. Users with this name may be freely removed. */
private static final String TEST_USER_NAME = "UserLifecycleTests_test_user";
/** Name of dummy package used when timing how long app launches take. */
private static final String DUMMY_PACKAGE_NAME = "perftests.multiuser.apps.dummyapp";
// Copy of UserSystemPackageInstaller whitelist mode constants.
@@ -115,6 +124,11 @@ public class UserLifecycleTests {
mUsersToRemove = new ArrayList<>();
mPm = context.getPackageManager();
mHasManagedUserFeature = mPm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
removeAnyPreviousTestUsers();
if (mAm.getCurrentUser() != UserHandle.USER_SYSTEM) {
Log.w(TAG, "WARNING: Tests are being run from user " + mAm.getCurrentUser()
+ " rather than the system user");
}
}
@After
@@ -140,7 +154,7 @@ public class UserLifecycleTests {
}
@Test
public void createAndStartUser() throws Exception {
public void createAndStartUser() throws RemoteException {
while (mRunner.keepRunning()) {
final int userId = createUserNoFlags();
@@ -149,7 +163,7 @@ public class UserLifecycleTests {
// Don't use this.startUserInBackgroundAndWaitForUnlock() since only waiting until
// ACTION_USER_STARTED.
mIam.startUserInBackground(userId);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
waitForLatch("Failed to achieve ACTION_USER_STARTED for user " + userId, latch);
mRunner.pauseTiming();
removeUser(userId);
@@ -161,7 +175,7 @@ public class UserLifecycleTests {
* Measures the time until ACTION_USER_STARTED is received.
*/
@Test
public void startUser() throws Exception {
public void startUser() throws RemoteException {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createUserNoFlags();
@@ -170,7 +184,7 @@ public class UserLifecycleTests {
mRunner.resumeTiming();
mIam.startUserInBackground(userId);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
waitForLatch("Failed to achieve ACTION_USER_STARTED for user " + userId, latch);
mRunner.pauseTiming();
removeUser(userId);
@@ -182,7 +196,7 @@ public class UserLifecycleTests {
* Measures the time until unlock listener is triggered and user is unlocked.
*/
@Test
public void startAndUnlockUser() throws Exception {
public void startAndUnlockUser() {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createUserNoFlags();
@@ -197,10 +211,8 @@ public class UserLifecycleTests {
}
}
@Test
public void switchUser() throws Exception {
public void switchUser() throws RemoteException {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
@@ -210,7 +222,7 @@ public class UserLifecycleTests {
switchUser(userId);
mRunner.pauseTiming();
switchUser(startUser);
switchUserNoCheck(startUser);
removeUser(userId);
mRunner.resumeTiming();
}
@@ -218,7 +230,7 @@ public class UserLifecycleTests {
/** Tests switching to an already-created, but no-longer-running, user. */
@Test
public void switchUser_stopped() throws Exception {
public void switchUser_stopped() throws RemoteException {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
@@ -228,11 +240,11 @@ public class UserLifecycleTests {
mRunner.resumeTiming();
mAm.switchUser(testUser);
boolean success = latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
waitForLatch("Failed to achieve 2nd ACTION_USER_UNLOCKED for user " + testUser, latch);
mRunner.pauseTiming();
attestTrue("Failed to achieve 2nd ACTION_USER_UNLOCKED for user " + testUser, success);
switchUser(startUser);
switchUserNoCheck(startUser);
removeUser(testUser);
mRunner.resumeTiming();
}
@@ -240,7 +252,7 @@ public class UserLifecycleTests {
/** Tests switching to an already-created already-running non-owner user. */
@Test
public void switchUser_running() throws Exception {
public void switchUser_running() throws RemoteException {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
@@ -250,22 +262,21 @@ public class UserLifecycleTests {
switchUser(testUser);
mRunner.pauseTiming();
attestTrue("Failed to switch to user " + testUser, mAm.isUserRunning(testUser));
switchUser(startUser);
switchUserNoCheck(startUser);
removeUser(testUser);
mRunner.resumeTiming();
}
}
@Test
public void stopUser() throws Exception {
public void stopUser() throws RemoteException {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createUserNoFlags();
final CountDownLatch latch = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
mIam.startUserInBackground(userId);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
waitForLatch("Failed to achieve ACTION_USER_STARTED for user " + userId, latch);
mRunner.resumeTiming();
stopUser(userId, false);
@@ -277,7 +288,7 @@ public class UserLifecycleTests {
}
@Test
public void lockedBootCompleted() throws Exception {
public void lockedBootCompleted() throws RemoteException {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
@@ -287,17 +298,17 @@ public class UserLifecycleTests {
mRunner.resumeTiming();
mAm.switchUser(userId);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
waitForLatch("Failed to achieve onLockedBootComplete for user " + userId, latch);
mRunner.pauseTiming();
switchUser(startUser);
switchUserNoCheck(startUser);
removeUser(userId);
mRunner.resumeTiming();
}
}
@Test
public void ephemeralUserStopped() throws Exception {
public void ephemeralUserStopped() throws RemoteException {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
@@ -318,10 +329,14 @@ public class UserLifecycleTests {
mRunner.resumeTiming();
mAm.switchUser(startUser);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
waitForLatch("Failed to achieve ACTION_USER_STOPPED for user " + userId, latch);
mRunner.pauseTiming();
switchLatch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
try {
switchLatch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "Thread interrupted unexpectedly while waiting for switch.", e);
}
removeUser(userId);
mRunner.resumeTiming();
}
@@ -329,7 +344,7 @@ public class UserLifecycleTests {
/** Tests creating a new profile. */
@Test
public void managedProfileCreate() throws Exception {
public void managedProfileCreate() {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -344,7 +359,7 @@ public class UserLifecycleTests {
/** Tests starting (unlocking) a newly-created profile. */
@Test
public void managedProfileUnlock() throws Exception {
public void managedProfileUnlock() {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -362,7 +377,7 @@ public class UserLifecycleTests {
/** Tests starting (unlocking) an already-created, but no-longer-running, profile. */
@Test
public void managedProfileUnlock_stopped() throws Exception {
public void managedProfileUnlock_stopped() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -385,7 +400,7 @@ public class UserLifecycleTests {
* Tests starting (unlocking) and launching an already-installed app in a newly-created profile.
*/
@Test
public void managedProfileUnlockAndLaunchApp() throws Exception {
public void managedProfileUnlockAndLaunchApp() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -411,7 +426,7 @@ public class UserLifecycleTests {
* {@link #managedProfileUnlock_stopped}}.
*/
@Test
public void managedProfileUnlockAndLaunchApp_stopped() throws Exception {
public void managedProfileUnlockAndLaunchApp_stopped() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -422,7 +437,7 @@ public class UserLifecycleTests {
startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
stopUser(userId, true);
TimeUnit.SECONDS.sleep(1); // Brief cool-down before re-starting profile.
SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
mRunner.resumeTiming();
startUserInBackgroundAndWaitForUnlock(userId);
@@ -436,7 +451,7 @@ public class UserLifecycleTests {
/** Tests installing a pre-existing app in a newly-created profile. */
@Test
public void managedProfileInstall() throws Exception {
public void managedProfileInstall() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -457,7 +472,7 @@ public class UserLifecycleTests {
* and launching that app in it.
*/
@Test
public void managedProfileCreateUnlockInstallAndLaunchApp() throws Exception {
public void managedProfileCreateUnlockInstallAndLaunchApp() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -478,7 +493,7 @@ public class UserLifecycleTests {
/** Tests stopping a profile. */
@Test
public void managedProfileStopped() throws Exception {
public void managedProfileStopped() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -498,7 +513,7 @@ public class UserLifecycleTests {
// TODO: This is just a POC. Do this properly and add more.
/** Tests starting (unlocking) a newly-created profile using the user-type-pkg-whitelist. */
@Test
public void managedProfileUnlock_usingWhitelist() throws Exception {
public void managedProfileUnlock_usingWhitelist() {
assumeTrue(mHasManagedUserFeature);
final int origMode = getUserTypePackageWhitelistMode();
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE
@@ -522,7 +537,7 @@ public class UserLifecycleTests {
}
/** Tests starting (unlocking) a newly-created profile NOT using the user-type-pkg-whitelist. */
@Test
public void managedProfileUnlock_notUsingWhitelist() throws Exception {
public void managedProfileUnlock_notUsingWhitelist() {
assumeTrue(mHasManagedUserFeature);
final int origMode = getUserTypePackageWhitelistMode();
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE);
@@ -551,19 +566,17 @@ public class UserLifecycleTests {
/** Creates a new user with the given flags, returning its userId. */
private int createUserWithFlags(int flags) {
int userId = mUm.createUser("TestUser", flags).id;
int userId = mUm.createUser(TEST_USER_NAME, flags).id;
mUsersToRemove.add(userId);
return userId;
}
/** Creates a managed (work) profile under the current user, returning its userId. */
private int createManagedProfile() {
final UserInfo userInfo = mUm.createProfileForUser("TestProfile",
final UserInfo userInfo = mUm.createProfileForUser(TEST_USER_NAME,
UserManager.USER_TYPE_PROFILE_MANAGED, /* flags */ 0, mAm.getCurrentUser());
if (userInfo == null) {
throw new IllegalStateException("Creating managed profile failed. Most likely there is "
+ "already a pre-existing profile on the device.");
}
attestFalse("Creating managed profile failed. Most likely there is "
+ "already a pre-existing profile on the device.", userInfo == null);
mUsersToRemove.add(userInfo.id);
return userInfo.id;
}
@@ -576,24 +589,40 @@ public class UserLifecycleTests {
*/
private void startUserInBackgroundAndWaitForUnlock(int userId) {
final ProgressWaiter waiter = new ProgressWaiter();
boolean success = false;
try {
mIam.startUserInBackgroundWithListener(userId, waiter);
boolean success = waiter.waitForFinish(TIMEOUT_IN_SECOND);
attestTrue("Failed to start user " + userId + " in background.", success);
success = waiter.waitForFinish(TIMEOUT_IN_SECOND);
} catch (RemoteException e) {
Log.e(TAG, "startUserInBackgroundAndWaitForUnlock failed", e);
}
attestTrue("Failed to start user " + userId + " in background.", success);
}
/** Starts the given user in the foreground. */
private void switchUser(int userId) throws Exception {
private void switchUser(int userId) throws RemoteException {
boolean success = switchUserNoCheck(userId);
attestTrue("Failed to properly switch to user " + userId, success);
}
/**
* Starts the given user in the foreground.
* Returns true if successful. Does not fail the test if unsuccessful.
* If lack of success should fail the test, use {@link #switchUser(int)} instead.
*/
private boolean switchUserNoCheck(int userId) throws RemoteException {
final CountDownLatch latch = new CountDownLatch(1);
registerUserSwitchObserver(latch, null, userId);
mAm.switchUser(userId);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
try {
return latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "Thread interrupted unexpectedly.", e);
return false;
}
}
private void stopUser(int userId, boolean force) throws Exception {
private void stopUser(int userId, boolean force) throws RemoteException {
final CountDownLatch latch = new CountDownLatch(1);
mIam.stopUser(userId, force /* force */, new IStopUserCallback.Stub() {
@Override
@@ -605,7 +634,7 @@ public class UserLifecycleTests {
public void userStopAborted(int userId) throws RemoteException {
}
});
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
waitForLatch("Failed to properly stop user " + userId, latch);
}
/**
@@ -615,15 +644,14 @@ public class UserLifecycleTests {
* @param stopNewUser whether to stop the new user after switching to otherUser.
* @return userId of the newly created user.
*/
private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws Exception {
private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws RemoteException {
final int origUser = mAm.getCurrentUser();
// First, create and switch to testUser, waiting for its ACTION_USER_UNLOCKED
final int testUser = createUserNoFlags();
final CountDownLatch latch1 = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch1, testUser);
mAm.switchUser(testUser);
attestTrue("Failed to achieve initial ACTION_USER_UNLOCKED for user " + testUser,
latch1.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS));
waitForLatch("Failed to achieve initial ACTION_USER_UNLOCKED for user " + testUser, latch1);
// Second, switch back to origUser, waiting merely for switchUser() to finish
switchUser(origUser);
@@ -656,11 +684,7 @@ public class UserLifecycleTests {
PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
PackageManager.INSTALL_REASON_UNKNOWN, sender, userId, null);
try {
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "Thread interrupted unexpectedly.", e);
}
waitForLatch("Failed to install app " + packageName + " on user " + userId, latch);
}
/**
@@ -678,7 +702,7 @@ public class UserLifecycleTests {
}
private void registerUserSwitchObserver(final CountDownLatch switchLatch,
final CountDownLatch bootCompleteLatch, final int userId) throws Exception {
final CountDownLatch bootCompleteLatch, final int userId) throws RemoteException {
ActivityManager.getService().registerUserSwitchObserver(
new UserSwitchObserver() {
@Override
@@ -734,6 +758,17 @@ public class UserLifecycleTests {
}
}
/** Waits TIMEOUT_IN_SECOND for the latch to complete, otherwise declares the given error. */
private void waitForLatch(String errMsg, CountDownLatch latch) {
boolean success = false;
try {
success = latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "Thread interrupted unexpectedly.", e);
}
attestTrue(errMsg, success);
}
/** Gets the PACKAGE_WHITELIST_MODE_PROP System Property. */
private int getUserTypePackageWhitelistMode() {
return SystemProperties.getInt(PACKAGE_WHITELIST_MODE_PROP,
@@ -767,13 +802,30 @@ public class UserLifecycleTests {
}
}
private void attestTrue(String message, boolean assertion) {
if (!assertion) {
Log.w(TAG, message);
private void removeAnyPreviousTestUsers() {
for (UserInfo user : mUm.getUsers()) {
if (TEST_USER_NAME.equals(user.name)) {
Log.i(TAG, "Found previous test user " + user.id + ". Removing it.");
if (mAm.getCurrentUser() == user.id) {
try {
switchUserNoCheck(UserHandle.USER_SYSTEM);
} catch (RemoteException e) {
Log.e(TAG, "Failed to correctly switch to system user", e);
}
}
mUm.removeUser(user.id);
}
}
}
private void attestFalse(String message, boolean assertion) {
private void attestTrue(@NonNull String message, boolean assertion) {
if (!assertion) {
Log.e(TAG, "Test failed on iteration #" + mRunner.getIteration() + ": " + message);
mRunner.markAsFailed(new AssertionError(message));
}
}
private void attestFalse(@NonNull String message, boolean assertion) {
attestTrue(message, !assertion);
}
}

View File

@@ -80,21 +80,21 @@ data_sources {
atrace_apps: "*"
atrace_categories: "am"
atrace_categories: "binder_driver"
atrace_categories: "bionic"
atrace_categories: "dalvik"
atrace_categories: "input"
atrace_categories: "pm"
atrace_categories: "res"
atrace_categories: "rro"
atrace_categories: "ss"
atrace_categories: "view"
atrace_categories: "camera"
atrace_categories: "wm"
atrace_categories: "freq"
atrace_categories: "dalvik"
atrace_categories: "sched"
atrace_categories: "freq"
atrace_categories: "gfx"
atrace_categories: "view"
atrace_categories: "webview"
atrace_categories: "input"
atrace_categories: "hal"
atrace_categories: "binder_driver"
atrace_categories: "sync"
atrace_categories: "workq"
atrace_categories: "res"
}
}

View File

@@ -19,6 +19,7 @@ android_test {
"androidx.test.ext.junit",
"androidx.annotation_annotation",
"apct-perftests-utils",
"collector-device-lib-platform",
],
libs: ["android.test.base"],
@@ -27,4 +28,8 @@ android_test {
test_suites: ["device-tests"],
data: [":perfetto_artifacts"],
certificate: "platform",
}

View File

@@ -76,7 +76,8 @@
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.PerfTestActivity">
<activity android:name="android.perftests.utils.PerfTestActivity"
android:exported="true">
<intent-filter>
<action android:name="com.android.perftests.packagemanager.PERFTEST" />
</intent-filter>

View File

@@ -15,74 +15,122 @@
~ limitations under the License.
-->
<configuration description="Runs PackageManagerPerfTests metric instrumentation.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-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="PackageManagerPerfTests.apk" />
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="PackageManagerPerfTests.apk"/>
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="force-queryable" value="false" />
<option name="test-file-name" value="QueriesAll0.apk" />
<option name="test-file-name" value="QueriesAll1.apk" />
<option name="test-file-name" value="QueriesAll2.apk" />
<option name="test-file-name" value="QueriesAll3.apk" />
<option name="test-file-name" value="QueriesAll4.apk" />
<option name="test-file-name" value="QueriesAll5.apk" />
<option name="test-file-name" value="QueriesAll6.apk" />
<option name="test-file-name" value="QueriesAll7.apk" />
<option name="test-file-name" value="QueriesAll8.apk" />
<option name="test-file-name" value="QueriesAll9.apk" />
<option name="test-file-name" value="QueriesAll10.apk" />
<option name="test-file-name" value="QueriesAll11.apk" />
<option name="test-file-name" value="QueriesAll12.apk" />
<option name="test-file-name" value="QueriesAll13.apk" />
<option name="test-file-name" value="QueriesAll14.apk" />
<option name="test-file-name" value="QueriesAll15.apk" />
<option name="test-file-name" value="QueriesAll16.apk" />
<option name="test-file-name" value="QueriesAll17.apk" />
<option name="test-file-name" value="QueriesAll18.apk" />
<option name="test-file-name" value="QueriesAll19.apk" />
<option name="test-file-name" value="QueriesAll20.apk" />
<option name="test-file-name" value="QueriesAll21.apk" />
<option name="test-file-name" value="QueriesAll22.apk" />
<option name="test-file-name" value="QueriesAll23.apk" />
<option name="test-file-name" value="QueriesAll24.apk" />
<option name="test-file-name" value="QueriesAll25.apk" />
<option name="test-file-name" value="QueriesAll26.apk" />
<option name="test-file-name" value="QueriesAll27.apk" />
<option name="test-file-name" value="QueriesAll28.apk" />
<option name="test-file-name" value="QueriesAll29.apk" />
<option name="test-file-name" value="QueriesAll30.apk" />
<option name="test-file-name" value="QueriesAll31.apk" />
<option name="test-file-name" value="QueriesAll32.apk" />
<option name="test-file-name" value="QueriesAll33.apk" />
<option name="test-file-name" value="QueriesAll34.apk" />
<option name="test-file-name" value="QueriesAll35.apk" />
<option name="test-file-name" value="QueriesAll36.apk" />
<option name="test-file-name" value="QueriesAll37.apk" />
<option name="test-file-name" value="QueriesAll38.apk" />
<option name="test-file-name" value="QueriesAll39.apk" />
<option name="test-file-name" value="QueriesAll40.apk" />
<option name="test-file-name" value="QueriesAll41.apk" />
<option name="test-file-name" value="QueriesAll42.apk" />
<option name="test-file-name" value="QueriesAll43.apk" />
<option name="test-file-name" value="QueriesAll44.apk" />
<option name="test-file-name" value="QueriesAll45.apk" />
<option name="test-file-name" value="QueriesAll46.apk" />
<option name="test-file-name" value="QueriesAll47.apk" />
<option name="test-file-name" value="QueriesAll48.apk" />
<option name="test-file-name" value="QueriesAll49.apk" />
<option name="cleanup-apks" value="true"/>
<option name="force-queryable" value="false"/>
<option name="test-file-name" value="QueriesAll0.apk"/>
<option name="test-file-name" value="QueriesAll1.apk"/>
<option name="test-file-name" value="QueriesAll2.apk"/>
<option name="test-file-name" value="QueriesAll3.apk"/>
<option name="test-file-name" value="QueriesAll4.apk"/>
<option name="test-file-name" value="QueriesAll5.apk"/>
<option name="test-file-name" value="QueriesAll6.apk"/>
<option name="test-file-name" value="QueriesAll7.apk"/>
<option name="test-file-name" value="QueriesAll8.apk"/>
<option name="test-file-name" value="QueriesAll9.apk"/>
<option name="test-file-name" value="QueriesAll10.apk"/>
<option name="test-file-name" value="QueriesAll11.apk"/>
<option name="test-file-name" value="QueriesAll12.apk"/>
<option name="test-file-name" value="QueriesAll13.apk"/>
<option name="test-file-name" value="QueriesAll14.apk"/>
<option name="test-file-name" value="QueriesAll15.apk"/>
<option name="test-file-name" value="QueriesAll16.apk"/>
<option name="test-file-name" value="QueriesAll17.apk"/>
<option name="test-file-name" value="QueriesAll18.apk"/>
<option name="test-file-name" value="QueriesAll19.apk"/>
<option name="test-file-name" value="QueriesAll20.apk"/>
<option name="test-file-name" value="QueriesAll21.apk"/>
<option name="test-file-name" value="QueriesAll22.apk"/>
<option name="test-file-name" value="QueriesAll23.apk"/>
<option name="test-file-name" value="QueriesAll24.apk"/>
<option name="test-file-name" value="QueriesAll25.apk"/>
<option name="test-file-name" value="QueriesAll26.apk"/>
<option name="test-file-name" value="QueriesAll27.apk"/>
<option name="test-file-name" value="QueriesAll28.apk"/>
<option name="test-file-name" value="QueriesAll29.apk"/>
<option name="test-file-name" value="QueriesAll30.apk"/>
<option name="test-file-name" value="QueriesAll31.apk"/>
<option name="test-file-name" value="QueriesAll32.apk"/>
<option name="test-file-name" value="QueriesAll33.apk"/>
<option name="test-file-name" value="QueriesAll34.apk"/>
<option name="test-file-name" value="QueriesAll35.apk"/>
<option name="test-file-name" value="QueriesAll36.apk"/>
<option name="test-file-name" value="QueriesAll37.apk"/>
<option name="test-file-name" value="QueriesAll38.apk"/>
<option name="test-file-name" value="QueriesAll39.apk"/>
<option name="test-file-name" value="QueriesAll40.apk"/>
<option name="test-file-name" value="QueriesAll41.apk"/>
<option name="test-file-name" value="QueriesAll42.apk"/>
<option name="test-file-name" value="QueriesAll43.apk"/>
<option name="test-file-name" value="QueriesAll44.apk"/>
<option name="test-file-name" value="QueriesAll45.apk"/>
<option name="test-file-name" value="QueriesAll46.apk"/>
<option name="test-file-name" value="QueriesAll47.apk"/>
<option name="test-file-name" value="QueriesAll48.apk"/>
<option name="test-file-name" value="QueriesAll49.apk"/>
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.perftests.packagemanager" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.perftests.packagemanager"/>
<option name="hidden-api-checks" value="false"/>
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/data/local/PackageManagerPerfTests" />
<option name="collect-on-run-ended-only" value="true" />
<option name="directory-keys" value="/data/local/PackageManagerPerfTests"/>
<option name="collect-on-run-ended-only" value="true"/>
</metrics_collector>
<!-- 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 pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="perfetto_file_path"/>
</metrics_collector>
<!-- 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.packagemanager"/>
<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"/>
<!-- 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>
</configuration>

View File

@@ -18,7 +18,8 @@
package="com.android.perftests.appenumeration">
<application android:hasCode="false" >
<activity android:name="android.perftests.utils.PerfTestActivity">
<activity android:name="android.perftests.utils.PerfTestActivity"
android:exported="true">
<intent-filter>
<action android:name="com.android.perftests.packagemanager.PERFTEST" />
</intent-filter>
@@ -78,4 +79,4 @@
<package android:name="com.android.perftests.appenumeration49" />
</queries>
</manifest>
</manifest>

View File

@@ -28,7 +28,9 @@ android_test {
"androidx.test.rules",
"androidx.annotation_annotation",
"apct-perftests-utils",
"collector-device-lib",
],
data: [":perfetto_artifacts"],
platform_apis: true,
test_suites: ["device-tests"],
}

View File

@@ -21,8 +21,40 @@
<option name="test-file-name" value="TextClassifierPerfTests.apk" />
</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" />
</target_preparer>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="perfetto_file_path" />
</metrics_collector>
<!-- 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.textclassifier" />
<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" />
<!-- 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" />
<option name="instrumentation-arg" key="newRunListenerMode" value="true" />
</test>
</configuration>

View File

@@ -18,6 +18,7 @@ package android.view.textclassifier;
import android.content.Context;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.service.textclassifier.TextClassifierService;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
@@ -25,48 +26,73 @@ import androidx.test.filters.LargeTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Random;
@RunWith(Parameterized.class)
@LargeTest
public class TextClassifierPerfTest {
/** Request contains meaning text, rather than garbled text. */
private static final int ACTUAL_REQUEST = 0;
private static final String RANDOM_CHAR_SET = "abcdefghijklmnopqrstuvwxyz0123456789";
private static final String TEXT = " Oh hi Mark, the number is (323) 654-6192.\n"
+ "Anyway, I'll meet you at 1600 Pennsylvania Avenue NW.\n"
+ "My flight is LX 38 and I'll arrive at 8:00pm.\n"
+ "Also, check out www.google.com.\n";
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Parameterized.Parameters(name = "size{0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{{ACTUAL_REQUEST}, {10}, {100}, {1000}});
}
private TextClassifier mTextClassifier;
private final int mSize;
public TextClassifierPerfTest(int size) {
mSize = size;
}
@Before
public void setUp() {
Context context = InstrumentationRegistry.getTargetContext();
TextClassificationManager textClassificationManager =
context.getSystemService(TextClassificationManager.class);
mTextClassifier = textClassificationManager.getTextClassifier(TextClassifier.LOCAL);
mTextClassifier = TextClassifierService.getDefaultTextClassifierImplementation(context);
}
@Test
public void testClassifyText() {
TextClassification.Request request =
new TextClassification.Request.Builder(TEXT, 0, TEXT.length()).build();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mTextClassifier.classifyText(request);
}
}
@Test
public void testSuggestSelection() {
// Trying to select the phone number.
TextSelection.Request request =
new TextSelection.Request.Builder(
TEXT,
/* startIndex= */ 28,
/* endIndex= */29)
.build();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mTextClassifier.suggestSelection(request);
}
}
@Test
public void testGenerateLinks() {
TextLinks.Request request =
new TextLinks.Request.Builder(TEXT).build();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mTextClassifier.generateLinks(request);
}
}
@Test
public void testSuggestConversationActions() {
String text = mSize == ACTUAL_REQUEST ? "Where are you?" : generateRandomString(mSize);
ConversationActions.Request request = createConversationActionsRequest(text);
ConversationActions.Message message =
new ConversationActions.Message.Builder(
ConversationActions.Message.PERSON_USER_OTHERS)
.setText(TEXT)
.build();
ConversationActions.Request request = new ConversationActions.Request.Builder(
Collections.singletonList(message))
.build();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mTextClassifier.suggestConversationActions(request);
@@ -75,36 +101,10 @@ public class TextClassifierPerfTest {
@Test
public void testDetectLanguage() {
String text = mSize == ACTUAL_REQUEST
? "これは日本語のテキストです" : generateRandomString(mSize);
TextLanguage.Request request = createTextLanguageRequest(text);
TextLanguage.Request request = new TextLanguage.Request.Builder(TEXT).build();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mTextClassifier.detectLanguage(request);
}
}
private static ConversationActions.Request createConversationActionsRequest(CharSequence text) {
ConversationActions.Message message =
new ConversationActions.Message.Builder(
ConversationActions.Message.PERSON_USER_OTHERS)
.setText(text)
.build();
return new ConversationActions.Request.Builder(Collections.singletonList(message))
.build();
}
private static TextLanguage.Request createTextLanguageRequest(CharSequence text) {
return new TextLanguage.Request.Builder(text).build();
}
private static String generateRandomString(int length) {
Random random = new Random();
StringBuilder stringBuilder = new StringBuilder(length);
for (int i = 0; i < length; i++) {
int index = random.nextInt(RANDOM_CHAR_SET.length());
stringBuilder.append(RANDOM_CHAR_SET.charAt(index));
}
return stringBuilder.toString();
}
}

View File

@@ -53,7 +53,8 @@ public final class BenchmarkState {
private static final int NOT_STARTED = 0; // The benchmark has not started yet.
private static final int WARMUP = 1; // The benchmark is warming up.
private static final int RUNNING = 2; // The benchmark is running.
private static final int FINISHED = 3; // The benchmark has stopped.
private static final int RUNNING_CUSTOMIZED = 3; // Running for customized measurement.
private static final int FINISHED = 4; // The benchmark has stopped.
private int mState = NOT_STARTED; // Current benchmark state.
@@ -76,6 +77,14 @@ public final class BenchmarkState {
private int mRepeatCount = 0;
/**
* Additional iteration that used to apply customized measurement. The result during these
* iterations won't be counted into {@link #mStats}.
*/
private int mMaxCustomizedIterations;
private int mCustomizedIterations;
private CustomizedIterationListener mCustomizedIterationListener;
// Statistics. These values will be filled when the benchmark has finished.
// The computation needs double precision, but long int is fine for final reporting.
private Stats mStats;
@@ -110,6 +119,15 @@ public final class BenchmarkState {
mPaused = false;
}
/**
* This is used to run the benchmark with more information by enabling some debug mechanism but
* we don't want to account the special runs (slower) in the stats report.
*/
public void setCustomizedIterations(int iterations, CustomizedIterationListener listener) {
mMaxCustomizedIterations = iterations;
mCustomizedIterationListener = listener;
}
private void beginWarmup() {
mStartTimeNs = System.nanoTime();
mIteration = 0;
@@ -141,6 +159,11 @@ public final class BenchmarkState {
Debug.stopMethodTracing();
}
mStats = new Stats(mResults);
if (mMaxCustomizedIterations > 0 && mCustomizedIterationListener != null) {
mState = RUNNING_CUSTOMIZED;
mCustomizedIterationListener.onStart(mCustomizedIterations);
return true;
}
mState = FINISHED;
return false;
}
@@ -180,6 +203,15 @@ public final class BenchmarkState {
"Resume the benchmark before finishing each step.");
}
return true;
case RUNNING_CUSTOMIZED:
mCustomizedIterationListener.onFinished(mCustomizedIterations);
mCustomizedIterations++;
if (mCustomizedIterations >= mMaxCustomizedIterations) {
mState = FINISHED;
return false;
}
mCustomizedIterationListener.onStart(mCustomizedIterations);
return true;
case FINISHED:
throw new IllegalStateException("The benchmark has finished.");
default:
@@ -240,4 +272,13 @@ public final class BenchmarkState {
status.putLong(key + "_standardDeviation", standardDeviation());
instrumentation.sendStatus(Activity.RESULT_OK, status);
}
/** The interface to receive the events of customized iteration. */
public interface CustomizedIterationListener {
/** The customized iteration starts. */
void onStart(int iteration);
/** The customized iteration finished. */
void onFinished(int iteration);
}
}

View File

@@ -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) {

View File

@@ -134,7 +134,7 @@ public class TestPackageInstaller {
Intent intent = new Intent(action);
PendingIntent pending = PendingIntent.getBroadcast(mContext, sessionId, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
return pending.getIntentSender();
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2021 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.perftests.utils;
import android.app.Instrumentation;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
public final class TestUtils {
/**
* Retrieves a value that needs to be obtained on the main thread.
*
* <p>A simple utility method that helps to return an object from the UI thread.</p>
*
* @param supplier callback to be called on the UI thread to return a value
* @param <T> Type of the value to be returned
* @return Value returned from {@code supplier}
*/
public static <T> T getOnMainSync(@NonNull Supplier<T> supplier) {
final AtomicReference<T> result = new AtomicReference<>();
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
instrumentation.runOnMainSync(() -> result.set(supplier.get()));
return result.get();
}
}

View File

@@ -0,0 +1,155 @@
/*
* 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.perftests.utils;
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.perftests.utils.WindowPerfTestBase.executeShellCommand;
import static android.perftests.utils.WindowPerfTestBase.runWithShellPermissionIdentity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.perftests.utils.WindowPerfTestBase.SettingsSession;
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 WindowPerfRunPreconditionBase extends RunListener {
protected final String mTag = getClass().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 = "0";
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_2BUTTON:
navOverlay = WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
break;
case WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON:
navOverlay = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
break;
case WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL:
default:
navOverlay = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
break;
}
executeShellCommand("cmd overlay enable-exclusive --category " + 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(mTag, "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, mTag);
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(mTag, "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();
}
}

View File

@@ -0,0 +1,193 @@
/*
* 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.perftests.utils;
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.os.SystemClock;
import androidx.test.rule.ActivityTestRule;
import org.junit.After;
import org.junit.BeforeClass;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/** The base class for window related performance tests. */
public class WindowPerfTestBase {
public static final long NANOS_PER_S = 1000L * 1000 * 1000;
public static final long TIME_1_S_IN_NS = 1 * NANOS_PER_S;
static boolean sIsProfilingMethod;
@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));
}
@After
public void tearDown() {
// Make sure that profiling is stopped if test fails.
if (sIsProfilingMethod) {
stopProfiling();
}
}
public static UiAutomation getUiAutomation() {
return getInstrumentation().getUiAutomation();
}
public static void startAsyncAtrace(String tags) {
getUiAutomation().executeShellCommand("atrace -b 32768 --async_start " + tags);
// Avoid atrace isn't ready immediately.
SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
}
public static InputStream stopAsyncAtraceWithStream() {
return new ParcelFileDescriptor.AutoCloseInputStream(
getUiAutomation().executeShellCommand("atrace --async_stop"));
}
/** Starts method tracing on system server. */
public static void startProfiling(File basePath, String outFileName) {
if (!basePath.exists()) {
executeShellCommand("mkdir -p " + basePath);
}
final String samplingArg = WindowPerfRunPreconditionBase.sSamplingIntervalUs > 0
? ("--sampling " + WindowPerfRunPreconditionBase.sSamplingIntervalUs)
: "";
executeShellCommand("am profile start " + samplingArg + " system "
+ new File(basePath, outFileName));
sIsProfilingMethod = true;
}
/** Stops method tracing of system server. */
public static void stopProfiling() {
executeShellCommand("am profile stop system");
sIsProfilingMethod = false;
}
public static boolean sIsProfilingMethod() {
return sIsProfilingMethod;
}
/** Returns how many iterations should run with method tracing. */
public static int getProfilingIterations() {
return WindowPerfRunPreconditionBase.sProfilingIterations;
}
/**
* Executes shell command with reading the output. It may also used to block until the current
* command is completed.
*/
public static ByteArrayOutputStream executeShellCommand(String command) {
final ParcelFileDescriptor pfd = getUiAutomation().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;
}
public static void runWithShellPermissionIdentity(Runnable runnable) {
getUiAutomation().adoptShellPermissionIdentity();
try {
runnable.run();
} finally {
getUiAutomation().dropShellPermissionIdentity();
}
}
public static class SettingsSession<T> implements AutoCloseable {
private final Consumer<T> mSetter;
private final T mOriginalValue;
private boolean mChanged;
public SettingsSession(T originalValue, Consumer<T> setter) {
mOriginalValue = originalValue;
mSetter = setter;
}
public 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 the {@link PerfTestActivity} with an associated customizable intent.
*/
public static class PerfTestActivityRuleBase extends ActivityTestRule<PerfTestActivity> {
protected final Intent mStartIntent =
new Intent(getInstrumentation().getTargetContext(), PerfTestActivity.class);
public PerfTestActivityRuleBase() {
this(false /* launchActivity */);
}
public PerfTestActivityRuleBase(boolean launchActivity) {
super(PerfTestActivity.class, false /* initialTouchMode */, launchActivity);
}
@Override
public Intent getActivityIntent() {
return mStartIntent;
}
public PerfTestActivity launchActivity() {
return launchActivity(mStartIntent);
}
}
}

View File

@@ -28,8 +28,11 @@ android_test {
"androidx.test.rules",
"androidx.annotation_annotation",
"apct-perftests-utils",
"collector-device-lib",
"platform-test-annotations",
],
test_suites: ["device-tests"],
data: [":perfetto_artifacts"],
platform_apis: true,
certificate: "platform",
}

View File

@@ -20,7 +20,8 @@
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.PerfTestActivity">
<activity android:name="android.perftests.utils.PerfTestActivity"
android:exported="true">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
@@ -28,5 +29,7 @@
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.perftests.wm"/>
android:targetPackage="com.android.perftests.wm">
<meta-data android:name="listener" android:value="android.wm.WmPerfRunListener" />
</instrumentation>
</manifest>

View File

@@ -21,13 +21,47 @@
<option name="test-file-name" value="WmPerfTests.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.wm" />
</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" />
</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.wm" />
<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" />
<!-- 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/WmPerfTests" />
<option name="collect-on-run-ended-only" value="true" />
<option name="directory-keys" value="/data/local/tmp/WmPerfTests" />
<!-- Needed for pulling the collected trace config on to the host -->
<option name="pull-pattern-keys" value="perfetto_file_path" />
</metrics_collector>
</configuration>

View File

@@ -0,0 +1,35 @@
## Window manager performance tests
### Precondition
To reduce the variance of the test, if `perf-setup.sh` (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 WmPerfTests:RelayoutPerfTest -- \
--module-arg WmPerfTests:instrumentation-arg:kill-bg:=true
```
Use `am instrument`
```
adb shell am instrument -w -r -e class android.wm.RelayoutPerfTest \
-e listener android.wm.WmPerfRunListener \
-e kill-bg true \
com.android.perftests.wm/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.

View File

@@ -18,7 +18,6 @@ package android.wm;
import static android.perftests.utils.ManualBenchmarkState.StatsReport;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
@@ -37,11 +36,11 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
/** Measure the performance of internal methods in window manager service by trace tag. */
@LargeTest
public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase
implements ManualBenchmarkState.CustomizedIterationListener {
private static final String TAG = InternalWindowOperationPerfTest.class.getSimpleName();
@Rule
@@ -54,7 +53,7 @@ public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
"applyPostLayoutPolicy",
"applySurfaceChanges",
"AppTransitionReady",
"closeSurfaceTransactiom",
"closeSurfaceTransaction",
"openSurfaceTransaction",
"performLayout",
"performSurfacePlacement",
@@ -68,6 +67,9 @@ public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
"finishActivity",
"startActivityInner");
private boolean mIsProfiling;
private boolean mIsTraceStarted;
@Test
@ManualBenchmarkTest(
targetTestDurationNs = 20 * TIME_1_S_IN_NS,
@@ -76,13 +78,13 @@ public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
| StatsReport.FLAG_MAX | StatsReport.FLAG_COEFFICIENT_VAR))
public void testLaunchAndFinishActivity() throws Throwable {
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
state.setCustomizedIterations(getProfilingIterations(), this);
long measuredTimeNs = 0;
boolean isTraceStarted = false;
while (state.keepRunning(measuredTimeNs)) {
if (!isTraceStarted && !state.isWarmingUp()) {
startAsyncAtrace();
isTraceStarted = true;
if (!mIsTraceStarted && !mIsProfiling && !state.isWarmingUp()) {
startAsyncAtrace("wm");
mIsTraceStarted = true;
}
final long startTime = SystemClock.elapsedRealtimeNanos();
mActivityRule.launchActivity();
@@ -91,9 +93,15 @@ public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
}
stopAsyncAtrace();
if (mIsTraceStarted) {
stopAsyncAtrace();
}
mTraceMarkParser.forAllSlices((key, slices) -> {
if (slices.size() < 2) {
Log.w(TAG, "No sufficient samples: " + key);
return;
}
for (TraceMarkSlice slice : slices) {
state.addExtraResult(key, (long) (slice.getDurationInSeconds() * NANOS_PER_S));
}
@@ -102,20 +110,35 @@ public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
Log.i(TAG, String.valueOf(mTraceMarkParser));
}
private void startAsyncAtrace() throws IOException {
sUiAutomation.executeShellCommand("atrace -b 32768 --async_start wm");
// Avoid atrace isn't ready immediately.
SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
}
private void stopAsyncAtrace() throws IOException {
final ParcelFileDescriptor pfd = sUiAutomation.executeShellCommand("atrace --async_stop");
final InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
private void stopAsyncAtrace() {
final InputStream inputStream = stopAsyncAtraceWithStream();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
mTraceMarkParser.visit(line);
}
} catch (IOException e) {
Log.w(TAG, "Failed to read the result of stopped atrace", e);
}
}
@Override
public void onStart(int iteration) {
if (mIsTraceStarted) {
// Do not capture trace when profiling because the result will be much slower.
stopAsyncAtrace();
mIsTraceStarted = false;
}
mIsProfiling = true;
startProfiling(InternalWindowOperationPerfTest.class.getSimpleName()
+ "_MethodTracing_" + iteration + ".trace");
}
@Override
public void onFinished(int iteration) {
stopProfiling();
if (iteration >= getProfilingIterations() - 1) {
mIsProfiling = false;
}
}
}

View File

@@ -24,8 +24,8 @@ import static org.hamcrest.core.AnyOf.anyOf;
import static org.hamcrest.core.Is.is;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityTaskManager;
import android.window.TaskSnapshot;
import android.app.IActivityTaskManager;
import android.content.ComponentName;
import android.content.Context;
@@ -62,7 +62,8 @@ import java.util.concurrent.TimeUnit;
@RunWith(Parameterized.class)
@LargeTest
public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase
implements ManualBenchmarkState.CustomizedIterationListener {
private static Intent sRecentsIntent;
@Rule
@@ -95,7 +96,7 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
@BeforeClass
public static void setUpClass() {
// Get the permission to invoke startRecentsActivity.
sUiAutomation.adoptShellPermissionIdentity();
getUiAutomation().adoptShellPermissionIdentity();
final Context context = getInstrumentation().getContext();
final PackageManager pm = context.getPackageManager();
@@ -128,7 +129,7 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
ActivityManager.resumeAppSwitches();
} catch (RemoteException ignored) {
}
sUiAutomation.dropShellPermissionIdentity();
getUiAutomation().dropShellPermissionIdentity();
}
@Before
@@ -162,6 +163,7 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
| StatsReport.FLAG_COEFFICIENT_VAR))
public void testRecentsAnimation() throws Throwable {
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
state.setCustomizedIterations(getProfilingIterations(), this);
final IActivityTaskManager atm = ActivityTaskManager.getService();
final ArrayList<Pair<String, Boolean>> finishCases = new ArrayList<>();
@@ -224,13 +226,27 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
mMeasuredTimeNs = 0;
final long startTime = SystemClock.elapsedRealtimeNanos();
atm.startRecentsActivity(sRecentsIntent, null /* unused */, anim);
atm.startRecentsActivity(sRecentsIntent, 0 /* eventTime */, anim);
final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime;
mMeasuredTimeNs += elapsedTimeNsOfStart;
state.addExtraResult("start", elapsedTimeNsOfStart);
// Ensure the animation callback is done.
Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
Assume.assumeTrue(recentsSemaphore.tryAcquire(
sIsProfilingMethod() ? 10 * TIME_5_S_IN_NS : TIME_5_S_IN_NS,
TimeUnit.NANOSECONDS));
}
}
@Override
public void onStart(int iteration) {
startProfiling(RecentsAnimationPerfTest.class.getSimpleName()
+ "_interval_" + intervalBetweenOperations
+ "_MethodTracing_" + iteration + ".trace");
}
@Override
public void onFinished(int iteration) {
stopProfiling();
}
}

View File

@@ -21,13 +21,12 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import android.app.Activity;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.PerfTestActivity;
import android.platform.test.annotations.Presubmit;
import android.util.MergedConfiguration;
import android.view.DisplayCutout;
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.InsetsSourceControl;
@@ -37,6 +36,7 @@ import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.widget.LinearLayout;
import android.window.ClientWindowFrames;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -52,7 +52,9 @@ import java.util.function.IntSupplier;
@RunWith(Parameterized.class)
@LargeTest
public class RelayoutPerfTest extends WindowManagerPerfTestBase {
@Presubmit
public class RelayoutPerfTest extends WindowManagerPerfTestBase
implements BenchmarkState.CustomizedIterationListener {
private int mIteration;
@Rule
@@ -91,9 +93,22 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase {
mActivityRule.runOnUiThread(() -> activity.setContentView(contentView));
getInstrumentation().waitForIdleSync();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
state.setCustomizedIterations(getProfilingIterations(), this);
final RelayoutRunner relayoutRunner = new RelayoutRunner(activity, contentView.getWindow(),
() -> visibilities[mIteration++ % visibilities.length]);
relayoutRunner.runBenchmark(mPerfStatusReporter.getBenchmarkState());
relayoutRunner.runBenchmark(state);
}
@Override
public void onStart(int iteration) {
startProfiling(RelayoutPerfTest.class.getSimpleName() + "_" + testName
+ "_MethodTracing_" + iteration + ".trace");
}
@Override
public void onFinished(int iteration) {
stopProfiling();
}
/** A dummy view to get IWindow. */
@@ -109,13 +124,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase {
}
private static class RelayoutRunner {
final Rect mOutFrame = new Rect();
final Rect mOutContentInsets = new Rect();
final Rect mOutVisibleInsets = new Rect();
final Rect mOutStableInsets = new Rect();
final Rect mOutBackDropFrame = new Rect();
final DisplayCutout.ParcelableWrapper mOutDisplayCutout =
new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT);
final ClientWindowFrames mOutFrames = new ClientWindowFrames();
final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
@@ -126,11 +135,9 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase {
final int mHeight;
final Point mOutSurfaceSize = new Point();
final SurfaceControl mOutSurfaceControl;
final SurfaceControl mOutBlastSurfaceControl = new SurfaceControl();
final IntSupplier mViewVisibility;
int mSeq;
int mFrameNumber;
int mFlags;
@@ -147,12 +154,10 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase {
void runBenchmark(BenchmarkState state) throws RemoteException {
final IWindowSession session = WindowManagerGlobal.getWindowSession();
while (state.keepRunning()) {
session.relayout(mWindow, mSeq, mParams, mWidth, mHeight,
mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame,
mOutContentInsets, mOutVisibleInsets, mOutStableInsets,
mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration,
mOutSurfaceControl, mOutInsetsState, mOutControls, mOutSurfaceSize,
mOutBlastSurfaceControl);
session.relayout(mWindow, mParams, mWidth, mHeight,
mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrames,
mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls,
mOutSurfaceSize);
}
}
}

View File

@@ -19,14 +19,12 @@ package android.wm;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
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.view.Display;
import android.view.DisplayCutout;
import android.view.IWindowSession;
import android.view.InputChannel;
import android.view.InsetsSourceControl;
@@ -48,23 +46,21 @@ import org.junit.Test;
public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
implements ManualBenchmarkState.CustomizedIterationListener {
private static final int PROFILED_ITERATIONS = 2;
@Rule
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@BeforeClass
public static void setUpClass() {
// Get the permission to use most window types.
sUiAutomation.adoptShellPermissionIdentity();
getUiAutomation().adoptShellPermissionIdentity();
}
@AfterClass
public static void tearDownClass() {
sUiAutomation.dropShellPermissionIdentity();
getUiAutomation().dropShellPermissionIdentity();
}
/** The last {@link #PROFILED_ITERATIONS} will provide the information of method profiling. */
/** The last customized iterations will provide the information of method profiling. */
@Override
public void onStart(int iteration) {
startProfiling(WindowAddRemovePerfTest.class.getSimpleName()
@@ -80,17 +76,13 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
@ManualBenchmarkTest(warmupDurationNs = TIME_1_S_IN_NS, targetTestDurationNs = TIME_5_S_IN_NS)
public void testAddRemoveWindow() throws Throwable {
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
state.setCustomizedIterations(PROFILED_ITERATIONS, this);
state.setCustomizedIterations(getProfilingIterations(), this);
new TestWindow().runBenchmark(state);
}
private static class TestWindow extends BaseIWindow {
final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
final Rect mOutFrame = new Rect();
final Rect mOutContentInsets = new Rect();
final Rect mOutStableInsets = new Rect();
final DisplayCutout.ParcelableWrapper mOutDisplayCutout =
new DisplayCutout.ParcelableWrapper();
final InsetsState mRequestedVisibility = new InsetsState();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
@@ -109,9 +101,9 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
final InputChannel inputChannel = new InputChannel();
long startTime = SystemClock.elapsedRealtimeNanos();
session.addToDisplay(this, mSeq, mLayoutParams, View.VISIBLE,
Display.DEFAULT_DISPLAY, mOutFrame, mOutContentInsets, mOutStableInsets,
mOutDisplayCutout, inputChannel, mOutInsetsState, mOutControls);
session.addToDisplay(this, mLayoutParams, View.VISIBLE,
Display.DEFAULT_DISPLAY, mRequestedVisibility, inputChannel,
mOutInsetsState, mOutControls);
final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
state.addExtraResult("add", elapsedTimeNsOfAdd);

View File

@@ -19,125 +19,45 @@ package android.wm;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import android.app.Activity;
import android.app.KeyguardManager;
import android.app.UiAutomation;
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.perftests.utils.PerfTestActivity;
import android.provider.Settings;
import android.perftests.utils.WindowPerfTestBase;
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.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class WindowManagerPerfTestBase {
static final UiAutomation sUiAutomation = getInstrumentation().getUiAutomation();
static final long NANOS_PER_S = 1000L * 1000 * 1000;
static final long TIME_1_S_IN_NS = 1 * NANOS_PER_S;
public class WindowManagerPerfTestBase extends WindowPerfTestBase {
static final long TIME_5_S_IN_NS = 5 * NANOS_PER_S;
/**
* The out directory matching the directory-keys of collector in AndroidTest.xml. The directory
* is in /data because while enabling method profling of system server, it cannot write the
* is in /data because while enabling method profiling of system server, it cannot write the
* trace to external storage.
*/
static final File BASE_OUT_PATH = new File("/data/local/CorePerfTests");
static final File BASE_OUT_PATH = new File("/data/local/tmp/WmPerfTests");
private static int sOriginalStayOnWhilePluggedIn;
@BeforeClass
public static void setUpOnce() {
final Context context = getInstrumentation().getContext();
final int stayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
sOriginalStayOnWhilePluggedIn = -1;
if (stayOnWhilePluggedIn != BatteryManager.BATTERY_PLUGGED_ANY) {
sOriginalStayOnWhilePluggedIn = stayOnWhilePluggedIn;
// Keep the device awake during testing.
setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_ANY);
}
if (!BASE_OUT_PATH.exists()) {
executeShellCommand("mkdir -p " + BASE_OUT_PATH);
}
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));
}
@AfterClass
public static void tearDownOnce() {
if (sOriginalStayOnWhilePluggedIn != -1) {
setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn);
}
}
private static void setStayOnWhilePluggedIn(int value) {
executeShellCommand(String.format("settings put global %s %d",
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value));
static void startProfiling(String outFileName) {
startProfiling(BASE_OUT_PATH, outFileName);
}
/**
* Executes shell command with reading the output. It may also used to block until the current
* command is completed.
* Provides an activity that is able to wait for a stable lifecycle stage.
*/
static ByteArrayOutputStream executeShellCommand(String command) {
final ParcelFileDescriptor pfd = sUiAutomation.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;
}
/** Starts method tracing on system server. */
void startProfiling(String subPath) {
executeShellCommand("am profile start system " + new File(BASE_OUT_PATH, subPath));
}
void stopProfiling() {
executeShellCommand("am profile stop system");
}
/**
* 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);
static class PerfTestActivityRule extends PerfTestActivityRuleBase {
private final LifecycleListener mLifecycleListener = new LifecycleListener();
PerfTestActivityRule() {
this(false /* launchActivity */);
}
PerfTestActivityRule(boolean launchActivity) {
super(PerfTestActivity.class, false /* initialTouchMode */, launchActivity);
super(launchActivity);
}
@Override
@@ -155,11 +75,6 @@ public class WindowManagerPerfTestBase {
return super.apply(wrappedStatement, description);
}
@Override
protected Intent getActivityIntent() {
return mStartIntent;
}
@Override
public PerfTestActivity launchActivity(Intent intent) {
final PerfTestActivity activity = super.launchActivity(intent);
@@ -167,10 +82,6 @@ public class WindowManagerPerfTestBase {
return activity;
}
PerfTestActivity launchActivity() {
return launchActivity(mStartIntent);
}
void waitForIdleSync(Stage state) {
mLifecycleListener.waitForIdleSync(state);
}

View File

@@ -0,0 +1,23 @@
/*
* 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.wm;
import android.perftests.utils.WindowPerfRunPreconditionBase;
/** Prepare the preconditions before running performance test. */
public class WmPerfRunListener extends WindowPerfRunPreconditionBase {
}

View File

@@ -0,0 +1,45 @@
/*
* 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 com.android.server.wm.test.filters;
import android.wm.RelayoutPerfTest;
import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;
/**
* A static filter to have the same signature as the one in frameworks/base/tests/utils/testutils/.
* This doesn't share the existing library because it doesn't support parameterized test.
*/
public class FrameworksTestsFilter extends Filter {
private boolean mShouldRun;
@Override
public boolean shouldRun(Description description) {
final Class<?> testClass = description.getTestClass();
// Parameterized test methods don't have the original information. So keep the last status
// that matches the target class.
mShouldRun = (mShouldRun && testClass == null) || testClass == RelayoutPerfTest.class;
return mShouldRun;
}
@Override
public String describe() {
return "Default filter";
}
}

Some files were not shown because too many files have changed in this diff Show More