Merge Android R

Bug: 168057903
Merged-In: Ice3e441cc9c0df8d0a6acc016bb74375e081bd67
Change-Id: I1d85742f594be2007c99841b290e502b6ede624e
This commit is contained in:
Xin Li
2020-09-09 20:21:10 -07:00
10395 changed files with 1016759 additions and 381623 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/.idea
.idea
*.iml
*.sw*
gen/

View File

@@ -0,0 +1,12 @@
drops {
android_build_drop {
build_id: "6508977"
target: "CtsShim"
source_file: "aosp_arm64/CtsShimPriv.apk"
}
dest_file: "packages/CtsShim/apk//arm/CtsShimPriv.apk"
version: ""
version_group: ""
git_project: "platform/frameworks/base"
git_branch: "rvc-dev"
}

View File

@@ -0,0 +1,12 @@
drops {
android_build_drop {
build_id: "6508977"
target: "CtsShim"
source_file: "aosp_arm64/CtsShim.apk"
}
dest_file: "packages/CtsShim/apk//arm/CtsShim.apk"
version: ""
version_group: ""
git_project: "platform/frameworks/base"
git_branch: "rvc-dev"
}

View File

@@ -0,0 +1,12 @@
drops {
android_build_drop {
build_id: "6508977"
target: "CtsShim"
source_file: "aosp_x86_64/CtsShimPriv.apk"
}
dest_file: "packages/CtsShim/apk//x86/CtsShimPriv.apk"
version: ""
version_group: ""
git_project: "platform/frameworks/base"
git_branch: "rvc-dev"
}

View File

@@ -0,0 +1,12 @@
drops {
android_build_drop {
build_id: "6508977"
target: "CtsShim"
source_file: "aosp_x86_64/CtsShim.apk"
}
dest_file: "packages/CtsShim/apk//x86/CtsShim.apk"
version: ""
version_group: ""
git_project: "platform/frameworks/base"
git_branch: "rvc-dev"
}

View File

@@ -265,12 +265,20 @@ filegroup {
path: "telephony/java",
}
genrule {
name: "statslog-telephony-common-java-gen",
tools: ["stats-log-api-gen"],
cmd: "$(location stats-log-api-gen) --java $(out) --module telephony_common"
+ " --javaPackage com.android.internal.telephony --javaClass TelephonyCommonStatsLog",
out: ["com/android/internal/telephony/TelephonyCommonStatsLog.java"],
}
filegroup {
name: "framework-telephony-common-sources",
srcs: [
"telephony/common/**/*.java",
":statslog-telephony-common-java-gen",
],
path: "telephony/common",
}
filegroup {
@@ -282,38 +290,23 @@ filegroup {
path: "mms/java",
}
filegroup {
name: "framework-wifi-sources",
srcs: [
"wifi/java/**/*.java",
"wifi/java/**/*.aidl",
],
path: "wifi/java",
}
filegroup {
name: "framework-wifi-updatable-exported-aidl-sources",
srcs: [
"wifi/aidl-export/**/*.aidl",
],
path: "wifi/aidl-export",
}
filegroup {
name: "framework-non-updatable-sources",
srcs: [
// Java/AIDL sources under frameworks/base
":framework-blobstore-sources",
":framework-core-sources",
":framework-drm-sources",
":framework-graphics-sources",
":framework-jobscheduler-sources", // jobscheduler is not a module for R
":framework-keystore-sources",
":framework-identity-sources",
":framework-location-sources",
":framework-lowpan-sources",
":framework-media-sources",
":framework-mca-effect-sources",
":framework-mca-filterfw-sources",
":framework-mca-filterpacks-sources",
":framework-media-sources",
":framework-mms-sources",
":framework-opengl-sources",
":framework-rs-sources",
@@ -321,7 +314,8 @@ filegroup {
":framework-telecomm-sources",
":framework-telephony-common-sources",
":framework-telephony-sources",
":framework-wifi-sources",
":framework-wifi-annotations",
":framework-wifi-non-updatable-sources",
":PacProcessor-aidl-sources",
":ProxyHandler-aidl-sources",
":net-utils-framework-common-srcs",
@@ -344,6 +338,7 @@ filegroup {
":libcamera_client_aidl",
":libcamera_client_framework_aidl",
":libupdate_engine_aidl",
":resourcemanager_aidl",
":storaged_aidl",
":vold_aidl",
@@ -352,7 +347,6 @@ filegroup {
// etc.
":framework-javastream-protos",
":framework-statslog-gen", // StatsLogInternal.java
":statslog-framework-java-gen", // FrameworkStatsLog.java
],
}
@@ -360,23 +354,52 @@ filegroup {
filegroup {
name: "framework-updatable-sources",
srcs: [
":framework-mediaprovider-sources",
":framework-permission-sources",
":framework-sdkextensions-sources",
":framework-statsd-sources",
":framework-tethering-srcs",
":framework-wifi-updatable-exported-aidl-sources",
":framework-wifi-updatable-sources",
":updatable-media-srcs",
":ike-srcs",
]
],
visibility: ["//visibility:private"],
}
java_library {
name: "framework-updatable-stubs-module_libs_api",
static_libs: [
"framework-media.stubs.module_lib",
"framework-mediaprovider.stubs.module_lib",
"framework-permission.stubs.module_lib",
"framework-sdkextensions.stubs.module_lib",
"framework-statsd.stubs.module_lib",
"framework-tethering.stubs.module_lib",
"updatable_media_stubs",
"framework-wifi.stubs.module_lib",
],
sdk_version: "module_current",
visibility: [":__pkg__"],
visibility: ["//visibility:private"],
}
java_library {
name: "framework-all",
installable: false,
static_libs: [
"framework-minus-apex",
"framework-mediaprovider.impl",
"framework-permission.impl",
"framework-sdkextensions.impl",
"framework-statsd.impl",
"framework-tethering.impl",
"framework-wifi.impl",
"updatable-media",
],
apex_available: ["//apex_available:platform"],
sdk_version: "core_platform",
visibility: [
// DO NOT ADD ANY MORE ENTRIES TO THIS LIST
"//external/robolectric-shadows:__subpackages__",
"//frameworks/layoutlib:__subpackages__",
],
}
filegroup {
@@ -388,6 +411,8 @@ filegroup {
],
}
// AIDL files under these paths are mixture of public and private ones.
// They shouldn't be exported across module boundaries.
java_defaults {
name: "framework-aidl-export-defaults",
aidl: {
@@ -400,7 +425,6 @@ java_defaults {
"location/java",
"lowpan/java",
"media/java",
"media/apex/java",
"media/mca/effect/java",
"media/mca/filterfw/java",
"media/mca/filterpacks/java",
@@ -409,9 +433,8 @@ java_defaults {
"rs/java",
"sax/java",
"telecomm/java",
"wifi/java",
"wifi/aidl-export",
"apex/media/aidl/stable",
// TODO(b/147699819): remove this
"telephony/java",
],
@@ -428,9 +451,13 @@ java_library {
"framework-protos",
"game-driver-protos",
"android.hidl.base-V1.0-java",
"android.hardware.cas-V1.1-java",
"android.hardware.cas-V1.0-java",
"android.hardware.cas-V1.1-java",
"android.hardware.cas-V1.2-java",
"android.hardware.contexthub-V1.0-java",
"android.hardware.contexthub-V1.1-java",
"android.hardware.gnss-V1.0-java",
"android.hardware.gnss-V2.1-java",
"android.hardware.health-V1.0-java-constants",
"android.hardware.radio-V1.0-java",
"android.hardware.radio-V1.1-java",
@@ -443,6 +470,7 @@ java_library {
"android.hardware.thermal-V1.1-java",
"android.hardware.thermal-V2.0-java",
"android.hardware.tv.input-V1.0-java-constants",
"android.hardware.tv.tuner-V1.0-java-constants",
"android.hardware.usb-V1.0-java-constants",
"android.hardware.usb-V1.1-java-constants",
"android.hardware.usb-V1.2-java-constants",
@@ -451,7 +479,6 @@ java_library {
"android.hardware.vibrator-V1.1-java",
"android.hardware.vibrator-V1.2-java",
"android.hardware.vibrator-V1.3-java",
"android.hardware.wifi-V1.0-java-constants",
"devicepolicyprotosnano",
"com.android.sysprop.apex",
@@ -463,72 +490,21 @@ java_library {
installable: false,
}
java_defaults {
name: "framework-defaults",
defaults: ["framework-aidl-export-defaults"],
installable: true,
aidl: {
generate_get_transaction_name: true,
},
srcs: ["core/java/**/*.logtags"],
exclude_srcs: [
// See comment on framework-atb-backward-compatibility module below
"core/java/android/content/pm/AndroidTestBaseUpdater.java",
],
sdk_version: "core_platform",
libs: [
"app-compat-annotations",
"ext",
"unsupportedappusage",
],
jarjar_rules: ":framework-jarjar-rules",
static_libs: [
"framework-internal-utils",
],
required: [
// TODO: remove gps_debug when the build system propagates "required" properly.
"gps_debug.conf",
],
dxflags: [
"--core-library",
"--multi-dex",
],
plugins: ["view-inspector-annotation-processor"],
}
filegroup {
name: "framework-jarjar-rules",
srcs: ["jarjar_rules_hidl.txt"],
srcs: ["framework-jarjar-rules.txt"],
}
filegroup {
name: "libincident_aidl",
srcs: [
"core/java/android/os/IIncidentDumpCallback.aidl",
"core/java/android/os/IIncidentManager.aidl",
"core/java/android/os/IIncidentReportStatusListener.aidl",
],
path: "core/java",
}
filegroup {
name: "statsd_aidl",
srcs: [
"core/java/android/os/IStatsCompanionService.aidl",
"core/java/android/os/IStatsManager.aidl",
"core/java/android/os/IStatsPullerCallback.aidl",
],
path: "core/java",
}
filegroup {
name: "libvibrator_aidl",
srcs: [
@@ -538,19 +514,59 @@ filegroup {
path: "core/java",
}
filegroup {
name: "libpowermanager_aidl",
srcs: [
"core/java/android/os/Temperature.aidl",
"core/java/android/os/CoolingDevice.aidl",
"core/java/android/os/IThermalEventListener.aidl",
"core/java/android/os/IThermalStatusListener.aidl",
"core/java/android/os/IThermalService.aidl",
],
path: "core/java",
}
java_library {
name: "framework-minus-apex",
defaults: ["framework-defaults"],
srcs: [":framework-non-updatable-sources"],
defaults: ["framework-aidl-export-defaults"],
srcs: [
":framework-non-updatable-sources",
"core/java/**/*.logtags",
],
// See comment on framework-atb-backward-compatibility module below
exclude_srcs: ["core/java/android/content/pm/AndroidTestBaseUpdater.java"],
aidl: {
generate_get_transaction_name: true,
},
dxflags: [
"--core-library",
"--multi-dex",
],
installable: true,
jarjar_rules: ":framework-jarjar-rules",
javac_shard_size: 150,
plugins: [
"view-inspector-annotation-processor",
"staledataclass-annotation-processor",
],
required: [
"framework-platform-compat-config",
// TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly.
"gps_debug.conf",
"libcore-platform-compat-config",
"protolog.conf.json.gz",
"services-platform-compat-config",
"documents-ui-compat-config",
],
libs: ["framework-updatable-stubs-module_libs_api"],
libs: [
"app-compat-annotations",
"ext",
"framework-updatable-stubs-module_libs_api",
"unsupportedappusage",
],
sdk_version: "core_platform",
static_libs: [
"framework-internal-utils",
// 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",
@@ -559,7 +575,10 @@ java_library {
stem: "framework",
apex_available: ["//apex_available:platform"],
visibility: [
"//frameworks/base",
// TODO(b/147128803) remove the below lines
"//frameworks/base/apex/blobstore/framework",
"//frameworks/base/apex/jobscheduler/framework",
"//frameworks/base/packages/Tethering/tests/unit",
],
}
@@ -578,6 +597,7 @@ java_library {
defaults: ["framework-aidl-export-defaults"],
installable: false, // this lib is a build-only library
static_libs: [
"app-compat-annotations",
"framework-minus-apex",
"framework-updatable-stubs-module_libs_api",
],
@@ -585,27 +605,9 @@ java_library {
apex_available: ["//apex_available:platform"],
}
java_library {
name: "framework-all",
defaults: ["framework-defaults"],
srcs: [":framework-all-sources"],
installable: false,
static_libs: [
// Additional dependencies needed to build the ike API classes.
"ike-internals",
],
apex_available: ["//apex_available:platform"],
visibility: [
// DO NOT ADD ANY MORE ENTRIES TO THIS LIST
"//external/robolectric-shadows:__subpackages__",
"//frameworks/base",
"//frameworks/layoutlib:__subpackages__",
],
}
platform_compat_config {
name: "framework-platform-compat-config",
src: ":framework-minus-apex",
name: "framework-platform-compat-config",
src: ":framework-minus-apex",
}
// A temporary build target that is conditionally included on the bootclasspath if
@@ -622,13 +624,6 @@ java_library {
],
}
genrule {
name: "framework-statslog-gen",
tools: ["stats-log-api-gen"],
cmd: "$(location stats-log-api-gen) --java $(out) --worksource",
out: ["android/util/StatsLogInternal.java"],
}
genrule {
name: "statslog-framework-java-gen",
tools: ["stats-log-api-gen"],
@@ -637,6 +632,18 @@ genrule {
out: ["com/android/internal/util/FrameworkStatsLog.java"],
}
java_library {
name: "uieventloggerlib",
srcs: [
"core/java/com/android/internal/logging/UiEvent.java",
"core/java/com/android/internal/logging/UiEventLogger.java",
"core/java/com/android/internal/logging/UiEventLoggerImpl.java",
"core/java/com/android/internal/logging/InstanceId.java",
"core/java/com/android/internal/logging/InstanceIdSequence.java",
":statslog-framework-java-gen",
],
}
gensrcs {
name: "framework-javastream-protos",
depfile: true,
@@ -658,25 +665,62 @@ gensrcs {
"&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
srcs: [
":ipconnectivity-proto-src",
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
],
output_extension: "srcjar",
}
gensrcs {
name: "framework-cppstream-protos",
depfile: true,
tools: [
"aprotoc",
"protoc-gen-cppstream",
],
cmd: "mkdir -p $(genDir) " +
"&& $(location aprotoc) " +
" --plugin=$(location protoc-gen-cppstream) " +
" --dependency_out=$(depfile) " +
" --cppstream_out=$(genDir) " +
" -Iexternal/protobuf/src " +
" -I . " +
" $(in)",
srcs: [
":ipconnectivity-proto-src",
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
],
output_extension: "proto.h",
}
filegroup {
name: "framework-annotations",
srcs: [
"core/java/android/annotation/CallbackExecutor.java",
"core/java/android/annotation/CheckResult.java",
"core/java/android/annotation/CurrentTimeMillisLong.java",
"core/java/android/annotation/Hide.java",
"core/java/android/annotation/NonNull.java",
"core/java/android/annotation/Nullable.java",
"core/java/android/annotation/IntDef.java",
"core/java/android/annotation/IntRange.java",
"core/java/android/annotation/LongDef.java",
"core/java/android/annotation/NonNull.java",
"core/java/android/annotation/Nullable.java",
"core/java/android/annotation/RequiresPermission.java",
"core/java/android/annotation/SdkConstant.java",
"core/java/android/annotation/StringDef.java",
"core/java/android/annotation/SystemApi.java",
"core/java/android/annotation/SystemService.java",
"core/java/android/annotation/TestApi.java",
"core/java/android/annotation/WorkerThread.java",
"core/java/com/android/internal/annotations/GuardedBy.java",
"core/java/com/android/internal/annotations/VisibleForTesting.java",
"core/java/com/android/internal/annotations/Immutable.java",
],
}
@@ -721,24 +765,6 @@ filegroup {
],
}
// utility classes statically linked into framework-wifi and dynamically linked
// into wifi-service
java_library {
name: "framework-wifi-util-lib",
sdk_version: "module_current",
srcs: [
"core/java/com/android/internal/util/Preconditions.java",
],
libs: [
"framework-annotations-lib",
"unsupportedappusage",
],
visibility: [
"//frameworks/base/wifi",
"//frameworks/base/services/net",
],
}
filegroup {
name: "framework-services-net-module-wifi-shared-srcs",
srcs: [
@@ -772,10 +798,6 @@ java_library {
"libphonenumber-platform",
"tagsoup",
"rappor",
"libtextclassifier-java",
],
required: [
"libtextclassifier",
],
dxflags: ["--core-library"],
}
@@ -784,6 +806,7 @@ java_library {
java_library_host {
name: "platformprotos",
srcs: [
":ipconnectivity-proto-src",
"cmds/am/proto/instrumentation_data.proto",
"cmds/statsd/src/**/*.proto",
"core/proto/**/*.proto",
@@ -812,6 +835,7 @@ java_library {
],
sdk_version: "9",
srcs: [
":ipconnectivity-proto-src",
"core/proto/**/*.proto",
"libs/incident/proto/android/os/**/*.proto",
],
@@ -826,6 +850,7 @@ java_library {
},
srcs: [
":ipconnectivity-proto-src",
"core/proto/**/*.proto",
"libs/incident/proto/android/os/**/*.proto",
],
@@ -856,6 +881,7 @@ cc_defaults {
],
srcs: [
":ipconnectivity-proto-src",
"core/proto/**/*.proto",
],
}
@@ -936,11 +962,39 @@ cc_library {
filegroup {
name: "incremental_aidl",
srcs: [
"core/java/android/os/incremental/IIncrementalServiceConnector.aidl",
"core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
],
path: "core/java",
}
filegroup {
name: "dataloader_aidl",
srcs: [
"core/java/android/content/pm/DataLoaderParamsParcel.aidl",
"core/java/android/content/pm/DataLoaderType.aidl",
"core/java/android/content/pm/FileSystemControlParcel.aidl",
"core/java/android/content/pm/IDataLoader.aidl",
"core/java/android/content/pm/IDataLoaderManager.aidl",
"core/java/android/content/pm/InstallationFileParcel.aidl",
"core/java/android/content/pm/InstallationFileLocation.aidl",
"core/java/android/content/pm/IDataLoaderStatusListener.aidl",
"core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl",
],
path: "core/java",
}
filegroup {
name: "incremental_manager_aidl",
srcs: [
"core/java/android/os/incremental/IIncrementalService.aidl",
"core/java/android/os/incremental/IncrementalNewFileParams.aidl",
"core/java/android/os/incremental/IStorageHealthListener.aidl",
"core/java/android/os/incremental/StorageHealthCheckParams.aidl",
],
path: "core/java",
}
aidl_interface {
name: "libincremental_aidl",
unstable: true,
@@ -960,41 +1014,49 @@ aidl_interface {
},
}
gensrcs {
name: "gen-platform-proto-constants",
depfile: true,
tools: [
"aprotoc",
"protoc-gen-cppstream",
],
aidl_interface {
name: "libdataloader_aidl",
unstable: true,
srcs: [
"core/proto/android/os/backtrace.proto",
"core/proto/android/os/batterytype.proto",
"core/proto/android/os/cpufreq.proto",
"core/proto/android/os/cpuinfo.proto",
"core/proto/android/os/data.proto",
"core/proto/android/os/kernelwake.proto",
"core/proto/android/os/pagetypeinfo.proto",
"core/proto/android/os/procrank.proto",
"core/proto/android/os/ps.proto",
"core/proto/android/os/system_properties.proto",
"core/proto/android/util/event_log_tags.proto",
"core/proto/android/util/log.proto",
":dataloader_aidl",
],
imports: [
"libincremental_aidl",
],
backend: {
java: {
sdk_version: "28",
},
cpp: {
enabled: true,
},
ndk: {
enabled: false,
},
},
}
// Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool
cmd: "mkdir -p $(genDir) " +
"&& $(location aprotoc) " +
" --plugin=$(location protoc-gen-cppstream) " +
" --dependency_out=$(depfile) " +
" --cppstream_out=$(genDir) " +
" -Iexternal/protobuf/src " +
" -I . " +
" $(in)",
output_extension: "proto.h",
aidl_interface {
name: "libincremental_manager_aidl",
unstable: true,
srcs: [
":incremental_manager_aidl",
],
imports: [
"libincremental_aidl",
"libdataloader_aidl",
],
backend: {
java: {
sdk_version: "28",
},
cpp: {
enabled: true,
},
ndk: {
enabled: false,
},
},
}
// TODO(b/77285514): remove this once the last few hidl interfaces have been
@@ -1058,19 +1120,22 @@ python_binary_host {
filegroup {
name: "framework-media-annotation-srcs",
srcs: [
":framework-annotations",
"core/java/android/annotation/CallbackExecutor.java",
"core/java/android/annotation/CallSuper.java",
"core/java/android/annotation/DrawableRes.java",
"core/java/android/annotation/IntDef.java",
"core/java/android/annotation/LongDef.java",
"core/java/android/annotation/NonNull.java",
"core/java/android/annotation/Nullable.java",
"core/java/android/annotation/RequiresPermission.java",
"core/java/android/annotation/SdkConstant.java",
"core/java/android/annotation/StringDef.java",
"core/java/android/annotation/SystemApi.java",
"core/java/android/annotation/TestApi.java",
"core/java/com/android/internal/annotations/GuardedBy.java",
],
}
filegroup {
name: "framework-mediaprovider-annotation-sources",
srcs: [
":framework-annotations",
"core/java/android/annotation/BytesLong.java",
"core/java/android/annotation/CurrentTimeSecondsLong.java",
"core/java/android/annotation/DurationMillisLong.java",
],
}
@@ -1082,24 +1147,13 @@ aidl_mapping {
output: "framework-aidl-mappings.txt",
}
filegroup {
name: "framework-cellbroadcast-shared-srcs",
srcs: [
"core/java/android/os/HandlerExecutor.java",
"core/java/android/util/LocalLog.java",
"core/java/android/util/Slog.java",
"core/java/com/android/internal/util/IState.java",
"core/java/com/android/internal/util/Preconditions.java",
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
],
}
// Avoid including Parcelable classes as we don't want to have two copies of
// Parcelable cross the process.
// Parcelable cross the libraries. This is used by telephony-common (frameworks/opt/telephony)
// and TeleService app (packages/services/Telephony).
filegroup {
name: "framework-telephony-stack-shared-srcs",
name: "framework-telephony-common-shared-srcs",
srcs: [
"core/java/android/os/BasicShellCommandHandler.java",
"core/java/android/os/RegistrantList.java",
"core/java/android/os/Registrant.java",
"core/java/android/util/LocalLog.java",
@@ -1119,6 +1173,97 @@ filegroup {
],
}
// Avoid including Parcelable classes as we don't want to have two copies of
// Parcelable cross the process. This is used by framework-telephony (frameworks/base/telephony).
filegroup {
name: "framework-telephony-shared-srcs",
srcs: [
"core/java/android/util/RecurrenceRule.java",
"core/java/com/android/internal/os/SomeArgs.java",
"core/java/com/android/internal/util/BitwiseInputStream.java",
"core/java/com/android/internal/util/BitwiseOutputStream.java",
"core/java/com/android/internal/util/FunctionalUtils.java",
"core/java/com/android/internal/util/HexDump.java",
"core/java/com/android/internal/util/IndentingPrintWriter.java",
"core/java/com/android/internal/util/Preconditions.java",
],
}
// Avoid including Parcelable classes as we don't want to have two copies of
// Parcelable cross the process.
filegroup {
name: "framework-cellbroadcast-shared-srcs",
srcs: [
"core/java/android/os/HandlerExecutor.java",
"core/java/android/util/LocalLog.java",
"core/java/com/android/internal/util/IState.java",
"core/java/com/android/internal/util/Preconditions.java",
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
],
}
filegroup {
name: "framework-ims-common-shared-srcs",
srcs: [
"core/java/android/os/RegistrantList.java",
"core/java/android/os/Registrant.java",
"core/java/com/android/internal/os/SomeArgs.java",
"core/java/com/android/internal/util/Preconditions.java",
],
}
// utility classes statically linked into framework-wifi and dynamically linked
// into wifi-service
java_library {
name: "framework-wifi-util-lib",
sdk_version: "module_current",
srcs: [
"core/java/android/content/pm/BaseParceledListSlice.java",
"core/java/android/content/pm/ParceledListSlice.java",
"core/java/android/os/HandlerExecutor.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",
"core/java/com/android/internal/util/Preconditions.java",
"telephony/java/android/telephony/Annotation.java",
":net-utils-framework-wifi-common-srcs",
],
libs: [
"framework-annotations-lib",
"unsupportedappusage",
],
visibility: [
"//frameworks/base/wifi",
"//frameworks/base/services/net",
],
}
filegroup {
name: "framework-wifi-util-lib-aidls",
srcs: ["core/java/android/content/pm/ParceledListSlice.aidl"],
path: "core/java",
}
// utility classes statically linked into wifi-service
filegroup {
name: "framework-wifi-service-shared-srcs",
srcs: [
"core/java/android/net/InterfaceConfiguration.java",
"core/java/android/os/BasicShellCommandHandler.java",
"core/java/android/util/BackupUtils.java",
"core/java/android/util/Rational.java",
"core/java/com/android/internal/util/FastXmlSerializer.java",
"core/java/com/android/internal/util/HexDump.java",
"core/java/com/android/internal/util/IState.java",
"core/java/com/android/internal/util/MessageUtils.java",
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
"core/java/com/android/internal/util/WakeupMessage.java",
],
visibility: ["//frameworks/opt/net/wifi/service"],
}
// TODO(b/145644363): move this to under StubLibraries.bp or ApiDocs.bp
metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " +
"--ignore-classes-on-classpath " +
@@ -1140,3 +1285,43 @@ build = [
"StubLibraries.bp",
"ApiDocs.bp",
]
java_library {
name: "framework-telephony",
srcs: [
//":framework-telephony-sources",
//":framework-telephony-shared-srcs",
],
// TODO: change to framework-system-stub to build against system APIs.
libs: [
"framework-minus-apex",
"unsupportedappusage",
],
static_libs: [
"libphonenumber-platform",
"app-compat-annotations",
],
sdk_version: "core_platform",
aidl: {
export_include_dirs: ["telephony/java"],
include_dirs: [
"frameworks/native/aidl/binder",
"frameworks/native/aidl/gui",
]
},
jarjar_rules: ":framework-telephony-jarjar-rules",
dxflags: [
"--core-library",
"--multi-dex",
],
// This is to break the dependency from boot jars.
dex_preopt: {
enabled: false,
},
installable: true,
}
filegroup {
name: "framework-telephony-jarjar-rules",
srcs: ["telephony/framework-telephony-jarjar-rules.txt"],
}

View File

@@ -255,7 +255,13 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/DefaultContainerSer
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/CaptivePortalLogin)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/ext.jar)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/google/android/mms)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/*-service.jar)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/service-statsd.jar)
$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_aidl-cpp-source/)
$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_manager_aidl-cpp-source/)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/OsuLogin)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system_other/system/app/OsuLogin)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************

View File

@@ -9,6 +9,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
core/jni/
libs/input/
services/core/jni/
services/incremental/
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}

View File

@@ -51,6 +51,10 @@ stubs_defaults {
":android_icu4j_public_api_files",
"**/package.html",
],
// TODO(b/147699819): remove below aidl includes.
aidl: {
local_include_dirs: ["telephony/java"],
},
libs: ["framework-internal-utils"],
installable: false,
annotations_enabled: true,
@@ -81,12 +85,9 @@ stubs_defaults {
// finalized stub library to resolve them. If a new class gets added,
// this may be need to be revisited to use a manually maintained stub
// library with empty classes in order to resolve those references.
libs: ["sdk_system_29_android"],
libs: ["sdk_system_30_android"],
aidl: {
local_include_dirs: [
"media/apex/java",
"wifi/aidl-export",
],
local_include_dirs: ["apex/media/aidl/stable"],
},
}
@@ -112,7 +113,7 @@ droidstubs {
},
last_released: {
api_file: ":android.api.public.latest",
removed_api_file: "api/removed.txt",
removed_api_file: ":removed.api.public.latest",
baseline_file: ":public-api-incompatibilities-with-last-released",
},
api_lint: {
@@ -139,6 +140,10 @@ droidstubs {
api_file: "non-updatable-api/current.txt",
removed_api_file: "non-updatable-api/removed.txt",
},
api_lint: {
enabled: true,
new_since: ":android-non-updatable.api.public.latest",
},
},
}
@@ -170,7 +175,7 @@ droidstubs {
},
last_released: {
api_file: ":android.api.system.latest",
removed_api_file: "api/system-removed.txt",
removed_api_file: ":removed.api.system.latest",
baseline_file: ":system-api-incompatibilities-with-last-released"
},
api_lint: {
@@ -197,6 +202,11 @@ droidstubs {
api_file: "non-updatable-api/system-current.txt",
removed_api_file: "non-updatable-api/system-removed.txt",
},
api_lint: {
enabled: true,
new_since: ":android-non-updatable.api.system.latest",
baseline_file: "non-updatable-api/system-lint-baseline.txt",
},
},
}
@@ -245,7 +255,7 @@ droidstubs {
},
last_released: {
api_file: ":android.api.module-lib.latest",
removed_api_file: "api/module-lib-removed.txt",
removed_api_file: ":removed.api.module-lib.latest",
baseline_file: ":module-lib-api-incompatibilities-with-last-released"
},
api_lint: {
@@ -271,6 +281,10 @@ droidstubs {
api_file: "non-updatable-api/module-lib-current.txt",
removed_api_file: "non-updatable-api/module-lib-removed.txt",
},
api_lint: {
enabled: true,
new_since: ":android-non-updatable.api.module-lib.latest",
},
},
}
@@ -283,8 +297,6 @@ java_defaults {
name: "android_defaults_stubs_current",
libs: [ "stub-annotations" ],
static_libs: [
"private-stub-annotations-jar",
// License notices from art module
"art-notices-for-framework-stubs-jar",
"framework-res-package-jar", // Export package of framework-res
@@ -312,6 +324,7 @@ java_defaults {
java_library_static {
name: "android_monolith_stubs_current",
srcs: [ ":api-stubs-docs" ],
static_libs: [ "private-stub-annotations-jar" ],
defaults: ["android_defaults_stubs_current"],
}
@@ -320,9 +333,14 @@ java_library_static {
srcs: [ ":api-stubs-docs-non-updatable" ],
static_libs: [
"conscrypt.module.public.api.stubs",
"framework-media.stubs",
"framework-mediaprovider.stubs",
"framework-permission.stubs",
"framework-sdkextensions.stubs",
"framework-statsd.stubs",
"framework-tethering.stubs",
"updatable_media_stubs.public",
"framework-wifi.stubs",
"private-stub-annotations-jar",
],
defaults: ["android_defaults_stubs_current"],
}
@@ -336,6 +354,7 @@ java_library_static {
java_library_static {
name: "android_system_monolith_stubs_current",
srcs: [ ":system-api-stubs-docs" ],
static_libs: [ "private-stub-annotations-jar" ],
defaults: [
"android_defaults_stubs_current",
"android_stubs_dists_default",
@@ -358,9 +377,14 @@ java_library_static {
srcs: [ ":system-api-stubs-docs-non-updatable" ],
static_libs: [
"conscrypt.module.public.api.stubs",
"framework-media.stubs.system",
"framework-mediaprovider.stubs.system",
"framework-permission.stubs.system",
"framework-sdkextensions.stubs.system",
"framework-statsd.stubs.system",
"framework-tethering.stubs.system",
"updatable_media_stubs",
"framework-wifi.stubs.system",
"private-stub-annotations-jar",
],
defaults: ["android_defaults_stubs_current"],
}
@@ -374,6 +398,7 @@ java_library_static {
java_library_static {
name: "android_test_stubs_current",
srcs: [ ":test-api-stubs-docs" ],
static_libs: [ "private-stub-annotations-jar" ],
defaults: [
"android_defaults_stubs_current",
"android_stubs_dists_default",

View File

@@ -52,5 +52,10 @@
}
]
}
],
"postsubmit-managedprofile-stress": [
{
"name": "ManagedProfileLifecycleStressTest"
}
]
}

View File

@@ -18,7 +18,7 @@
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.StubActivity">
<activity android:name="android.perftests.utils.PerfTestActivity">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>

View File

@@ -20,9 +20,9 @@ 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.perftests.utils.StubActivity;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
@@ -46,8 +46,8 @@ public abstract class AbstractAutofillPerfTestCase {
Settings.Secure.AUTOFILL_SERVICE);
@Rule
public ActivityTestRule<StubActivity> mActivityRule =
new ActivityTestRule<StubActivity>(StubActivity.class);
public ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -68,7 +68,7 @@ public abstract class AbstractAutofillPerfTestCase {
Looper.getMainLooper().getThread() == Thread.currentThread());
assertTrue("We should be running on the main thread",
Looper.myLooper() == Looper.getMainLooper());
StubActivity activity = mActivityRule.getActivity();
PerfTestActivity activity = mActivityRule.getActivity();
activity.setContentView(mLayoutId);
onCreate(activity);
});
@@ -89,9 +89,9 @@ public abstract class AbstractAutofillPerfTestCase {
}
/**
* Initializes the {@link StubActivity} after it was launched.
* Initializes the {@link PerfTestActivity} after it was launched.
*/
protected abstract void onCreate(StubActivity activity);
protected abstract void onCreate(PerfTestActivity activity);
/**
* Uses the {@code settings} binary to set the autofill service.

View File

@@ -20,7 +20,7 @@ import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT
import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import android.view.View;
import android.widget.EditText;
@@ -39,7 +39,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
}
@Override
protected void onCreate(StubActivity activity) {
protected void onCreate(PerfTestActivity activity) {
View root = activity.getWindow().getDecorView();
mUsername = root.findViewById(R.id.username);
mPassword = root.findViewById(R.id.password);

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
android_test {
name: "BlobStorePerfTests",
srcs: ["src/**/*.java"],
static_libs: [
"BlobStoreTestUtils",
"androidx.test.rules",
"androidx.annotation_annotation",
"apct-perftests-utils",
"ub-uiautomator",
],
platform_apis: true,
test_suites: ["device-tests"],
certificate: "platform",
}

View File

@@ -0,0 +1,27 @@
<?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.blob">
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.perftests.blob"/>
</manifest>

View File

@@ -0,0 +1,28 @@
<?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 BlobStorePerfTests 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="BlobStorePerfTests.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.perftests.blob" />
<option name="hidden-api-checks" value="false"/>
</test>
</configuration>

View File

@@ -0,0 +1,120 @@
/*
* 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.perftests.blob;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.os.ParcelFileDescriptor;
import android.perftests.utils.TraceMarkParser;
import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
import android.support.test.uiautomator.UiDevice;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.function.BiConsumer;
// Copy of com.android.frameworks.perftests.am.util.AtraceUtils. TODO: avoid this duplication.
public class AtraceUtils {
private static final String TAG = "AtraceUtils";
private static final boolean VERBOSE = true;
private static final String ATRACE_START = "atrace --async_start -b %d -c %s";
private static final String ATRACE_DUMP = "atrace --async_dump";
private static final String ATRACE_STOP = "atrace --async_stop";
private static final int DEFAULT_ATRACE_BUF_SIZE = 1024;
private UiAutomation mAutomation;
private static AtraceUtils sUtils = null;
private boolean mStarted = false;
private AtraceUtils(Instrumentation instrumentation) {
mAutomation = instrumentation.getUiAutomation();
}
public static AtraceUtils getInstance(Instrumentation instrumentation) {
if (sUtils == null) {
sUtils = new AtraceUtils(instrumentation);
}
return sUtils;
}
/**
* @param categories The list of the categories to trace, separated with space.
*/
public void startTrace(String categories) {
synchronized (this) {
if (mStarted) {
throw new IllegalStateException("atrace already started");
}
runShellCommand(String.format(
ATRACE_START, DEFAULT_ATRACE_BUF_SIZE, categories));
mStarted = true;
}
}
public void stopTrace() {
synchronized (this) {
mStarted = false;
runShellCommand(ATRACE_STOP);
}
}
private String runShellCommand(String cmd) {
try {
return UiDevice.getInstance(
InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* @param parser The function that can accept the buffer of atrace dump and parse it.
* @param handler The parse result handler
*/
public void performDump(TraceMarkParser parser,
BiConsumer<String, List<TraceMarkSlice>> handler) {
parser.reset();
try {
if (VERBOSE) {
Log.i(TAG, "Collecting atrace dump...");
}
writeDataToBuf(mAutomation.executeShellCommand(ATRACE_DUMP), parser);
} catch (IOException e) {
Log.e(TAG, "Error in reading dump", e);
}
parser.forAllSlices(handler);
}
// The given file descriptor here will be closed by this function
private void writeDataToBuf(ParcelFileDescriptor pfDescriptor,
TraceMarkParser parser) throws IOException {
InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
parser.visit(line);
}
}
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 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.perftests.blob;
import android.app.blob.BlobHandle;
import android.app.blob.BlobStoreManager;
import android.content.Context;
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.PerfManualStatusReporter;
import android.perftests.utils.TraceMarkParser;
import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
import android.support.test.uiautomator.UiDevice;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.utils.blob.DummyBlobData;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@LargeTest
@RunWith(Parameterized.class)
public class BlobStorePerfTests {
// From frameworks/native/cmds/atrace/atrace.cpp
private static final String ATRACE_CATEGORY_SYSTEM_SERVER = "ss";
// From f/b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
private static final String ATRACE_COMPUTE_DIGEST_PREFIX = "computeBlobDigest-";
private Context mContext;
private BlobStoreManager mBlobStoreManager;
private AtraceUtils mAtraceUtils;
private ManualBenchmarkState mState;
@Rule
public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
@Parameterized.Parameter(0)
public int fileSizeInMb;
@Parameterized.Parameters(name = "{0}MB")
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][] {
{ 25 },
{ 50 },
{ 100 },
{ 200 },
});
}
@Before
public void setUp() {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mBlobStoreManager = (BlobStoreManager) mContext.getSystemService(
Context.BLOB_STORE_SERVICE);
mAtraceUtils = AtraceUtils.getInstance(InstrumentationRegistry.getInstrumentation());
mState = mPerfManualStatusReporter.getBenchmarkState();
}
@After
public void tearDown() {
// TODO: Add a blob_store shell command to trigger idle maintenance to avoid hardcoding
// job id like this.
// From BlobStoreConfig.IDLE_JOB_ID = 191934935.
runShellCommand("cmd jobscheduler run -f android 191934935");
}
@Test
public void testComputeDigest() throws Exception {
mAtraceUtils.startTrace(ATRACE_CATEGORY_SYSTEM_SERVER);
try {
final List<Long> durations = new ArrayList<>();
final DummyBlobData blobData = prepareDataBlob(fileSizeInMb);
final TraceMarkParser parser = new TraceMarkParser(
line -> line.name.startsWith(ATRACE_COMPUTE_DIGEST_PREFIX));
while (mState.keepRunning(durations)) {
commitBlob(blobData);
durations.clear();
collectDigestDurationsFromTrace(parser, durations);
deleteBlob(blobData.getBlobHandle());
}
} finally {
mAtraceUtils.stopTrace();
}
}
private void collectDigestDurationsFromTrace(TraceMarkParser parser, List<Long> durations) {
mAtraceUtils.performDump(parser, (key, slices) -> {
for (TraceMarkSlice slice : slices) {
durations.add(TimeUnit.MICROSECONDS.toNanos(slice.getDurationInMicroseconds()));
}
});
}
private DummyBlobData prepareDataBlob(int fileSizeInMb) throws Exception {
final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
.setFileSize(fileSizeInMb * 1024 * 1024 /* bytes */)
.build();
blobData.prepare();
return blobData;
}
private void commitBlob(DummyBlobData blobData) throws Exception {
final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
blobData.writeToSession(session);
final CompletableFuture<Integer> callback = new CompletableFuture<>();
session.commit(mContext.getMainExecutor(), callback::complete);
// Ignore commit callback result.
callback.get();
}
}
private void deleteBlob(BlobHandle blobHandle) throws Exception {
runShellCommand(String.format(
"cmd blob_store delete-blob --algo %s --digest %s --label %s --expiry %d --tag %s",
blobHandle.algorithm,
Base64.getEncoder().encode(blobHandle.digest),
blobHandle.label,
blobHandle.expiryTimeMillis,
blobHandle.tag));
}
private String runShellCommand(String cmd) {
try {
return UiDevice.getInstance(
InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,34 @@
android_test {
name: "CorePerfTests",
resource_dirs: ["res"],
srcs: [
"src/**/*.java",
"src/**/*.kt",
"src/android/os/ISomeService.aidl",
],
static_libs: [
"androidx.appcompat_appcompat",
"androidx.test.rules",
"androidx.annotation_annotation",
"apct-perftests-overlay-apps",
"apct-perftests-resources-manager-apps",
"apct-perftests-utils",
"guava",
],
libs: ["android.test.base"],
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

@@ -1,29 +0,0 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
src/android/os/ISomeService.aidl
LOCAL_STATIC_JAVA_LIBRARIES := \
androidx.test.rules \
androidx.annotation_annotation \
apct-perftests-utils \
guava
LOCAL_JAVA_LIBRARIES := android.test.base
LOCAL_PACKAGE_NAME := CorePerfTests
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_JNI_SHARED_LIBRARIES := libperftestscore_jni
# Use google-fonts/dancing-script for the performance metrics
LOCAL_ASSET_DIR := $(TOP)/external/google-fonts/dancing-script
LOCAL_COMPATIBILITY_SUITE += device-tests
include $(BUILD_PACKAGE)

View File

@@ -5,12 +5,15 @@
<permission android:name="com.android.perftests.core.TestPermission" />
<uses-permission android:name="com.android.perftests.core.TestPermission" />
<uses-permission
android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" />
<uses-permission android:name="android.permission.DELETE_PACKAGES" />
<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" />
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.StubActivity">
<activity android:name="android.perftests.utils.PerfTestActivity">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>

View File

@@ -0,0 +1,188 @@
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
android_test_helper_app {
name: "Overlay0",
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay0",
]
}
android_test_helper_app {
name: "Overlay1",
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay1",
]
}
android_test_helper_app {
name: "Overlay2",
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay2",
]
}
android_test_helper_app {
name: "Overlay3",
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay3",
]
}
android_test_helper_app {
name: "Overlay4",
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay4",
]
}
android_test_helper_app {
name: "Overlay5",
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay5",
]
}
android_test_helper_app {
name: "Overlay6",
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay6",
]
}
android_test_helper_app {
name: "Overlay7",
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay7",
]
}
android_test_helper_app {
name: "Overlay8",
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay8",
]
}
android_test_helper_app {
name: "Overlay9",
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay9",
]
}
android_test_helper_app {
name: "LargeOverlay0",
resource_dirs : [ "res_large" ],
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay.large0",
]
}
android_test_helper_app {
name: "LargeOverlay1",
resource_dirs : [ "res_large" ],
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay.large1",
]
}
android_test_helper_app {
name: "LargeOverlay2",
resource_dirs : [ "res_large" ],
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay.large2",
]
}
android_test_helper_app {
name: "LargeOverlay3",
resource_dirs : [ "res_large" ],
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay.large3",
]
}
android_test_helper_app {
name: "LargeOverlay4",
resource_dirs : [ "res_large" ],
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay.large4",
]
}
android_test_helper_app {
name: "LargeOverlay5",
resource_dirs : [ "res_large" ],
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay.large5",
]
}
android_test_helper_app {
name: "LargeOverlay6",
resource_dirs : [ "res_large" ],
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay.large6",
]
}
android_test_helper_app {
name: "LargeOverlay7",
resource_dirs : [ "res_large" ],
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay.large7",
]
}
android_test_helper_app {
name: "LargeOverlay8",
resource_dirs : [ "res_large" ],
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay.large8",
]
}
android_test_helper_app {
name: "LargeOverlay9",
resource_dirs : [ "res_large" ],
aaptflags: [
"--rename-manifest-package com.android.perftests.overlay.large9",
]
}
java_library {
name: "apct-perftests-overlay-apps",
java_resources: [
":Overlay0",
":Overlay1",
":Overlay2",
":Overlay3",
":Overlay4",
":Overlay5",
":Overlay6",
":Overlay7",
":Overlay8",
":Overlay9",
":LargeOverlay0",
":LargeOverlay1",
":LargeOverlay2",
":LargeOverlay3",
":LargeOverlay4",
":LargeOverlay5",
":LargeOverlay6",
":LargeOverlay7",
":LargeOverlay8",
":LargeOverlay9",
],
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.perftests.overlay">
<application android:hasCode="false" />
<uses-sdk android:targetSdkVersion="29" />
<overlay android:targetPackage="com.android.perftests.core" android:targetName="TestResources"/>
</manifest>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="short_text">B</string>
</resources>

View File

@@ -0,0 +1,274 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="short_text000">B</string>
<string name="short_text001">B</string>
<string name="short_text002">B</string>
<string name="short_text003">B</string>
<string name="short_text004">B</string>
<string name="short_text005">B</string>
<string name="short_text006">B</string>
<string name="short_text007">B</string>
<string name="short_text008">B</string>
<string name="short_text009">B</string>
<string name="short_text010">B</string>
<string name="short_text011">B</string>
<string name="short_text012">B</string>
<string name="short_text013">B</string>
<string name="short_text014">B</string>
<string name="short_text015">B</string>
<string name="short_text016">B</string>
<string name="short_text017">B</string>
<string name="short_text018">B</string>
<string name="short_text019">B</string>
<string name="short_text020">B</string>
<string name="short_text021">B</string>
<string name="short_text022">B</string>
<string name="short_text023">B</string>
<string name="short_text024">B</string>
<string name="short_text025">B</string>
<string name="short_text026">B</string>
<string name="short_text027">B</string>
<string name="short_text028">B</string>
<string name="short_text029">B</string>
<string name="short_text030">B</string>
<string name="short_text031">B</string>
<string name="short_text032">B</string>
<string name="short_text033">B</string>
<string name="short_text034">B</string>
<string name="short_text035">B</string>
<string name="short_text036">B</string>
<string name="short_text037">B</string>
<string name="short_text038">B</string>
<string name="short_text039">B</string>
<string name="short_text040">B</string>
<string name="short_text041">B</string>
<string name="short_text042">B</string>
<string name="short_text043">B</string>
<string name="short_text044">B</string>
<string name="short_text045">B</string>
<string name="short_text046">B</string>
<string name="short_text047">B</string>
<string name="short_text048">B</string>
<string name="short_text049">B</string>
<string name="short_text050">B</string>
<string name="short_text051">B</string>
<string name="short_text052">B</string>
<string name="short_text053">B</string>
<string name="short_text054">B</string>
<string name="short_text055">B</string>
<string name="short_text056">B</string>
<string name="short_text057">B</string>
<string name="short_text058">B</string>
<string name="short_text059">B</string>
<string name="short_text060">B</string>
<string name="short_text061">B</string>
<string name="short_text062">B</string>
<string name="short_text063">B</string>
<string name="short_text064">B</string>
<string name="short_text065">B</string>
<string name="short_text066">B</string>
<string name="short_text067">B</string>
<string name="short_text068">B</string>
<string name="short_text069">B</string>
<string name="short_text070">B</string>
<string name="short_text071">B</string>
<string name="short_text072">B</string>
<string name="short_text073">B</string>
<string name="short_text074">B</string>
<string name="short_text075">B</string>
<string name="short_text076">B</string>
<string name="short_text077">B</string>
<string name="short_text078">B</string>
<string name="short_text079">B</string>
<string name="short_text080">B</string>
<string name="short_text081">B</string>
<string name="short_text082">B</string>
<string name="short_text083">B</string>
<string name="short_text084">B</string>
<string name="short_text085">B</string>
<string name="short_text086">B</string>
<string name="short_text087">B</string>
<string name="short_text088">B</string>
<string name="short_text089">B</string>
<string name="short_text090">B</string>
<string name="short_text091">B</string>
<string name="short_text092">B</string>
<string name="short_text093">B</string>
<string name="short_text094">B</string>
<string name="short_text095">B</string>
<string name="short_text096">B</string>
<string name="short_text097">B</string>
<string name="short_text098">B</string>
<string name="short_text099">B</string>
<string name="short_text100">B</string>
<string name="short_text101">B</string>
<string name="short_text102">B</string>
<string name="short_text103">B</string>
<string name="short_text104">B</string>
<string name="short_text105">B</string>
<string name="short_text106">B</string>
<string name="short_text107">B</string>
<string name="short_text108">B</string>
<string name="short_text109">B</string>
<string name="short_text110">B</string>
<string name="short_text111">B</string>
<string name="short_text112">B</string>
<string name="short_text113">B</string>
<string name="short_text114">B</string>
<string name="short_text115">B</string>
<string name="short_text116">B</string>
<string name="short_text117">B</string>
<string name="short_text118">B</string>
<string name="short_text119">B</string>
<string name="short_text120">B</string>
<string name="short_text121">B</string>
<string name="short_text122">B</string>
<string name="short_text123">B</string>
<string name="short_text124">B</string>
<string name="short_text125">B</string>
<string name="short_text126">B</string>
<string name="short_text127">B</string>
<string name="short_text128">B</string>
<string name="short_text129">B</string>
<string name="short_text130">B</string>
<string name="short_text131">B</string>
<string name="short_text132">B</string>
<string name="short_text133">B</string>
<string name="short_text134">B</string>
<string name="short_text135">B</string>
<string name="short_text136">B</string>
<string name="short_text137">B</string>
<string name="short_text138">B</string>
<string name="short_text139">B</string>
<string name="short_text140">B</string>
<string name="short_text141">B</string>
<string name="short_text142">B</string>
<string name="short_text143">B</string>
<string name="short_text144">B</string>
<string name="short_text145">B</string>
<string name="short_text146">B</string>
<string name="short_text147">B</string>
<string name="short_text148">B</string>
<string name="short_text149">B</string>
<string name="short_text150">B</string>
<string name="short_text151">B</string>
<string name="short_text152">B</string>
<string name="short_text153">B</string>
<string name="short_text154">B</string>
<string name="short_text155">B</string>
<string name="short_text156">B</string>
<string name="short_text157">B</string>
<string name="short_text158">B</string>
<string name="short_text159">B</string>
<string name="short_text160">B</string>
<string name="short_text161">B</string>
<string name="short_text162">B</string>
<string name="short_text163">B</string>
<string name="short_text164">B</string>
<string name="short_text165">B</string>
<string name="short_text166">B</string>
<string name="short_text167">B</string>
<string name="short_text168">B</string>
<string name="short_text169">B</string>
<string name="short_text170">B</string>
<string name="short_text171">B</string>
<string name="short_text172">B</string>
<string name="short_text173">B</string>
<string name="short_text174">B</string>
<string name="short_text175">B</string>
<string name="short_text176">B</string>
<string name="short_text177">B</string>
<string name="short_text178">B</string>
<string name="short_text179">B</string>
<string name="short_text180">B</string>
<string name="short_text181">B</string>
<string name="short_text182">B</string>
<string name="short_text183">B</string>
<string name="short_text184">B</string>
<string name="short_text185">B</string>
<string name="short_text186">B</string>
<string name="short_text187">B</string>
<string name="short_text188">B</string>
<string name="short_text189">B</string>
<string name="short_text190">B</string>
<string name="short_text191">B</string>
<string name="short_text192">B</string>
<string name="short_text193">B</string>
<string name="short_text194">B</string>
<string name="short_text195">B</string>
<string name="short_text196">B</string>
<string name="short_text197">B</string>
<string name="short_text198">B</string>
<string name="short_text199">B</string>
<string name="short_text200">B</string>
<string name="short_text201">B</string>
<string name="short_text202">B</string>
<string name="short_text203">B</string>
<string name="short_text204">B</string>
<string name="short_text205">B</string>
<string name="short_text206">B</string>
<string name="short_text207">B</string>
<string name="short_text208">B</string>
<string name="short_text209">B</string>
<string name="short_text210">B</string>
<string name="short_text211">B</string>
<string name="short_text212">B</string>
<string name="short_text213">B</string>
<string name="short_text214">B</string>
<string name="short_text215">B</string>
<string name="short_text216">B</string>
<string name="short_text217">B</string>
<string name="short_text218">B</string>
<string name="short_text219">B</string>
<string name="short_text220">B</string>
<string name="short_text221">B</string>
<string name="short_text222">B</string>
<string name="short_text223">B</string>
<string name="short_text224">B</string>
<string name="short_text225">B</string>
<string name="short_text226">B</string>
<string name="short_text227">B</string>
<string name="short_text228">B</string>
<string name="short_text229">B</string>
<string name="short_text230">B</string>
<string name="short_text231">B</string>
<string name="short_text232">B</string>
<string name="short_text233">B</string>
<string name="short_text234">B</string>
<string name="short_text235">B</string>
<string name="short_text236">B</string>
<string name="short_text237">B</string>
<string name="short_text238">B</string>
<string name="short_text239">B</string>
<string name="short_text240">B</string>
<string name="short_text241">B</string>
<string name="short_text242">B</string>
<string name="short_text243">B</string>
<string name="short_text244">B</string>
<string name="short_text245">B</string>
<string name="short_text246">B</string>
<string name="short_text247">B</string>
<string name="short_text248">B</string>
<string name="short_text249">B</string>
<string name="short_text250">B</string>
<string name="short_text251">B</string>
<string name="short_text252">B</string>
<string name="short_text253">B</string>
<string name="short_text254">B</string>
<string name="short_text255">B</string>
</resources>

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
android_test_helper_app {
name: "LargeResourcesCompressed",
static_libs: [ "androidx.appcompat_appcompat" ],
}
genrule {
name: "LargeResourcesUncompressed",
srcs: [ ":LargeResourcesCompressed" ],
out: ["LargeResourcesUncompressed.apk"],
cmd: "cp $(in) $(out) && unzip -o $(out) resources.arsc"
+ " && zip $(out) resources.arsc"
}
java_library {
name: "apct-perftests-resources-manager-apps",
java_resources: [
":LargeResourcesCompressed",
":LargeResourcesUncompressed",
],
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="fake.android">
<application android:hasCode="false" />
<uses-sdk android:targetSdkVersion="29" />
</manifest>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true"
android:color="#000000" />
<item android:state_pressed="true"
android:state_enabled="false"
android:color="#212121" />
<item android:state_enabled="false"
android:color="#414141" />
<item android:color="#616161" />
</selector>

View File

@@ -0,0 +1,280 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<overlayable name="TestResources">
<policy type="public">
<item type="string" name="short_text" />
<item type="string" name="short_text000" />
<item type="string" name="short_text001" />
<item type="string" name="short_text002" />
<item type="string" name="short_text003" />
<item type="string" name="short_text004" />
<item type="string" name="short_text005" />
<item type="string" name="short_text006" />
<item type="string" name="short_text007" />
<item type="string" name="short_text008" />
<item type="string" name="short_text009" />
<item type="string" name="short_text010" />
<item type="string" name="short_text011" />
<item type="string" name="short_text012" />
<item type="string" name="short_text013" />
<item type="string" name="short_text014" />
<item type="string" name="short_text015" />
<item type="string" name="short_text016" />
<item type="string" name="short_text017" />
<item type="string" name="short_text018" />
<item type="string" name="short_text019" />
<item type="string" name="short_text020" />
<item type="string" name="short_text021" />
<item type="string" name="short_text022" />
<item type="string" name="short_text023" />
<item type="string" name="short_text024" />
<item type="string" name="short_text025" />
<item type="string" name="short_text026" />
<item type="string" name="short_text027" />
<item type="string" name="short_text028" />
<item type="string" name="short_text029" />
<item type="string" name="short_text030" />
<item type="string" name="short_text031" />
<item type="string" name="short_text032" />
<item type="string" name="short_text033" />
<item type="string" name="short_text034" />
<item type="string" name="short_text035" />
<item type="string" name="short_text036" />
<item type="string" name="short_text037" />
<item type="string" name="short_text038" />
<item type="string" name="short_text039" />
<item type="string" name="short_text040" />
<item type="string" name="short_text041" />
<item type="string" name="short_text042" />
<item type="string" name="short_text043" />
<item type="string" name="short_text044" />
<item type="string" name="short_text045" />
<item type="string" name="short_text046" />
<item type="string" name="short_text047" />
<item type="string" name="short_text048" />
<item type="string" name="short_text049" />
<item type="string" name="short_text050" />
<item type="string" name="short_text051" />
<item type="string" name="short_text052" />
<item type="string" name="short_text053" />
<item type="string" name="short_text054" />
<item type="string" name="short_text055" />
<item type="string" name="short_text056" />
<item type="string" name="short_text057" />
<item type="string" name="short_text058" />
<item type="string" name="short_text059" />
<item type="string" name="short_text060" />
<item type="string" name="short_text061" />
<item type="string" name="short_text062" />
<item type="string" name="short_text063" />
<item type="string" name="short_text064" />
<item type="string" name="short_text065" />
<item type="string" name="short_text066" />
<item type="string" name="short_text067" />
<item type="string" name="short_text068" />
<item type="string" name="short_text069" />
<item type="string" name="short_text070" />
<item type="string" name="short_text071" />
<item type="string" name="short_text072" />
<item type="string" name="short_text073" />
<item type="string" name="short_text074" />
<item type="string" name="short_text075" />
<item type="string" name="short_text076" />
<item type="string" name="short_text077" />
<item type="string" name="short_text078" />
<item type="string" name="short_text079" />
<item type="string" name="short_text080" />
<item type="string" name="short_text081" />
<item type="string" name="short_text082" />
<item type="string" name="short_text083" />
<item type="string" name="short_text084" />
<item type="string" name="short_text085" />
<item type="string" name="short_text086" />
<item type="string" name="short_text087" />
<item type="string" name="short_text088" />
<item type="string" name="short_text089" />
<item type="string" name="short_text090" />
<item type="string" name="short_text091" />
<item type="string" name="short_text092" />
<item type="string" name="short_text093" />
<item type="string" name="short_text094" />
<item type="string" name="short_text095" />
<item type="string" name="short_text096" />
<item type="string" name="short_text097" />
<item type="string" name="short_text098" />
<item type="string" name="short_text099" />
<item type="string" name="short_text100" />
<item type="string" name="short_text101" />
<item type="string" name="short_text102" />
<item type="string" name="short_text103" />
<item type="string" name="short_text104" />
<item type="string" name="short_text105" />
<item type="string" name="short_text106" />
<item type="string" name="short_text107" />
<item type="string" name="short_text108" />
<item type="string" name="short_text109" />
<item type="string" name="short_text110" />
<item type="string" name="short_text111" />
<item type="string" name="short_text112" />
<item type="string" name="short_text113" />
<item type="string" name="short_text114" />
<item type="string" name="short_text115" />
<item type="string" name="short_text116" />
<item type="string" name="short_text117" />
<item type="string" name="short_text118" />
<item type="string" name="short_text119" />
<item type="string" name="short_text120" />
<item type="string" name="short_text121" />
<item type="string" name="short_text122" />
<item type="string" name="short_text123" />
<item type="string" name="short_text124" />
<item type="string" name="short_text125" />
<item type="string" name="short_text126" />
<item type="string" name="short_text127" />
<item type="string" name="short_text128" />
<item type="string" name="short_text129" />
<item type="string" name="short_text130" />
<item type="string" name="short_text131" />
<item type="string" name="short_text132" />
<item type="string" name="short_text133" />
<item type="string" name="short_text134" />
<item type="string" name="short_text135" />
<item type="string" name="short_text136" />
<item type="string" name="short_text137" />
<item type="string" name="short_text138" />
<item type="string" name="short_text139" />
<item type="string" name="short_text140" />
<item type="string" name="short_text141" />
<item type="string" name="short_text142" />
<item type="string" name="short_text143" />
<item type="string" name="short_text144" />
<item type="string" name="short_text145" />
<item type="string" name="short_text146" />
<item type="string" name="short_text147" />
<item type="string" name="short_text148" />
<item type="string" name="short_text149" />
<item type="string" name="short_text150" />
<item type="string" name="short_text151" />
<item type="string" name="short_text152" />
<item type="string" name="short_text153" />
<item type="string" name="short_text154" />
<item type="string" name="short_text155" />
<item type="string" name="short_text156" />
<item type="string" name="short_text157" />
<item type="string" name="short_text158" />
<item type="string" name="short_text159" />
<item type="string" name="short_text160" />
<item type="string" name="short_text161" />
<item type="string" name="short_text162" />
<item type="string" name="short_text163" />
<item type="string" name="short_text164" />
<item type="string" name="short_text165" />
<item type="string" name="short_text166" />
<item type="string" name="short_text167" />
<item type="string" name="short_text168" />
<item type="string" name="short_text169" />
<item type="string" name="short_text170" />
<item type="string" name="short_text171" />
<item type="string" name="short_text172" />
<item type="string" name="short_text173" />
<item type="string" name="short_text174" />
<item type="string" name="short_text175" />
<item type="string" name="short_text176" />
<item type="string" name="short_text177" />
<item type="string" name="short_text178" />
<item type="string" name="short_text179" />
<item type="string" name="short_text180" />
<item type="string" name="short_text181" />
<item type="string" name="short_text182" />
<item type="string" name="short_text183" />
<item type="string" name="short_text184" />
<item type="string" name="short_text185" />
<item type="string" name="short_text186" />
<item type="string" name="short_text187" />
<item type="string" name="short_text188" />
<item type="string" name="short_text189" />
<item type="string" name="short_text190" />
<item type="string" name="short_text191" />
<item type="string" name="short_text192" />
<item type="string" name="short_text193" />
<item type="string" name="short_text194" />
<item type="string" name="short_text195" />
<item type="string" name="short_text196" />
<item type="string" name="short_text197" />
<item type="string" name="short_text198" />
<item type="string" name="short_text199" />
<item type="string" name="short_text200" />
<item type="string" name="short_text201" />
<item type="string" name="short_text202" />
<item type="string" name="short_text203" />
<item type="string" name="short_text204" />
<item type="string" name="short_text205" />
<item type="string" name="short_text206" />
<item type="string" name="short_text207" />
<item type="string" name="short_text208" />
<item type="string" name="short_text209" />
<item type="string" name="short_text210" />
<item type="string" name="short_text211" />
<item type="string" name="short_text212" />
<item type="string" name="short_text213" />
<item type="string" name="short_text214" />
<item type="string" name="short_text215" />
<item type="string" name="short_text216" />
<item type="string" name="short_text217" />
<item type="string" name="short_text218" />
<item type="string" name="short_text219" />
<item type="string" name="short_text220" />
<item type="string" name="short_text221" />
<item type="string" name="short_text222" />
<item type="string" name="short_text223" />
<item type="string" name="short_text224" />
<item type="string" name="short_text225" />
<item type="string" name="short_text226" />
<item type="string" name="short_text227" />
<item type="string" name="short_text228" />
<item type="string" name="short_text229" />
<item type="string" name="short_text230" />
<item type="string" name="short_text231" />
<item type="string" name="short_text232" />
<item type="string" name="short_text233" />
<item type="string" name="short_text234" />
<item type="string" name="short_text235" />
<item type="string" name="short_text236" />
<item type="string" name="short_text237" />
<item type="string" name="short_text238" />
<item type="string" name="short_text239" />
<item type="string" name="short_text240" />
<item type="string" name="short_text241" />
<item type="string" name="short_text242" />
<item type="string" name="short_text243" />
<item type="string" name="short_text244" />
<item type="string" name="short_text245" />
<item type="string" name="short_text246" />
<item type="string" name="short_text247" />
<item type="string" name="short_text248" />
<item type="string" name="short_text249" />
<item type="string" name="short_text250" />
<item type="string" name="short_text251" />
<item type="string" name="short_text252" />
<item type="string" name="short_text253" />
<item type="string" name="short_text254" />
<item type="string" name="short_text255" />
</policy>
</overlayable>
</resources>

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="long_text">text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text typo text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text </string>
<string name="short_text">text text</string>
</resources>

View File

@@ -0,0 +1,368 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="long_text">text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text typo text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text text
text text text text text text text text text text text text </string>
<plurals name="plurals_text">
<item quantity="one">1 text</item>
<item quantity="other"><xliff:g id="count" example="3">%d</xliff:g> texts</item>
</plurals>
<string-array name="strings">
<item>Run</item>
<item>the</item>
<item>performance</item>
<item>tests!</item>
<item>Run</item>
<item>the</item>
<item>performance</item>
<item>tests!</item>
<item>Run</item>
<item>the</item>
<item>performance</item>
<item>tests!</item>
<item>Run</item>
<item>the</item>
<item>performance</item>
<item>tests!</item>
<item>Run</item>
<item>the</item>
<item>performance</item>
<item>tests!</item>
<item>Run</item>
<item>the</item>
<item>performance</item>
<item>tests!</item>
<item>Run</item>
<item>the</item>
<item>performance</item>
<item>tests!</item>
<item>Run</item>
<item>the</item>
<item>performance</item>
<item>tests!</item>
</string-array>
<integer-array name="ints">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</integer-array>
<color name="white">#ffffff</color>
<integer name="forty_two">42</integer>
<string name="short_text">text text</string>
<string name="short_text000">B</string>
<string name="short_text001">B</string>
<string name="short_text002">B</string>
<string name="short_text003">B</string>
<string name="short_text004">B</string>
<string name="short_text005">B</string>
<string name="short_text006">B</string>
<string name="short_text007">B</string>
<string name="short_text008">B</string>
<string name="short_text009">B</string>
<string name="short_text010">B</string>
<string name="short_text011">B</string>
<string name="short_text012">B</string>
<string name="short_text013">B</string>
<string name="short_text014">B</string>
<string name="short_text015">B</string>
<string name="short_text016">B</string>
<string name="short_text017">B</string>
<string name="short_text018">B</string>
<string name="short_text019">B</string>
<string name="short_text020">B</string>
<string name="short_text021">B</string>
<string name="short_text022">B</string>
<string name="short_text023">B</string>
<string name="short_text024">B</string>
<string name="short_text025">B</string>
<string name="short_text026">B</string>
<string name="short_text027">B</string>
<string name="short_text028">B</string>
<string name="short_text029">B</string>
<string name="short_text030">B</string>
<string name="short_text031">B</string>
<string name="short_text032">B</string>
<string name="short_text033">B</string>
<string name="short_text034">B</string>
<string name="short_text035">B</string>
<string name="short_text036">B</string>
<string name="short_text037">B</string>
<string name="short_text038">B</string>
<string name="short_text039">B</string>
<string name="short_text040">B</string>
<string name="short_text041">B</string>
<string name="short_text042">B</string>
<string name="short_text043">B</string>
<string name="short_text044">B</string>
<string name="short_text045">B</string>
<string name="short_text046">B</string>
<string name="short_text047">B</string>
<string name="short_text048">B</string>
<string name="short_text049">B</string>
<string name="short_text050">B</string>
<string name="short_text051">B</string>
<string name="short_text052">B</string>
<string name="short_text053">B</string>
<string name="short_text054">B</string>
<string name="short_text055">B</string>
<string name="short_text056">B</string>
<string name="short_text057">B</string>
<string name="short_text058">B</string>
<string name="short_text059">B</string>
<string name="short_text060">B</string>
<string name="short_text061">B</string>
<string name="short_text062">B</string>
<string name="short_text063">B</string>
<string name="short_text064">B</string>
<string name="short_text065">B</string>
<string name="short_text066">B</string>
<string name="short_text067">B</string>
<string name="short_text068">B</string>
<string name="short_text069">B</string>
<string name="short_text070">B</string>
<string name="short_text071">B</string>
<string name="short_text072">B</string>
<string name="short_text073">B</string>
<string name="short_text074">B</string>
<string name="short_text075">B</string>
<string name="short_text076">B</string>
<string name="short_text077">B</string>
<string name="short_text078">B</string>
<string name="short_text079">B</string>
<string name="short_text080">B</string>
<string name="short_text081">B</string>
<string name="short_text082">B</string>
<string name="short_text083">B</string>
<string name="short_text084">B</string>
<string name="short_text085">B</string>
<string name="short_text086">B</string>
<string name="short_text087">B</string>
<string name="short_text088">B</string>
<string name="short_text089">B</string>
<string name="short_text090">B</string>
<string name="short_text091">B</string>
<string name="short_text092">B</string>
<string name="short_text093">B</string>
<string name="short_text094">B</string>
<string name="short_text095">B</string>
<string name="short_text096">B</string>
<string name="short_text097">B</string>
<string name="short_text098">B</string>
<string name="short_text099">B</string>
<string name="short_text100">B</string>
<string name="short_text101">B</string>
<string name="short_text102">B</string>
<string name="short_text103">B</string>
<string name="short_text104">B</string>
<string name="short_text105">B</string>
<string name="short_text106">B</string>
<string name="short_text107">B</string>
<string name="short_text108">B</string>
<string name="short_text109">B</string>
<string name="short_text110">B</string>
<string name="short_text111">B</string>
<string name="short_text112">B</string>
<string name="short_text113">B</string>
<string name="short_text114">B</string>
<string name="short_text115">B</string>
<string name="short_text116">B</string>
<string name="short_text117">B</string>
<string name="short_text118">B</string>
<string name="short_text119">B</string>
<string name="short_text120">B</string>
<string name="short_text121">B</string>
<string name="short_text122">B</string>
<string name="short_text123">B</string>
<string name="short_text124">B</string>
<string name="short_text125">B</string>
<string name="short_text126">B</string>
<string name="short_text127">B</string>
<string name="short_text128">B</string>
<string name="short_text129">B</string>
<string name="short_text130">B</string>
<string name="short_text131">B</string>
<string name="short_text132">B</string>
<string name="short_text133">B</string>
<string name="short_text134">B</string>
<string name="short_text135">B</string>
<string name="short_text136">B</string>
<string name="short_text137">B</string>
<string name="short_text138">B</string>
<string name="short_text139">B</string>
<string name="short_text140">B</string>
<string name="short_text141">B</string>
<string name="short_text142">B</string>
<string name="short_text143">B</string>
<string name="short_text144">B</string>
<string name="short_text145">B</string>
<string name="short_text146">B</string>
<string name="short_text147">B</string>
<string name="short_text148">B</string>
<string name="short_text149">B</string>
<string name="short_text150">B</string>
<string name="short_text151">B</string>
<string name="short_text152">B</string>
<string name="short_text153">B</string>
<string name="short_text154">B</string>
<string name="short_text155">B</string>
<string name="short_text156">B</string>
<string name="short_text157">B</string>
<string name="short_text158">B</string>
<string name="short_text159">B</string>
<string name="short_text160">B</string>
<string name="short_text161">B</string>
<string name="short_text162">B</string>
<string name="short_text163">B</string>
<string name="short_text164">B</string>
<string name="short_text165">B</string>
<string name="short_text166">B</string>
<string name="short_text167">B</string>
<string name="short_text168">B</string>
<string name="short_text169">B</string>
<string name="short_text170">B</string>
<string name="short_text171">B</string>
<string name="short_text172">B</string>
<string name="short_text173">B</string>
<string name="short_text174">B</string>
<string name="short_text175">B</string>
<string name="short_text176">B</string>
<string name="short_text177">B</string>
<string name="short_text178">B</string>
<string name="short_text179">B</string>
<string name="short_text180">B</string>
<string name="short_text181">B</string>
<string name="short_text182">B</string>
<string name="short_text183">B</string>
<string name="short_text184">B</string>
<string name="short_text185">B</string>
<string name="short_text186">B</string>
<string name="short_text187">B</string>
<string name="short_text188">B</string>
<string name="short_text189">B</string>
<string name="short_text190">B</string>
<string name="short_text191">B</string>
<string name="short_text192">B</string>
<string name="short_text193">B</string>
<string name="short_text194">B</string>
<string name="short_text195">B</string>
<string name="short_text196">B</string>
<string name="short_text197">B</string>
<string name="short_text198">B</string>
<string name="short_text199">B</string>
<string name="short_text200">B</string>
<string name="short_text201">B</string>
<string name="short_text202">B</string>
<string name="short_text203">B</string>
<string name="short_text204">B</string>
<string name="short_text205">B</string>
<string name="short_text206">B</string>
<string name="short_text207">B</string>
<string name="short_text208">B</string>
<string name="short_text209">B</string>
<string name="short_text210">B</string>
<string name="short_text211">B</string>
<string name="short_text212">B</string>
<string name="short_text213">B</string>
<string name="short_text214">B</string>
<string name="short_text215">B</string>
<string name="short_text216">B</string>
<string name="short_text217">B</string>
<string name="short_text218">B</string>
<string name="short_text219">B</string>
<string name="short_text220">B</string>
<string name="short_text221">B</string>
<string name="short_text222">B</string>
<string name="short_text223">B</string>
<string name="short_text224">B</string>
<string name="short_text225">B</string>
<string name="short_text226">B</string>
<string name="short_text227">B</string>
<string name="short_text228">B</string>
<string name="short_text229">B</string>
<string name="short_text230">B</string>
<string name="short_text231">B</string>
<string name="short_text232">B</string>
<string name="short_text233">B</string>
<string name="short_text234">B</string>
<string name="short_text235">B</string>
<string name="short_text236">B</string>
<string name="short_text237">B</string>
<string name="short_text238">B</string>
<string name="short_text239">B</string>
<string name="short_text240">B</string>
<string name="short_text241">B</string>
<string name="short_text242">B</string>
<string name="short_text243">B</string>
<string name="short_text244">B</string>
<string name="short_text245">B</string>
<string name="short_text246">B</string>
<string name="short_text247">B</string>
<string name="short_text248">B</string>
<string name="short_text249">B</string>
<string name="short_text250">B</string>
<string name="short_text251">B</string>
<string name="short_text252">B</string>
<string name="short_text253">B</string>
<string name="short_text254">B</string>
<string name="short_text255">B</string>
</resources>

View File

@@ -0,0 +1,234 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.om.OverlayManager;
import android.os.UserHandle;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.TestPackageInstaller;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import com.android.perftests.core.R;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* Benchmarks for {@link android.content.om.OverlayManager}.
*/
@LargeTest
public class OverlayManagerPerfTest {
private static final int OVERLAY_PKG_COUNT = 10;
private static Context sContext;
private static OverlayManager sOverlayManager;
private static Executor sExecutor;
private static ArrayList<TestPackageInstaller.InstalledPackage> sSmallOverlays =
new ArrayList<>();
private static ArrayList<TestPackageInstaller.InstalledPackage> sLargeOverlays =
new ArrayList<>();
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@BeforeClass
public static void classSetUp() throws Exception {
sContext = InstrumentationRegistry.getTargetContext();
sOverlayManager = new OverlayManager(sContext);
sExecutor = (command) -> new Thread(command).start();
// Install all of the test overlays.
TestPackageInstaller installer = new TestPackageInstaller(sContext);
for (int i = 0; i < OVERLAY_PKG_COUNT; i++) {
sSmallOverlays.add(installer.installPackage("Overlay" + i +".apk"));
sLargeOverlays.add(installer.installPackage("LargeOverlay" + i +".apk"));
}
}
@AfterClass
public static void classTearDown() throws Exception {
for (TestPackageInstaller.InstalledPackage overlay : sSmallOverlays) {
overlay.uninstall();
}
for (TestPackageInstaller.InstalledPackage overlay : sLargeOverlays) {
overlay.uninstall();
}
}
@After
public void tearDown() throws Exception {
// Disable all test overlays after each test.
for (TestPackageInstaller.InstalledPackage overlay : sSmallOverlays) {
assertSetEnabled(sContext, overlay.getPackageName(), false);
}
for (TestPackageInstaller.InstalledPackage overlay : sLargeOverlays) {
assertSetEnabled(sContext, overlay.getPackageName(), false);
}
}
/**
* Enables the overlay and waits for the APK path change sto be propagated to the context
* AssetManager.
*/
private void assertSetEnabled(Context context, String overlayPackage, boolean eanabled)
throws Exception {
sOverlayManager.setEnabled(overlayPackage, true, UserHandle.SYSTEM);
// Wait for the overlay changes to propagate
FutureTask<Boolean> task = new FutureTask<>(() -> {
while (true) {
for (String path : context.getAssets().getApkPaths()) {
if (eanabled == path.contains(overlayPackage)) {
return true;
}
}
}
});
sExecutor.execute(task);
assertTrue("Failed to load overlay " + overlayPackage,
task.get(20, TimeUnit.SECONDS));
}
@Test
public void setEnabledWarmCache() throws Exception {
String packageName = sSmallOverlays.get(0).getPackageName();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
assertSetEnabled(sContext, packageName, true);
// Disable the overlay for the next iteration of the test
state.pauseTiming();
assertSetEnabled(sContext, packageName, false);
state.resumeTiming();
}
}
@Test
public void setEnabledColdCacheSmallOverlay() throws Exception {
String packageName = sSmallOverlays.get(0).getPackageName();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
assertSetEnabled(sContext, packageName, true);
// Disable the overlay and remove the idmap for the next iteration of the test
state.pauseTiming();
assertSetEnabled(sContext, packageName, false);
sOverlayManager.invalidateCachesForOverlay(packageName, UserHandle.SYSTEM);
state.resumeTiming();
}
}
@Test
public void setEnabledColdCacheLargeOverlay() throws Exception {
String packageName = sLargeOverlays.get(0).getPackageName();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
assertSetEnabled(sContext, packageName, true);
// Disable the overlay and remove the idmap for the next iteration of the test
state.pauseTiming();
assertSetEnabled(sContext, packageName, false);
sOverlayManager.invalidateCachesForOverlay(packageName, UserHandle.SYSTEM);
state.resumeTiming();
}
}
@Test
public void setEnabledDisable() throws Exception {
String packageName = sSmallOverlays.get(0).getPackageName();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
assertSetEnabled(sContext, packageName, true);
state.resumeTiming();
assertSetEnabled(sContext, packageName, false);
}
}
@Test
public void getStringOneSmallOverlay() throws Exception {
String packageName = sSmallOverlays.get(0).getPackageName();
assertSetEnabled(sContext, packageName, true);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
sContext.getString(R.string.short_text);
}
assertSetEnabled(sContext, packageName, false);
}
@Test
public void getStringOneLargeOverlay() throws Exception {
String packageName = sLargeOverlays.get(0).getPackageName();
assertSetEnabled(sContext, packageName, true);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
for (int resId = R.string.short_text000; resId < R.string.short_text255; resId++) {
sContext.getString(resId);
}
}
assertSetEnabled(sContext, packageName, false);
}
@Test
public void getStringTenOverlays() throws Exception {
// Enable all test overlays
for (TestPackageInstaller.InstalledPackage overlay : sSmallOverlays) {
assertSetEnabled(sContext, overlay.getPackageName(), true);
}
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
sContext.getString(R.string.short_text);
}
}
@Test
public void getStringLargeTenOverlays() throws Exception {
// Enable all test overlays
for (TestPackageInstaller.InstalledPackage overlay : sLargeOverlays) {
assertSetEnabled(sContext, overlay.getPackageName(), true);
}
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
for (int resId = R.string.short_text000; resId < R.string.short_text255; resId++) {
sContext.getString(resId);
}
}
}
}

View File

@@ -20,7 +20,7 @@ import android.content.Context;
import android.content.Intent;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
@@ -48,7 +48,7 @@ public class PendingIntentPerfTest {
@Before
public void setUp() {
mContext = InstrumentationRegistry.getTargetContext();
mIntent = StubActivity.createLaunchIntent(mContext);
mIntent = PerfTestActivity.createLaunchIntent(mContext);
}
/**

View File

@@ -0,0 +1,139 @@
/*
* Copyright (C) 2018 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.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;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Benchmarks for {@link android.app.ResourcesManager}.
*/
@LargeTest
public class ResourcesManagerPerfTest {
private static Context sContext;
private static File sResourcesCompressed;
private static File sResourcesUncompressed;
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@BeforeClass
public static void setUp() throws Exception {
sContext = InstrumentationRegistry.getTargetContext();
sResourcesCompressed = copyApkToTemp("LargeResourcesCompressed.apk",
"LargeResourcesCompressed.apk");
sResourcesUncompressed = copyApkToTemp("LargeResourcesUncompressed.apk",
"LargeResourcesUncompressed.apk");
}
@AfterClass
public static void tearDown() {
Assert.assertTrue(sResourcesCompressed.delete());
Assert.assertTrue(sResourcesUncompressed.delete());
}
private static File copyApkToTemp(String inputFileName, String fileName) throws Exception {
File file = File.createTempFile(fileName, null, sContext.getCacheDir());
try (OutputStream tempOutputStream = new FileOutputStream(file);
InputStream is = sContext.getResources().getAssets().openNonAsset(inputFileName)) {
byte[] buffer = new byte[4096];
int n;
while ((n = is.read(buffer)) >= 0) {
tempOutputStream.write(buffer, 0, n);
}
tempOutputStream.flush();
}
return file;
}
private void getResourcesForPath(String path) {
ResourcesManager.getInstance().getResources(null, path, null, null, null,
Display.DEFAULT_DISPLAY, null, sContext.getResources().getCompatibilityInfo(),
null, null);
}
@Test
public void getResourcesCached() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
getResourcesForPath(sResourcesCompressed.getPath());
while (state.keepRunning()) {
getResourcesForPath(sResourcesCompressed.getPath());
}
}
@Test
public void getResourcesCompressedUncached() {
ResourcesManager resourcesManager = ResourcesManager.getInstance();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
resourcesManager.invalidatePath(sResourcesCompressed.getPath());
state.resumeTiming();
getResourcesForPath(sResourcesCompressed.getPath());
}
}
@Test
public void getResourcesUncompressedUncached() {
ResourcesManager resourcesManager = ResourcesManager.getInstance();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
resourcesManager.invalidatePath(sResourcesUncompressed.getPath());
state.resumeTiming();
getResourcesForPath(sResourcesUncompressed.getPath());
}
}
@Test
public void applyConfigurationToResourcesLocked() {
ResourcesManager resourcesManager = ResourcesManager.getInstance();
Configuration c = new Configuration(resourcesManager.getConfiguration());
c.uiMode = Configuration.UI_MODE_TYPE_WATCH;
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
resourcesManager.applyConfigurationToResources(c, null);
// Alternate configurations to ensure the set configuration is different each iteration
if (c.uiMode == Configuration.UI_MODE_TYPE_WATCH) {
c.uiMode = Configuration.UI_MODE_TYPE_TELEVISION;
} else {
c.uiMode = Configuration.UI_MODE_TYPE_WATCH;
}
}
}
}

View File

@@ -18,15 +18,18 @@ package android.app;
import static org.junit.Assert.fail;
import android.content.res.AssetManager;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.util.TypedValue;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import org.junit.After;
import com.android.perftests.core.R;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -43,36 +46,123 @@ public class ResourcesPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private AssetManager mAsset;
private Resources mRes;
private int mTextId;
private int mColorId;
private int mIntegerId;
private int mLayoutId;
@Before
public void setUp() {
mAsset = new AssetManager();
mAsset.addAssetPath("/system/framework/framework-res.apk");
mRes = new Resources(mAsset, null, null);
mTextId = mRes.getIdentifier("cancel", "string", "android");
mColorId = mRes.getIdentifier("transparent", "color", "android");
mIntegerId = mRes.getIdentifier("config_shortAnimTime", "integer", "android");
mLayoutId = mRes.getIdentifier("two_line_list_item", "layout", "android");
Context context = InstrumentationRegistry.getTargetContext();
mRes = context.getResources();
}
@After
public void tearDown() {
mAsset.close();
@Test
public void getValue() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
TypedValue value = new TypedValue();
while (state.keepRunning()) {
mRes.getValue(R.integer.forty_two, value, false /* resolve_refs */);
}
}
@Test
public void getFrameworkValue() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
TypedValue value = new TypedValue();
while (state.keepRunning()) {
mRes.getValue(com.android.internal.R.integer.autofill_max_visible_datasets, value,
false /* resolve_refs */);
}
}
@Test
public void getValueString() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
TypedValue value = new TypedValue();
while (state.keepRunning()) {
mRes.getValue(R.string.long_text, value, false /* resolve_refs */);
}
}
@Test
public void getFrameworkStringValue() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
TypedValue value = new TypedValue();
while (state.keepRunning()) {
mRes.getValue(com.android.internal.R.string.cancel, value, false /* resolve_refs */);
}
}
@Test
public void getValueManyConfigurations() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
TypedValue value = new TypedValue();
while (state.keepRunning()) {
mRes.getValue(com.android.internal.R.string.mmcc_illegal_me, value,
false /* resolve_refs */);
}
}
@Test
public void getText() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mRes.getText(mTextId);
mRes.getText(R.string.long_text);
}
}
@Test
public void getFont() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mRes.getFont(R.font.samplefont);
}
}
@Test
public void getString() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mRes.getString(R.string.long_text);
}
}
@Test
public void getQuantityString() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mRes.getQuantityString(R.plurals.plurals_text, 5);
}
}
@Test
public void getQuantityText() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mRes.getQuantityText(R.plurals.plurals_text, 5);
}
}
@Test
public void getTextArray() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mRes.getTextArray(R.array.strings);
}
}
@Test
public void getStringArray() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mRes.getStringArray(R.array.strings);
}
}
@Test
public void getIntegerArray() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mRes.getIntArray(R.array.ints);
}
}
@@ -80,15 +170,23 @@ public class ResourcesPerfTest {
public void getColor() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mRes.getColor(mColorId, null);
mRes.getColor(R.color.white, null);
}
}
@Test
public void getInteger() {
public void getColorStateList() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mRes.getInteger(mIntegerId);
mRes.getColorStateList(R.color.color_state_list, null);
}
}
@Test
public void getVectorDrawable() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mRes.getDrawable(R.drawable.vector_drawable01, null);
}
}
@@ -96,7 +194,7 @@ public class ResourcesPerfTest {
public void getLayoutAndTravese() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
try (XmlResourceParser parser = mRes.getLayout(mLayoutId)) {
try (XmlResourceParser parser = mRes.getLayout(R.layout.test_relative_layout)) {
while (parser.next() != XmlPullParser.END_DOCUMENT) {
// Walk the entire tree
}
@@ -105,4 +203,23 @@ public class ResourcesPerfTest {
}
}
}
}
@Test
public void getLayoutAndTraverseInvalidateCaches() {
mRes.flushLayoutCache();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
try (XmlResourceParser parser = mRes.getLayout(R.layout.test_relative_layout)) {
while (parser.next() != XmlPullParser.END_DOCUMENT) {
// Walk the entire tree
}
} catch (IOException | XmlPullParserException exception) {
fail("Parsing of the layout failed. Something is really broken");
}
state.pauseTiming();
mRes.flushLayoutCache();
state.resumeTiming();
}
}
}

View File

@@ -16,13 +16,19 @@
package android.app;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.UserHandle;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.view.Display;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -35,13 +41,69 @@ public class ResourcesThemePerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private Context mContext;
private int mThemeResId;
private Resources.Theme mTheme;
@Before
public void setUp() {
Context context = InstrumentationRegistry.getTargetContext();
mTheme = context.getTheme();
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
mThemeResId = com.android.perftests.core.R.style.Base_V7_Theme_AppCompat;
mTheme = mContext.getResources().newTheme();
mTheme.applyStyle(mThemeResId, true /* force */);
}
@Test
public void applyStyle() {
Resources.Theme destTheme = mContext.getResources().newTheme();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
destTheme.applyStyle(mThemeResId, true /* force */);
}
}
@Test
public void rebase() {
Resources.Theme destTheme = mContext.getResources().newTheme();
destTheme.applyStyle(mThemeResId, true /* force */);
destTheme.applyStyle(android.R.style.Theme_Material, true /* force */);
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
destTheme.rebase();
}
}
@Test
public void setToSameAssetManager() {
Resources.Theme destTheme = mContext.getResources().newTheme();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
destTheme.setTo(mTheme);
}
}
@Test
public void setToDifferentAssetManager() throws Exception {
// Create a new Resources object with the same asset paths but a different AssetManager
PackageManager packageManager = mContext.getApplicationContext().getPackageManager();
ApplicationInfo ai = packageManager.getApplicationInfo(mContext.getPackageName(),
UserHandle.myUserId());
ResourcesManager resourcesManager = ResourcesManager.getInstance();
Configuration c = resourcesManager.getConfiguration();
c.orientation = (c.orientation == Configuration.ORIENTATION_PORTRAIT)
? 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);
Assert.assertNotEquals(destResources.getAssets(), mContext.getAssets());
Resources.Theme destTheme = destResources.newTheme();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
destTheme.setTo(mTheme);
}
}
@Test
@@ -51,5 +113,4 @@ public class ResourcesThemePerfTest {
mTheme.obtainStyledAttributes(android.R.style.Theme_Material, android.R.styleable.View);
}
}
}
}

View File

@@ -19,7 +19,7 @@ package android.graphics.perftests;
import android.graphics.Paint;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -58,7 +58,8 @@ public class PaintHasGlyphPerfTest {
}
@Rule
public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
public ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

View File

@@ -26,7 +26,7 @@ import android.graphics.drawable.VectorDrawable;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.BitmapUtils;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import android.test.suitebuilder.annotation.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -48,8 +48,8 @@ public class VectorDrawablePerfTest {
private int[] mTestHeights = {512, 1024};
@Rule
public ActivityTestRule<StubActivity> mActivityRule =
new ActivityTestRule(StubActivity.class);
public ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

View File

@@ -1,110 +0,0 @@
/*
* Copyright (C) 2017 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.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
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 PackageManagerPerfTest {
private static final String PERMISSION_NAME_EXISTS =
"com.android.perftests.core.TestPermission";
private static final String PERMISSION_NAME_DOESNT_EXIST =
"com.android.perftests.core.TestBadPermission";
private static final ComponentName TEST_ACTIVITY =
new ComponentName("com.android.perftests.core", "android.perftests.utils.StubActivity");
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Test
public void testCheckPermissionExists() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
final String packageName = TEST_ACTIVITY.getPackageName();
while (state.keepRunning()) {
int ret = pm.checkPermission(PERMISSION_NAME_EXISTS, packageName);
}
}
@Test
public void testCheckPermissionDoesntExist() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
final String packageName = TEST_ACTIVITY.getPackageName();
while (state.keepRunning()) {
int ret = pm.checkPermission(PERMISSION_NAME_DOESNT_EXIST, packageName);
}
}
@Test
public void testQueryIntentActivities() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
final Intent intent = new Intent("com.android.perftests.core.PERFTEST");
while (state.keepRunning()) {
pm.queryIntentActivities(intent, 0);
}
}
@Test
public void testGetPackageInfo() throws Exception {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
final String packageName = TEST_ACTIVITY.getPackageName();
while (state.keepRunning()) {
pm.getPackageInfo(packageName, 0);
}
}
@Test
public void testGetApplicationInfo() throws Exception {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
final String packageName = TEST_ACTIVITY.getPackageName();
while (state.keepRunning()) {
pm.getApplicationInfo(packageName, 0);
}
}
@Test
public void testGetActivityInfo() throws Exception {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
while (state.keepRunning()) {
pm.getActivityInfo(TEST_ACTIVITY, 0);
}
}
}

View File

@@ -0,0 +1,254 @@
/*
* 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.pm.PackageParser
import android.content.pm.PackageParserCacheHelper.ReadHelper
import android.content.pm.PackageParserCacheHelper.WriteHelper
import android.content.pm.parsing.ParsingPackageImpl
import android.content.pm.parsing.ParsingPackageRead
import android.content.pm.parsing.ParsingPackageUtils
import android.content.pm.parsing.result.ParseInput
import android.content.pm.parsing.result.ParseTypeImpl
import android.content.res.TypedArray
import android.perftests.utils.BenchmarkState
import android.perftests.utils.PerfStatusReporter
import androidx.test.filters.LargeTest
import com.android.internal.util.ConcurrentUtils
import libcore.io.IoUtils
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.TimeUnit
@LargeTest
@RunWith(Parameterized::class)
class PackageParsingPerfTest {
companion object {
private const val PARALLEL_QUEUE_CAPACITY = 10
private const val PARALLEL_MAX_THREADS = 4
private const val QUEUE_POLL_TIMEOUT_SECONDS = 5L
// TODO: Replace this with core version of SYSTEM_PARTITIONS
val FOLDERS_TO_TEST = listOf(
Environment.getRootDirectory(),
Environment.getVendorDirectory(),
Environment.getOdmDirectory(),
Environment.getOemDirectory(),
Environment.getOemDirectory(),
Environment.getSystemExtDirectory()
)
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun parameters(): Array<Params> {
val apks = FOLDERS_TO_TEST
.filter(File::exists)
.map(File::walkTopDown)
.flatMap(Sequence<File>::asIterable)
.filter { it.name.endsWith(".apk") }
return arrayOf(
Params(1, apks) { ParallelParser1(it?.let(::PackageCacher1)) },
Params(2, apks) { ParallelParser2(it?.let(::PackageCacher2)) }
)
}
data class Params(
val version: Int,
val apks: List<File>,
val cacheDirToParser: (File?) -> ParallelParser<*>
) {
// For test name formatting
override fun toString() = "v$version"
}
}
@get:Rule
var perfStatusReporter = PerfStatusReporter()
@get:Rule
var testFolder = TemporaryFolder()
@Parameterized.Parameter(0)
lateinit var params: Params
private val state: BenchmarkState get() = perfStatusReporter.benchmarkState
private val apks: List<File> get() = params.apks
@Test
fun sequentialNoCache() {
params.cacheDirToParser(null).use { parser ->
while (state.keepRunning()) {
apks.forEach { parser.parse(it) }
}
}
}
@Test
fun sequentialCached() {
params.cacheDirToParser(testFolder.newFolder()).use { parser ->
// Fill the cache
apks.forEach { parser.parse(it) }
while (state.keepRunning()) {
apks.forEach { parser.parse(it) }
}
}
}
@Test
fun parallelNoCache() {
params.cacheDirToParser(null).use { parser ->
while (state.keepRunning()) {
apks.forEach { parser.submit(it) }
repeat(apks.size) { parser.take() }
}
}
}
@Test
fun parallelCached() {
params.cacheDirToParser(testFolder.newFolder()).use { parser ->
// Fill the cache
apks.forEach { parser.parse(it) }
while (state.keepRunning()) {
apks.forEach { parser.submit(it) }
repeat(apks.size) { parser.take() }
}
}
}
abstract class ParallelParser<PackageType : Parcelable>(
private val cacher: PackageCacher<PackageType>? = null
) : AutoCloseable {
private val queue = ArrayBlockingQueue<Any>(PARALLEL_QUEUE_CAPACITY)
private val service = ConcurrentUtils.newFixedThreadPool(
PARALLEL_MAX_THREADS, "package-parsing-test",
Process.THREAD_PRIORITY_FOREGROUND)
fun submit(file: File) = service.submit { queue.put(parse(file)) }
fun take() = queue.poll(QUEUE_POLL_TIMEOUT_SECONDS, TimeUnit.SECONDS)
override fun close() {
service.shutdownNow()
}
fun parse(file: File) = cacher?.getCachedResult(file)
?: parseImpl(file).also { cacher?.cacheResult(file, it) }
protected abstract fun parseImpl(file: File): PackageType
}
class ParallelParser1(private val cacher: PackageCacher1? = null)
: ParallelParser<PackageParser.Package>(cacher) {
val parser = PackageParser().apply {
setCallback { true }
}
override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null)
}
class ParallelParser2(cacher: PackageCacher2? = null)
: ParallelParser<ParsingPackageRead>(cacher) {
val input = ThreadLocal.withInitial {
// For testing, just disable enforcement to avoid hooking up to compat framework
ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
}
val parser = ParsingPackageUtils(false, null, null,
object : ParsingPackageUtils.Callback {
override fun hasFeature(feature: String) = true
override fun startParsingPackage(
packageName: String,
baseCodePath: String,
codePath: String,
manifestArray: TypedArray,
isCoreApp: Boolean
) = ParsingPackageImpl(packageName, baseCodePath, codePath, manifestArray)
})
override fun parseImpl(file: File) =
parser.parsePackage(input.get()!!.reset(), file, 0).result
}
abstract class PackageCacher<PackageType : Parcelable>(private val cacheDir: File) {
fun getCachedResult(file: File): PackageType? {
val cacheFile = File(cacheDir, file.name)
if (!cacheFile.exists()) {
return null
}
val bytes = IoUtils.readFileAsByteArray(cacheFile.absolutePath)
val parcel = Parcel.obtain().apply {
unmarshall(bytes, 0, bytes.size)
setDataPosition(0)
}
ReadHelper(parcel).apply { startAndInstall() }
return fromParcel(parcel).also {
parcel.recycle()
}
}
fun cacheResult(file: File, parsed: Parcelable) {
val cacheFile = File(cacheDir, file.name)
if (cacheFile.exists()) {
if (!cacheFile.delete()) {
throw IllegalStateException("Unable to delete cache file: $cacheFile")
}
}
val cacheEntry = toCacheEntry(parsed)
return FileOutputStream(cacheFile).use { fos -> fos.write(cacheEntry) }
}
private fun toCacheEntry(pkg: Parcelable): ByteArray {
val parcel = Parcel.obtain()
val helper = WriteHelper(parcel)
pkg.writeToParcel(parcel, 0 /* flags */)
helper.finishAndUninstall()
return parcel.marshall().also {
parcel.recycle()
}
}
protected abstract fun fromParcel(parcel: Parcel): PackageType
}
/**
* Re-implementation of v1's cache, since that's gone in R+.
*/
class PackageCacher1(cacheDir: File) : PackageCacher<PackageParser.Package>(cacheDir) {
override fun fromParcel(parcel: Parcel) = PackageParser.Package(parcel)
}
/**
* Re-implementation of the server side PackageCacher, as it's inaccessible here.
*/
class PackageCacher2(cacheDir: File) : PackageCacher<ParsingPackageRead>(cacheDir) {
override fun fromParcel(parcel: Parcel) = ParsingPackageImpl(parcel)
}
}

View File

@@ -23,7 +23,7 @@ import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import android.text.style.ReplacementSpan;
import android.util.ArraySet;
@@ -75,7 +75,8 @@ public class DynamicLayoutPerfTest {
}
@Rule
public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
public ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

View File

@@ -0,0 +1,240 @@
/*
* 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 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.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;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class CutoutSpecificationBenchmark {
private static final String TAG = "CutoutSpecificationBenchmark";
private static final String BOTTOM_MARKER = "@bottom";
private static final String DP_MARKER = "@dp";
private static final String RIGHT_MARKER = "@right";
private static final String LEFT_MARKER = "@left";
private static final String DOUBLE_CUTOUT_SPEC = "M 0,0\n"
+ "L -72, 0\n"
+ "L -69.9940446283, 20.0595537175\n"
+ "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n"
+ "L 56.8, 32.0\n"
+ "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n"
+ "L 72, 0\n"
+ "Z\n"
+ "@bottom\n"
+ "M 0,0\n"
+ "L -72, 0\n"
+ "L -69.9940446283, -20.0595537175\n"
+ "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n"
+ "L 56.8, -32.0\n"
+ "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20.0595537175\n"
+ "L 72, 0\n"
+ "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);
}
private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
final RectF rectF = new RectF();
p.computeBounds(rectF, false /* unused */);
rectF.round(inoutRect);
inoutRegion.op(inoutRect, Region.Op.UNION);
}
private static void oldMethodParsingSpec(String spec, int displayWidth, int displayHeight,
float density) {
Path p = null;
Rect boundTop = null;
Rect boundBottom = null;
Rect safeInset = new Rect();
String bottomSpec = null;
if (!TextUtils.isEmpty(spec)) {
spec = spec.trim();
final float offsetX;
if (spec.endsWith(RIGHT_MARKER)) {
offsetX = displayWidth;
spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
} else if (spec.endsWith(LEFT_MARKER)) {
offsetX = 0;
spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
} else {
offsetX = displayWidth / 2f;
}
final boolean inDp = spec.endsWith(DP_MARKER);
if (inDp) {
spec = spec.substring(0, spec.length() - DP_MARKER.length());
}
if (spec.contains(BOTTOM_MARKER)) {
String[] splits = spec.split(BOTTOM_MARKER, 2);
spec = splits[0].trim();
bottomSpec = splits[1].trim();
}
final Matrix m = new Matrix();
final Region r = Region.obtain();
if (!spec.isEmpty()) {
try {
p = PathParser.createPathFromPathData(spec);
} catch (Throwable e) {
Log.wtf(TAG, "Could not inflate cutout: ", e);
}
if (p != null) {
if (inDp) {
m.postScale(density, density);
}
m.postTranslate(offsetX, 0);
p.transform(m);
boundTop = new Rect();
toRectAndAddToRegion(p, r, boundTop);
safeInset.top = boundTop.bottom;
}
}
if (bottomSpec != null) {
int bottomInset = 0;
Path bottomPath = null;
try {
bottomPath = PathParser.createPathFromPathData(bottomSpec);
} catch (Throwable e) {
Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
}
if (bottomPath != null) {
// Keep top transform
m.postTranslate(0, displayHeight);
bottomPath.transform(m);
p.addPath(bottomPath);
boundBottom = new Rect();
toRectAndAddToRegion(bottomPath, r, boundBottom);
bottomInset = displayHeight - boundBottom.top;
}
safeInset.bottom = bottomInset;
}
}
}
@Test
public void parseByOldMethodForDoubleCutout() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
oldMethodParsingSpec(DOUBLE_CUTOUT_SPEC, mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels, mDisplayMetrics.density);
}
}
@Test
public void parseByNewMethodForDoubleCutout() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
new CutoutSpecification.Parser(mDisplayMetrics.density,
mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)
.parse(DOUBLE_CUTOUT_SPEC);
}
}
@Test
public void parseLongEdgeCutout() {
final String spec = "M 0,0\n"
+ "H 48\n"
+ "V 48\n"
+ "H -48\n"
+ "Z\n"
+ "@left\n"
+ "@center_vertical\n"
+ "M 0,0\n"
+ "H 48\n"
+ "V 48\n"
+ "H -48\n"
+ "Z\n"
+ "@left\n"
+ "@center_vertical\n"
+ "M 0,0\n"
+ "H -48\n"
+ "V 48\n"
+ "H 48\n"
+ "Z\n"
+ "@right\n"
+ "@dp";
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
new CutoutSpecification.Parser(mDisplayMetrics.density,
mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec);
}
}
@Test
public void parseShortEdgeCutout() {
final String spec = "M 0,0\n"
+ "H 48\n"
+ "V 48\n"
+ "H -48\n"
+ "Z\n"
+ "@bottom\n"
+ "M 0,0\n"
+ "H 48\n"
+ "V -48\n"
+ "H -48\n"
+ "Z\n"
+ "@dp";
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
new CutoutSpecification.Parser(mDisplayMetrics.density,
mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec);
}
}
}

View File

@@ -23,7 +23,7 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import android.view.View.MeasureSpec;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -46,7 +46,8 @@ import java.util.List;
public class ViewShowHidePerfTest {
@Rule
public ActivityTestRule mActivityRule = new ActivityTestRule(StubActivity.class);
public ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

View File

@@ -18,7 +18,7 @@ package android.widget;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
@@ -80,7 +80,8 @@ public class EditTextBackspacePerfTest {
}
@Rule
public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
public ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

View File

@@ -18,7 +18,7 @@ package android.widget;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
@@ -74,7 +74,8 @@ public class EditTextCursorMovementPerfTest {
}
@Rule
public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
public ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

View File

@@ -19,7 +19,7 @@ package android.widget;
import android.app.Activity;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
@@ -59,7 +59,8 @@ public class EditTextLongTextPerfTest {
}
@Rule
public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
public ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

View File

@@ -28,7 +28,7 @@ import android.app.Activity;
import android.os.Looper;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import android.view.View;
import android.view.ViewGroup;
@@ -72,8 +72,8 @@ public class LayoutPerfTest {
}
@Rule
public ActivityTestRule<StubActivity> mActivityRule =
new ActivityTestRule(StubActivity.class);
public ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

View File

@@ -22,7 +22,7 @@ import android.app.Activity;
import android.os.Looper;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -64,8 +64,8 @@ public class TextViewAutoSizeLayoutPerfTest {
}
@Rule
public ActivityTestRule<StubActivity> mActivityRule =
new ActivityTestRule(StubActivity.class);
public ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

View File

@@ -18,7 +18,7 @@ package android.widget;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.perftests.utils.PerfTestActivity;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -56,7 +56,8 @@ public class TextViewSetTextLocalePerfTest {
}
@Rule
public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
public ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

View File

@@ -17,6 +17,7 @@ android_test {
srcs: ["src/**/*.java"],
static_libs: [
"androidx.test.rules",
"collector-device-lib-platform",
"apct-perftests-utils",
],
platform_apis: true,

View File

@@ -17,9 +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.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.REAL_GET_TASKS" />
<application>
<uses-library android:name="android.test.runner" />

View File

@@ -19,6 +19,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="MultiUserPerfTests.apk" />
<option name="test-file-name" value="MultiUserPerfDummyApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >

View File

@@ -0,0 +1,21 @@
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
android_test_helper_app {
name: "MultiUserPerfDummyApp",
srcs: ["src/**/*.java"],
sdk_version: "test_current",
}

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="perftests.multiuser.apps.dummyapp" >
<application android:label="Perftest Multiuser Dummy App">
<activity
android:name=".DummyForegroundActivity"
android:label="Perftest Multiuser Dummy App"
android:exported="true"
android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package perftests.multiuser.apps.dummyapp;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
/** An activity. */
public class DummyForegroundActivity extends Activity {
private static final String TAG = DummyForegroundActivity.class.getSimpleName();
public static final int TOP_SLEEP_TIME_MS = 2_000;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
doSleepWhileTop(TOP_SLEEP_TIME_MS);
}
/** Does nothing, but asynchronously. */
private void doSleepWhileTop(int sleepTime) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(sleepTime);
return null;
}
@Override
protected void onPostExecute(Void nothing) {
finish();
}
}.execute();
}
}

View File

@@ -15,19 +15,35 @@
*/
package android.multiuser;
import static org.junit.Assume.assumeTrue;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.UserSwitchObserver;
import android.app.WaitResult;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.IPackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.perftests.utils.ShellHelper;
import android.util.Log;
import android.view.WindowManagerGlobal;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
@@ -66,13 +82,25 @@ import java.util.concurrent.TimeUnit;
public class UserLifecycleTests {
private static final String TAG = UserLifecycleTests.class.getSimpleName();
private final int TIMEOUT_IN_SECOND = 30;
private final int CHECK_USER_REMOVED_INTERVAL_MS = 200;
private static final int TIMEOUT_IN_SECOND = 30;
private static final int CHECK_USER_REMOVED_INTERVAL_MS = 200;
private static final String DUMMY_PACKAGE_NAME = "perftests.multiuser.apps.dummyapp";
// Copy of UserSystemPackageInstaller whitelist mode constants.
private static final String PACKAGE_WHITELIST_MODE_PROP =
"persist.debug.user.package_whitelist_mode";
private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0;
private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b001;
private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b100;
private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
private UserManager mUm;
private ActivityManager mAm;
private IActivityManager mIam;
private PackageManager mPm;
private ArrayList<Integer> mUsersToRemove;
private boolean mHasManagedUserFeature;
private final BenchmarkRunner mRunner = new BenchmarkRunner();
@Rule
@@ -85,6 +113,8 @@ public class UserLifecycleTests {
mAm = context.getSystemService(ActivityManager.class);
mIam = ActivityManager.getService();
mUsersToRemove = new ArrayList<>();
mPm = context.getPackageManager();
mHasManagedUserFeature = mPm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
}
@After
@@ -99,34 +129,89 @@ public class UserLifecycleTests {
}
@Test
public void createAndStartUser() throws Exception {
public void createUser() {
while (mRunner.keepRunning()) {
final UserInfo userInfo = mUm.createUser("TestUser", 0);
final CountDownLatch latch = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userInfo.id);
mIam.startUserInBackground(userInfo.id);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
final int userId = createUserNoFlags();
mRunner.pauseTiming();
removeUser(userInfo.id);
removeUser(userId);
mRunner.resumeTiming();
}
}
@Test
public void createAndStartUser() throws Exception {
while (mRunner.keepRunning()) {
final int userId = createUserNoFlags();
final CountDownLatch latch = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
// Don't use this.startUserInBackgroundAndWaitForUnlock() since only waiting until
// ACTION_USER_STARTED.
mIam.startUserInBackground(userId);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
mRunner.pauseTiming();
removeUser(userId);
mRunner.resumeTiming();
}
}
/**
* Measures the time until ACTION_USER_STARTED is received.
*/
@Test
public void startUser() throws Exception {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createUserNoFlags();
final CountDownLatch latch = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
mRunner.resumeTiming();
mIam.startUserInBackground(userId);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
mRunner.pauseTiming();
removeUser(userId);
mRunner.resumeTiming();
}
}
/**
* Measures the time until unlock listener is triggered and user is unlocked.
*/
@Test
public void startAndUnlockUser() throws Exception {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createUserNoFlags();
mRunner.resumeTiming();
// Waits for UserState.mUnlockProgress.finish().
startUserInBackgroundAndWaitForUnlock(userId);
mRunner.pauseTiming();
removeUser(userId);
mRunner.resumeTiming();
}
}
@Test
public void switchUser() throws Exception {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
final UserInfo userInfo = mUm.createUser("TestUser", 0);
final int userId = createUserNoFlags();
mRunner.resumeTiming();
switchUser(userInfo.id);
switchUser(userId);
mRunner.pauseTiming();
switchUser(startUser);
removeUser(userInfo.id);
removeUser(userId);
mRunner.resumeTiming();
}
}
@@ -176,17 +261,17 @@ public class UserLifecycleTests {
public void stopUser() throws Exception {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final UserInfo userInfo = mUm.createUser("TestUser", 0);
final int userId = createUserNoFlags();
final CountDownLatch latch = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userInfo.id);
mIam.startUserInBackground(userInfo.id);
registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
mIam.startUserInBackground(userId);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
mRunner.resumeTiming();
stopUser(userInfo.id, false);
stopUser(userId, false);
mRunner.pauseTiming();
removeUser(userInfo.id);
removeUser(userId);
mRunner.resumeTiming();
}
}
@@ -196,64 +281,17 @@ public class UserLifecycleTests {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
final UserInfo userInfo = mUm.createUser("TestUser", 0);
final int userId = createUserNoFlags();
final CountDownLatch latch = new CountDownLatch(1);
registerUserSwitchObserver(null, latch, userInfo.id);
registerUserSwitchObserver(null, latch, userId);
mRunner.resumeTiming();
mAm.switchUser(userInfo.id);
mAm.switchUser(userId);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
mRunner.pauseTiming();
switchUser(startUser);
removeUser(userInfo.id);
mRunner.resumeTiming();
}
}
@Test
public void managedProfileUnlock() throws Exception {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final UserInfo userInfo = mUm.createProfileForUser("TestUser",
UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser());
final CountDownLatch latch = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch, userInfo.id);
mRunner.resumeTiming();
mIam.startUserInBackground(userInfo.id);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
mRunner.pauseTiming();
removeUser(userInfo.id);
mRunner.resumeTiming();
}
}
/** Tests starting an already-created, but no-longer-running, profile. */
@Test
public void managedProfileUnlock_stopped() throws Exception {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final UserInfo userInfo = mUm.createProfileForUser("TestUser",
UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser());
// Start the profile initially, then stop it. Similar to setQuietModeEnabled.
final CountDownLatch latch1 = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch1, userInfo.id);
mIam.startUserInBackground(userInfo.id);
latch1.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
stopUser(userInfo.id, true);
// Now we restart the profile.
final CountDownLatch latch2 = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch2, userInfo.id);
mRunner.resumeTiming();
mIam.startUserInBackground(userInfo.id);
latch2.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
mRunner.pauseTiming();
removeUser(userInfo.id);
removeUser(userId);
mRunner.resumeTiming();
}
}
@@ -263,15 +301,14 @@ public class UserLifecycleTests {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
final UserInfo userInfo = mUm.createUser("TestUser",
UserInfo.FLAG_EPHEMERAL | UserInfo.FLAG_DEMO);
switchUser(userInfo.id);
final int userId = createUserWithFlags(UserInfo.FLAG_EPHEMERAL | UserInfo.FLAG_DEMO);
switchUser(userId);
final CountDownLatch latch = new CountDownLatch(1);
InstrumentationRegistry.getContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_USER_STOPPED.equals(intent.getAction()) && intent.getIntExtra(
Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) == userInfo.id) {
Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) == userId) {
latch.countDown();
}
}
@@ -285,31 +322,270 @@ public class UserLifecycleTests {
mRunner.pauseTiming();
switchLatch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
removeUser(userInfo.id);
removeUser(userId);
mRunner.resumeTiming();
}
}
/** Tests creating a new profile. */
@Test
public void managedProfileStopped() throws Exception {
public void managedProfileCreate() throws Exception {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
final int userId = createManagedProfile();
mRunner.pauseTiming();
attestTrue("Failed creating profile " + userId, mUm.isManagedProfile(userId));
removeUser(userId);
mRunner.resumeTiming();
}
}
/** Tests starting (unlocking) a newly-created profile. */
@Test
public void managedProfileUnlock() throws Exception {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final UserInfo userInfo = mUm.createProfileForUser("TestUser",
UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser());
final CountDownLatch latch = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch, userInfo.id);
mIam.startUserInBackground(userInfo.id);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
final int userId = createManagedProfile();
mRunner.resumeTiming();
stopUser(userInfo.id, true);
startUserInBackgroundAndWaitForUnlock(userId);
mRunner.pauseTiming();
removeUser(userInfo.id);
removeUser(userId);
mRunner.resumeTiming();
}
}
/** Tests starting (unlocking) an already-created, but no-longer-running, profile. */
@Test
public void managedProfileUnlock_stopped() throws Exception {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createManagedProfile();
// Start the profile initially, then stop it. Similar to setQuietModeEnabled.
startUserInBackgroundAndWaitForUnlock(userId);
stopUser(userId, true);
mRunner.resumeTiming();
startUserInBackgroundAndWaitForUnlock(userId);
mRunner.pauseTiming();
removeUser(userId);
mRunner.resumeTiming();
}
}
/**
* Tests starting (unlocking) and launching an already-installed app in a newly-created profile.
*/
@Test
public void managedProfileUnlockAndLaunchApp() throws Exception {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createManagedProfile();
WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
mRunner.resumeTiming();
startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
mRunner.pauseTiming();
removeUser(userId);
mRunner.resumeTiming();
}
}
/**
* Tests starting (unlocking) and launching a previously-launched app
* in an already-created, but no-longer-running, profile.
* A sort of combination of {@link #managedProfileUnlockAndLaunchApp} and
* {@link #managedProfileUnlock_stopped}}.
*/
@Test
public void managedProfileUnlockAndLaunchApp_stopped() throws Exception {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createManagedProfile();
WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
stopUser(userId, true);
TimeUnit.SECONDS.sleep(1); // Brief cool-down before re-starting profile.
mRunner.resumeTiming();
startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
mRunner.pauseTiming();
removeUser(userId);
mRunner.resumeTiming();
}
}
/** Tests installing a pre-existing app in a newly-created profile. */
@Test
public void managedProfileInstall() throws Exception {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createManagedProfile();
mRunner.resumeTiming();
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
mRunner.pauseTiming();
removeUser(userId);
mRunner.resumeTiming();
}
}
/**
* Tests creating a new profile, starting (unlocking) it, installing an app,
* and launching that app in it.
*/
@Test
public void managedProfileCreateUnlockInstallAndLaunchApp() throws Exception {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
mRunner.resumeTiming();
final int userId = createManagedProfile();
startUserInBackgroundAndWaitForUnlock(userId);
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
startApp(userId, DUMMY_PACKAGE_NAME);
mRunner.pauseTiming();
removeUser(userId);
mRunner.resumeTiming();
}
}
/** Tests stopping a profile. */
@Test
public void managedProfileStopped() throws Exception {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createManagedProfile();
startUserInBackgroundAndWaitForUnlock(userId);
mRunner.resumeTiming();
stopUser(userId, true);
mRunner.pauseTiming();
removeUser(userId);
mRunner.resumeTiming();
}
}
// 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 {
assumeTrue(mHasManagedUserFeature);
final int origMode = getUserTypePackageWhitelistMode();
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE
| USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST);
try {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createManagedProfile();
mRunner.resumeTiming();
startUserInBackgroundAndWaitForUnlock(userId);
mRunner.pauseTiming();
removeUser(userId);
mRunner.resumeTiming();
}
} finally {
setUserTypePackageWhitelistMode(origMode);
}
}
/** Tests starting (unlocking) a newly-created profile NOT using the user-type-pkg-whitelist. */
@Test
public void managedProfileUnlock_notUsingWhitelist() throws Exception {
assumeTrue(mHasManagedUserFeature);
final int origMode = getUserTypePackageWhitelistMode();
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE);
try {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createManagedProfile();
mRunner.resumeTiming();
startUserInBackgroundAndWaitForUnlock(userId);
mRunner.pauseTiming();
removeUser(userId);
mRunner.resumeTiming();
}
} finally {
setUserTypePackageWhitelistMode(origMode);
}
}
/** Creates a new user, returning its userId. */
private int createUserNoFlags() {
return createUserWithFlags(/* flags= */ 0);
}
/** Creates a new user with the given flags, returning its userId. */
private int createUserWithFlags(int flags) {
int userId = mUm.createUser("TestUser", 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",
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.");
}
mUsersToRemove.add(userInfo.id);
return userInfo.id;
}
/**
* Start user in background and wait for it to unlock by waiting for
* UserState.mUnlockProgress.finish().
* <p> To start in foreground instead, see {@link #switchUser(int)}.
* <p> This should always be used for profiles since profiles cannot be started in foreground.
*/
private void startUserInBackgroundAndWaitForUnlock(int userId) {
final ProgressWaiter waiter = new ProgressWaiter();
try {
mIam.startUserInBackgroundWithListener(userId, waiter);
boolean success = waiter.waitForFinish(TIMEOUT_IN_SECOND);
attestTrue("Failed to start user " + userId + " in background.", success);
} catch (RemoteException e) {
Log.e(TAG, "startUserInBackgroundAndWaitForUnlock failed", e);
}
}
/** Starts the given user in the foreground. */
private void switchUser(int userId) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
registerUserSwitchObserver(latch, null, userId);
@@ -342,7 +618,7 @@ public class UserLifecycleTests {
private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws Exception {
final int origUser = mAm.getCurrentUser();
// First, create and switch to testUser, waiting for its ACTION_USER_UNLOCKED
final int testUser = mUm.createUser("TestUser", 0).id;
final int testUser = createUserNoFlags();
final CountDownLatch latch1 = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch1, testUser);
mAm.switchUser(testUser);
@@ -361,6 +637,46 @@ public class UserLifecycleTests {
return testUser;
}
/**
* Installs the given package in the given user.
*/
private void installPreexistingApp(int userId, String packageName) throws RemoteException {
final CountDownLatch latch = new CountDownLatch(1);
final IntentSender sender = new IntentSender((IIntentSender) new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
latch.countDown();
}
});
final IPackageInstaller installer = AppGlobals.getPackageManager().getPackageInstaller();
installer.installExistingPackage(packageName,
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);
}
}
/**
* Launches the given package in the given user.
* Make sure the keyguard has been dismissed prior to calling.
*/
private void startApp(int userId, String packageName) throws RemoteException {
final Context context = InstrumentationRegistry.getContext();
final WaitResult result = ActivityTaskManager.getService().startActivityAndWait(null,
context.getPackageName(), context.getAttributionTag(),
context.getPackageManager().getLaunchIntentForPackage(packageName), null, null,
null, 0, 0, null, null, userId);
attestTrue("User " + userId + " failed to start " + packageName,
result.result == ActivityManager.START_SUCCESS);
}
private void registerUserSwitchObserver(final CountDownLatch switchLatch,
final CountDownLatch bootCompleteLatch, final int userId) throws Exception {
ActivityManager.getService().registerUserSwitchObserver(
@@ -394,6 +710,44 @@ public class UserLifecycleTests {
}, UserHandle.of(userId), new IntentFilter(action), null, null);
}
private class ProgressWaiter extends IProgressListener.Stub {
private final CountDownLatch mFinishedLatch = new CountDownLatch(1);
@Override
public void onStarted(int id, Bundle extras) {}
@Override
public void onProgress(int id, int progress, Bundle extras) {}
@Override
public void onFinished(int id, Bundle extras) {
mFinishedLatch.countDown();
}
public boolean waitForFinish(long timeoutSecs) {
try {
return mFinishedLatch.await(timeoutSecs, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "Thread interrupted unexpectedly.", e);
return false;
}
}
}
/** Gets the PACKAGE_WHITELIST_MODE_PROP System Property. */
private int getUserTypePackageWhitelistMode() {
return SystemProperties.getInt(PACKAGE_WHITELIST_MODE_PROP,
USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT);
}
/** Sets the PACKAGE_WHITELIST_MODE_PROP System Property to the given value. */
private void setUserTypePackageWhitelistMode(int mode) {
String result = ShellHelper.runShellCommand(
String.format("setprop %s %d", PACKAGE_WHITELIST_MODE_PROP, mode));
attestFalse("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ": " + result,
result != null && result.contains("Failed"));
}
private void removeUser(int userId) {
try {
mUm.removeUser(userId);
@@ -413,13 +767,13 @@ public class UserLifecycleTests {
}
}
private void attestTrue(String message, boolean attestion) {
if (!attestion) {
private void attestTrue(String message, boolean assertion) {
if (!assertion) {
Log.w(TAG, message);
}
}
private void attestFalse(String message, boolean attestion) {
attestTrue(message, !attestion);
private void attestFalse(String message, boolean assertion) {
attestTrue(message, !assertion);
}
}

View File

@@ -0,0 +1,21 @@
android_test {
name: "PackageManagerPerfTests",
srcs: ["src/**/*.java"],
static_libs: [
"platform-compat-test-rules",
"androidx.appcompat_appcompat",
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.annotation_annotation",
"apct-perftests-utils",
],
libs: ["android.test.base"],
platform_apis: true,
test_suites: ["device-tests"],
}

View File

@@ -0,0 +1,89 @@
<?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.packagemanager">
<permission android:name="com.android.perftests.packagemanager.TestPermission" />
<uses-permission android:name="com.android.perftests.packagemanager.TestPermission" />
<queries>
<package android:name="com.android.perftests.appenumeration0" />
<package android:name="com.android.perftests.appenumeration1" />
<package android:name="com.android.perftests.appenumeration2" />
<package android:name="com.android.perftests.appenumeration3" />
<package android:name="com.android.perftests.appenumeration4" />
<package android:name="com.android.perftests.appenumeration5" />
<package android:name="com.android.perftests.appenumeration6" />
<package android:name="com.android.perftests.appenumeration7" />
<package android:name="com.android.perftests.appenumeration8" />
<package android:name="com.android.perftests.appenumeration9" />
<package android:name="com.android.perftests.appenumeration10" />
<package android:name="com.android.perftests.appenumeration11" />
<package android:name="com.android.perftests.appenumeration12" />
<package android:name="com.android.perftests.appenumeration13" />
<package android:name="com.android.perftests.appenumeration14" />
<package android:name="com.android.perftests.appenumeration15" />
<package android:name="com.android.perftests.appenumeration16" />
<package android:name="com.android.perftests.appenumeration17" />
<package android:name="com.android.perftests.appenumeration18" />
<package android:name="com.android.perftests.appenumeration19" />
<package android:name="com.android.perftests.appenumeration20" />
<package android:name="com.android.perftests.appenumeration21" />
<package android:name="com.android.perftests.appenumeration22" />
<package android:name="com.android.perftests.appenumeration23" />
<package android:name="com.android.perftests.appenumeration24" />
<package android:name="com.android.perftests.appenumeration25" />
<package android:name="com.android.perftests.appenumeration26" />
<package android:name="com.android.perftests.appenumeration27" />
<package android:name="com.android.perftests.appenumeration28" />
<package android:name="com.android.perftests.appenumeration29" />
<package android:name="com.android.perftests.appenumeration30" />
<package android:name="com.android.perftests.appenumeration31" />
<package android:name="com.android.perftests.appenumeration32" />
<package android:name="com.android.perftests.appenumeration33" />
<package android:name="com.android.perftests.appenumeration34" />
<package android:name="com.android.perftests.appenumeration35" />
<package android:name="com.android.perftests.appenumeration36" />
<package android:name="com.android.perftests.appenumeration37" />
<package android:name="com.android.perftests.appenumeration38" />
<package android:name="com.android.perftests.appenumeration39" />
<package android:name="com.android.perftests.appenumeration40" />
<package android:name="com.android.perftests.appenumeration41" />
<package android:name="com.android.perftests.appenumeration42" />
<package android:name="com.android.perftests.appenumeration43" />
<package android:name="com.android.perftests.appenumeration44" />
<package android:name="com.android.perftests.appenumeration45" />
<package android:name="com.android.perftests.appenumeration46" />
<package android:name="com.android.perftests.appenumeration47" />
<package android:name="com.android.perftests.appenumeration48" />
<package android:name="com.android.perftests.appenumeration49" />
</queries>
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.PerfTestActivity">
<intent-filter>
<action android:name="com.android.perftests.packagemanager.PERFTEST" />
</intent-filter>
</activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.perftests.packagemanager"/>
</manifest>

View File

@@ -0,0 +1,88 @@
<?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 PackageManagerPerfTests 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" />
</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" />
</target_preparer>
<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" />
</metrics_collector>
</configuration>

View File

@@ -0,0 +1,314 @@
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
android_test_helper_app {
name: "QueriesAll0",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration0",
]
}
android_test_helper_app {
name: "QueriesAll1",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration1",
]
}
android_test_helper_app {
name: "QueriesAll2",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration2",
]
}
android_test_helper_app {
name: "QueriesAll3",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration3",
]
}
android_test_helper_app {
name: "QueriesAll4",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration4",
]
}
android_test_helper_app {
name: "QueriesAll5",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration5",
]
}
android_test_helper_app {
name: "QueriesAll6",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration6",
]
}
android_test_helper_app {
name: "QueriesAll7",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration7",
]
}
android_test_helper_app {
name: "QueriesAll8",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration8",
]
}
android_test_helper_app {
name: "QueriesAll9",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration9",
]
}
android_test_helper_app {
name: "QueriesAll10",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration10",
]
}
android_test_helper_app {
name: "QueriesAll11",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration11",
]
}
android_test_helper_app {
name: "QueriesAll12",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration12",
]
}
android_test_helper_app {
name: "QueriesAll13",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration13",
]
}
android_test_helper_app {
name: "QueriesAll14",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration14",
]
}
android_test_helper_app {
name: "QueriesAll15",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration15",
]
}
android_test_helper_app {
name: "QueriesAll16",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration16",
]
}
android_test_helper_app {
name: "QueriesAll17",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration17",
]
}
android_test_helper_app {
name: "QueriesAll18",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration18",
]
}
android_test_helper_app {
name: "QueriesAll19",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration19",
]
}
android_test_helper_app {
name: "QueriesAll20",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration20",
]
}
android_test_helper_app {
name: "QueriesAll21",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration21",
]
}
android_test_helper_app {
name: "QueriesAll22",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration22",
]
}
android_test_helper_app {
name: "QueriesAll23",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration23",
]
}
android_test_helper_app {
name: "QueriesAll24",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration24",
]
}
android_test_helper_app {
name: "QueriesAll25",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration25",
]
}
android_test_helper_app {
name: "QueriesAll26",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration26",
]
}
android_test_helper_app {
name: "QueriesAll27",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration27",
]
}
android_test_helper_app {
name: "QueriesAll28",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration28",
]
}
android_test_helper_app {
name: "QueriesAll29",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration29",
]
}
android_test_helper_app {
name: "QueriesAll30",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration30",
]
}
android_test_helper_app {
name: "QueriesAll31",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration31",
]
}
android_test_helper_app {
name: "QueriesAll32",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration32",
]
}
android_test_helper_app {
name: "QueriesAll33",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration33",
]
}
android_test_helper_app {
name: "QueriesAll34",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration34",
]
}
android_test_helper_app {
name: "QueriesAll35",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration35",
]
}
android_test_helper_app {
name: "QueriesAll36",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration36",
]
}
android_test_helper_app {
name: "QueriesAll37",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration37",
]
}
android_test_helper_app {
name: "QueriesAll38",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration38",
]
}
android_test_helper_app {
name: "QueriesAll39",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration39",
]
}
android_test_helper_app {
name: "QueriesAll40",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration40",
]
}
android_test_helper_app {
name: "QueriesAll41",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration41",
]
}
android_test_helper_app {
name: "QueriesAll42",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration42",
]
}
android_test_helper_app {
name: "QueriesAll43",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration43",
]
}
android_test_helper_app {
name: "QueriesAll44",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration44",
]
}
android_test_helper_app {
name: "QueriesAll45",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration45",
]
}
android_test_helper_app {
name: "QueriesAll46",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration46",
]
}
android_test_helper_app {
name: "QueriesAll47",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration47",
]
}
android_test_helper_app {
name: "QueriesAll48",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration48",
]
}
android_test_helper_app {
name: "QueriesAll49",
aaptflags: [
"--rename-manifest-package com.android.perftests.appenumeration49",
]
}

View File

@@ -0,0 +1,81 @@
<?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.appenumeration">
<application android:hasCode="false" >
<activity android:name="android.perftests.utils.PerfTestActivity">
<intent-filter>
<action android:name="com.android.perftests.packagemanager.PERFTEST" />
</intent-filter>
</activity>
</application>
<queries>
<package android:name="com.android.perftests.appenumeration0" />
<package android:name="com.android.perftests.appenumeration1" />
<package android:name="com.android.perftests.appenumeration2" />
<package android:name="com.android.perftests.appenumeration3" />
<package android:name="com.android.perftests.appenumeration4" />
<package android:name="com.android.perftests.appenumeration5" />
<package android:name="com.android.perftests.appenumeration6" />
<package android:name="com.android.perftests.appenumeration7" />
<package android:name="com.android.perftests.appenumeration8" />
<package android:name="com.android.perftests.appenumeration9" />
<package android:name="com.android.perftests.appenumeration10" />
<package android:name="com.android.perftests.appenumeration11" />
<package android:name="com.android.perftests.appenumeration12" />
<package android:name="com.android.perftests.appenumeration13" />
<package android:name="com.android.perftests.appenumeration14" />
<package android:name="com.android.perftests.appenumeration15" />
<package android:name="com.android.perftests.appenumeration16" />
<package android:name="com.android.perftests.appenumeration17" />
<package android:name="com.android.perftests.appenumeration18" />
<package android:name="com.android.perftests.appenumeration19" />
<package android:name="com.android.perftests.appenumeration20" />
<package android:name="com.android.perftests.appenumeration21" />
<package android:name="com.android.perftests.appenumeration22" />
<package android:name="com.android.perftests.appenumeration23" />
<package android:name="com.android.perftests.appenumeration24" />
<package android:name="com.android.perftests.appenumeration25" />
<package android:name="com.android.perftests.appenumeration26" />
<package android:name="com.android.perftests.appenumeration27" />
<package android:name="com.android.perftests.appenumeration28" />
<package android:name="com.android.perftests.appenumeration29" />
<package android:name="com.android.perftests.appenumeration30" />
<package android:name="com.android.perftests.appenumeration31" />
<package android:name="com.android.perftests.appenumeration32" />
<package android:name="com.android.perftests.appenumeration33" />
<package android:name="com.android.perftests.appenumeration34" />
<package android:name="com.android.perftests.appenumeration35" />
<package android:name="com.android.perftests.appenumeration36" />
<package android:name="com.android.perftests.appenumeration37" />
<package android:name="com.android.perftests.appenumeration38" />
<package android:name="com.android.perftests.appenumeration39" />
<package android:name="com.android.perftests.appenumeration40" />
<package android:name="com.android.perftests.appenumeration41" />
<package android:name="com.android.perftests.appenumeration42" />
<package android:name="com.android.perftests.appenumeration43" />
<package android:name="com.android.perftests.appenumeration44" />
<package android:name="com.android.perftests.appenumeration45" />
<package android:name="com.android.perftests.appenumeration46" />
<package android:name="com.android.perftests.appenumeration47" />
<package android:name="com.android.perftests.appenumeration48" />
<package android:name="com.android.perftests.appenumeration49" />
</queries>
</manifest>

View File

@@ -0,0 +1,195 @@
/*
* 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 libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class PackageManagerPerfTest {
private static final String PERMISSION_NAME_EXISTS =
"com.android.perftests.packagemanager.TestPermission";
private static final String PERMISSION_NAME_DOESNT_EXIST =
"com.android.perftests.packagemanager.TestBadPermission";
private static final ComponentName TEST_ACTIVITY =
new ComponentName("com.android.perftests.packagemanager",
"android.perftests.utils.PerfTestActivity");
@Rule
public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Rule
public final PlatformCompatChangeRule mPlatformCompatChangeRule =
new PlatformCompatChangeRule();
public PackageManagerPerfTest() throws PackageManager.NameNotFoundException {
final Context context = InstrumentationRegistry.getInstrumentation().getContext();
}
@Before
public void setup() {
PackageManager.disableApplicationInfoCache();
PackageManager.disablePackageInfoCache();
}
@Test
@DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testCheckPermissionExists() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm =
InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
final String packageName = TEST_ACTIVITY.getPackageName();
while (state.keepRunning()) {
int ret = pm.checkPermission(PERMISSION_NAME_EXISTS, packageName);
}
}
@Test
@EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testCheckPermissionExistsWithFiltering() {
testCheckPermissionExists();
}
@Test
@DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testCheckPermissionDoesntExist() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm =
InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
final String packageName = TEST_ACTIVITY.getPackageName();
while (state.keepRunning()) {
int ret = pm.checkPermission(PERMISSION_NAME_DOESNT_EXIST, packageName);
}
}
@Test
@EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testCheckPermissionDoesntExistWithFiltering() {
testCheckPermissionDoesntExist();
}
@Test
@DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testQueryIntentActivities() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm =
InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
final Intent intent = new Intent("com.android.perftests.core.PERFTEST");
while (state.keepRunning()) {
pm.queryIntentActivities(intent, 0);
}
}
@Test
@EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testQueryIntentActivitiesWithFiltering() {
testQueryIntentActivities();
}
@Test
@DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testGetPackageInfo() throws Exception {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm =
InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
while (state.keepRunning()) {
pm.getPackageInfo(TEST_ACTIVITY.getPackageName(), 0);
}
}
@Test
@EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testGetPackageInfoWithFiltering() throws Exception {
testGetPackageInfo();
}
@Test
@DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testGetApplicationInfo() throws Exception {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm =
InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
while (state.keepRunning()) {
pm.getApplicationInfo(TEST_ACTIVITY.getPackageName(), 0);
}
}
@Test
@EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testGetApplicationInfoWithFiltering() throws Exception {
testGetApplicationInfo();
}
@Test
@DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testGetActivityInfo() throws Exception {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm =
InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
while (state.keepRunning()) {
pm.getActivityInfo(TEST_ACTIVITY, 0);
}
}
@Test
@EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testGetActivityInfoWithFiltering() throws Exception {
testGetActivityInfo();
}
@Test
@DisableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testGetInstalledPackages() throws Exception {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final PackageManager pm =
InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
while (state.keepRunning()) {
pm.getInstalledPackages(0);
}
}
@Test
@EnableCompatChanges(PackageManager.FILTER_APPLICATION_QUERY)
public void testGetInstalledPackagesWithFiltering() throws Exception {
testGetInstalledPackages();
}
}

View File

@@ -1,5 +1,5 @@
set -e
make TextClassifierPerfTests perf-setup.sh
build/soong/soong_ui.bash --make-mode TextClassifierPerfTests perf-setup.sh
adb install ${OUT}/testcases/TextClassifierPerfTests/arm64/TextClassifierPerfTests.apk
adb shell cmd package compile -m speed -f com.android.perftests.textclassifier
adb push ${OUT}/obj/EXECUTABLES/perf-setup.sh_intermediates/perf-setup.sh /data/local/tmp/

View File

@@ -18,57 +18,82 @@ package android.view.textclassifier;
import android.content.Context;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.SettingsHelper;
import android.provider.Settings;
import android.provider.DeviceConfig;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
@LargeTest
public class TextClassificationManagerPerfTest {
private static final String WRITE_DEVICE_CONFIG_PERMISSION =
"android.permission.WRITE_DEVICE_CONFIG";
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private String mOriginalSystemTextclassifierStatus;
@BeforeClass
public static void setUpClass() {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(
WRITE_DEVICE_CONFIG_PERMISSION);
}
@AfterClass
public static void tearDownClass() {
InstrumentationRegistry
.getInstrumentation()
.getUiAutomation()
.dropShellPermissionIdentity();
}
@Before
public void setUp() {
// Saves config original value.
mOriginalSystemTextclassifierStatus = DeviceConfig.getProperty(
DeviceConfig.NAMESPACE_TEXTCLASSIFIER, "system_textclassifier_enabled");
}
@After
public void tearDown() {
SettingsHelper.delete(
SettingsHelper.NAMESPACE_GLOBAL, Settings.Global.TEXT_CLASSIFIER_CONSTANTS);
// Restores config original value.
enableSystemTextclassifier(mOriginalSystemTextclassifierStatus);
}
@Test
public void testGetTextClassifier_systemTextClassifierDisabled() {
Context context = InstrumentationRegistry.getTargetContext();
SettingsHelper.set(
SettingsHelper.NAMESPACE_GLOBAL,
Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
"system_textclassifier_enabled=false");
enableSystemTextclassifier(String.valueOf(false));
TextClassificationManager textClassificationManager =
context.getSystemService(TextClassificationManager.class);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
textClassificationManager.getTextClassifier();
textClassificationManager.invalidateForTesting();
}
}
@Test
public void testGetTextClassifier_systemTextClassifierEnabled() {
Context context = InstrumentationRegistry.getTargetContext();
SettingsHelper.set(
SettingsHelper.NAMESPACE_GLOBAL,
Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
"system_textclassifier_enabled=true");
enableSystemTextclassifier(String.valueOf(true));
TextClassificationManager textClassificationManager =
context.getSystemService(TextClassificationManager.class);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
textClassificationManager.getTextClassifier();
textClassificationManager.invalidateForTesting();
}
}
private void enableSystemTextclassifier(String enabled) {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
"system_textclassifier_enabled", enabled, /* makeDefault */ false);
}
}

View File

@@ -16,12 +16,22 @@
package android.perftests.utils;
import android.annotation.IntDef;
import android.app.Activity;
import android.app.Instrumentation;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.util.ArrayUtils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@@ -53,6 +63,47 @@ import java.util.concurrent.TimeUnit;
public final class ManualBenchmarkState {
private static final String TAG = ManualBenchmarkState.class.getSimpleName();
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface StatsReport {
int FLAG_MEDIAN = 0x00000001;
int FLAG_MEAN = 0x00000002;
int FLAG_MIN = 0x00000004;
int FLAG_MAX = 0x00000008;
int FLAG_STDDEV = 0x00000010;
int FLAG_COEFFICIENT_VAR = 0x00000020;
int FLAG_ITERATION = 0x00000040;
@Retention(RetentionPolicy.RUNTIME)
@IntDef(value = {
FLAG_MEDIAN,
FLAG_MEAN,
FLAG_MIN,
FLAG_MAX,
FLAG_STDDEV,
FLAG_COEFFICIENT_VAR,
FLAG_ITERATION,
})
@interface Flag {}
/** Defines which type of statistics should output. */
@Flag int flags() default -1;
/** An array with value 0~100 to provide the percentiles. */
int[] percentiles() default {};
}
/** 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);
}
/** It means the entire {@link StatsReport} is not given. */
private static final int DEFAULT_STATS_REPORT = -2;
// TODO: Tune these values.
// warm-up for duration
private static final long WARMUP_DURATION_NS = TimeUnit.SECONDS.toNanos(5);
@@ -67,24 +118,68 @@ public final class ManualBenchmarkState {
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.
private long mWarmupDurationNs = WARMUP_DURATION_NS;
private long mTargetTestDurationNs = TARGET_TEST_DURATION_NS;
private long mWarmupStartTime = 0;
private int mWarmupIterations = 0;
private int mMaxIterations = 0;
/**
* Additinal 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;
// Individual duration in nano seconds.
private ArrayList<Long> mResults = new ArrayList<>();
/** @see #addExtraResult(String, long) */
private ArrayMap<String, ArrayList<Long>> mExtraResults;
private final List<Long> mTmpDurations = Arrays.asList(0L);
// 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;
private int mStatsReportFlags =
StatsReport.FLAG_MEDIAN | StatsReport.FLAG_MEAN | StatsReport.FLAG_STDDEV;
private int[] mStatsReportPercentiles = {90 , 95};
private boolean shouldReport(int statsReportFlag) {
return (mStatsReportFlags & statsReportFlag) != 0;
}
void configure(ManualBenchmarkTest testAnnotation) {
if (testAnnotation == null) {
return;
}
final long warmupDurationNs = testAnnotation.warmupDurationNs();
if (warmupDurationNs >= 0) {
mWarmupDurationNs = warmupDurationNs;
}
final long targetTestDurationNs = testAnnotation.targetTestDurationNs();
if (targetTestDurationNs >= 0) {
mTargetTestDurationNs = targetTestDurationNs;
}
final StatsReport statsReport = testAnnotation.statsReport();
if (statsReport != null && statsReport.flags() != DEFAULT_STATS_REPORT) {
mStatsReportFlags = statsReport.flags();
mStatsReportPercentiles = statsReport.percentiles();
}
}
private void beginBenchmark(long warmupDuration, int iterations) {
mMaxIterations = (int) (TARGET_TEST_DURATION_NS / (warmupDuration / iterations));
mMaxIterations = (int) (mTargetTestDurationNs / (warmupDuration / iterations));
mMaxIterations = Math.min(MAX_TEST_ITERATIONS,
Math.max(mMaxIterations, MIN_TEST_ITERATIONS));
mState = RUNNING;
@@ -99,29 +194,58 @@ public final class ManualBenchmarkState {
if (duration < 0) {
throw new RuntimeException("duration is negative: " + duration);
}
mTmpDurations.set(0, duration);
return keepRunning(mTmpDurations);
}
/**
* Similar to the {@link #keepRunning(long)} but accepts a list of durations
*/
public boolean keepRunning(List<Long> durations) {
switch (mState) {
case NOT_STARTED:
mState = WARMUP;
mWarmupStartTime = System.nanoTime();
return true;
case WARMUP: {
if (ArrayUtils.isEmpty(durations)) {
return true;
}
final long timeSinceStartingWarmup = System.nanoTime() - mWarmupStartTime;
++mWarmupIterations;
mWarmupIterations += durations.size();
if (mWarmupIterations >= WARMUP_MIN_ITERATIONS
&& timeSinceStartingWarmup >= WARMUP_DURATION_NS) {
&& timeSinceStartingWarmup >= mWarmupDurationNs) {
beginBenchmark(timeSinceStartingWarmup, mWarmupIterations);
}
return true;
}
case RUNNING: {
mResults.add(duration);
if (ArrayUtils.isEmpty(durations)) {
return true;
}
mResults.addAll(durations);
final boolean keepRunning = mResults.size() < mMaxIterations;
if (!keepRunning) {
mStats = new Stats(mResults);
if (mMaxCustomizedIterations > 0 && mCustomizedIterationListener != null) {
mState = RUNNING_CUSTOMIZED;
mCustomizedIterationListener.onStart(mCustomizedIterations);
return true;
}
mState = FINISHED;
}
return keepRunning;
}
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:
@@ -129,31 +253,112 @@ public final class ManualBenchmarkState {
}
}
private String summaryLine() {
final StringBuilder sb = new StringBuilder();
sb.append("Summary: ");
sb.append("median=").append(mStats.getMedian()).append("ns, ");
sb.append("mean=").append(mStats.getMean()).append("ns, ");
sb.append("min=").append(mStats.getMin()).append("ns, ");
sb.append("max=").append(mStats.getMax()).append("ns, ");
sb.append("sigma=").append(mStats.getStandardDeviation()).append(", ");
sb.append("iteration=").append(mResults.size()).append(", ");
sb.append("values=").append(mResults.toString());
/**
* @return {@code true} if the benchmark is in warmup state. It can be used to skip the
* operations or measurements that are unnecessary while the test isn't running the
* actual benchmark.
*/
public boolean isWarmingUp() {
return mState == WARMUP;
}
/**
* 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;
}
/**
* Adds additional result while this benchmark isn't warming up or running in customized state.
* It is used when a sequence of operations is executed consecutively, the duration of each
* operation can also be recorded.
*/
public void addExtraResult(String key, long duration) {
if (isWarmingUp() || mState == RUNNING_CUSTOMIZED) {
return;
}
if (mExtraResults == null) {
mExtraResults = new ArrayMap<>();
}
mExtraResults.computeIfAbsent(key, k -> new ArrayList<>()).add(duration);
}
private static String summaryLine(String key, Stats stats, ArrayList<Long> results) {
final StringBuilder sb = new StringBuilder(key);
sb.append(" Summary: ");
sb.append("median=").append(stats.getMedian()).append("ns, ");
sb.append("mean=").append(stats.getMean()).append("ns, ");
sb.append("min=").append(stats.getMin()).append("ns, ");
sb.append("max=").append(stats.getMax()).append("ns, ");
sb.append("sigma=").append(stats.getStandardDeviation()).append(", ");
sb.append("iteration=").append(results.size()).append(", ");
sb.append("values=");
if (results.size() > 100) {
sb.append(results.subList(0, 100)).append(" ...");
} else {
sb.append(results);
}
return sb.toString();
}
private void fillStatus(Bundle status, String key, Stats stats) {
if (shouldReport(StatsReport.FLAG_ITERATION)) {
status.putLong(key + "_iteration", stats.getSize());
}
if (shouldReport(StatsReport.FLAG_MEDIAN)) {
status.putLong(key + "_median", stats.getMedian());
}
if (shouldReport(StatsReport.FLAG_MEAN)) {
status.putLong(key + "_mean", Math.round(stats.getMean()));
}
if (shouldReport(StatsReport.FLAG_MIN)) {
status.putLong(key + "_min", stats.getMin());
}
if (shouldReport(StatsReport.FLAG_MAX)) {
status.putLong(key + "_max", stats.getMax());
}
if (mStatsReportPercentiles != null) {
for (int percentile : mStatsReportPercentiles) {
status.putLong(key + "_percentile" + percentile, stats.getPercentile(percentile));
}
}
if (shouldReport(StatsReport.FLAG_STDDEV)) {
status.putLong(key + "_stddev", Math.round(stats.getStandardDeviation()));
}
if (shouldReport(StatsReport.FLAG_COEFFICIENT_VAR)) {
status.putLong(key + "_cv",
Math.round((100 * stats.getStandardDeviation() / stats.getMean())));
}
}
public void sendFullStatusReport(Instrumentation instrumentation, String key) {
if (mState != FINISHED) {
throw new IllegalStateException("The benchmark hasn't finished");
}
Log.i(TAG, key + summaryLine());
Log.i(TAG, summaryLine(key, mStats, mResults));
final Bundle status = new Bundle();
status.putLong(key + "_median", mStats.getMedian());
status.putLong(key + "_mean", (long) mStats.getMean());
status.putLong(key + "_percentile90", mStats.getPercentile90());
status.putLong(key + "_percentile95", mStats.getPercentile95());
status.putLong(key + "_stddev", (long) mStats.getStandardDeviation());
fillStatus(status, key, mStats);
if (mExtraResults != null) {
for (int i = 0; i < mExtraResults.size(); i++) {
final String subKey = key + "_" + mExtraResults.keyAt(i);
final ArrayList<Long> results = mExtraResults.valueAt(i);
final Stats stats = new Stats(results);
Log.i(TAG, summaryLine(subKey, stats, results));
fillStatus(status, subKey, stats);
}
}
instrumentation.sendStatus(Activity.RESULT_OK, status);
}
}
/** The annotation to customize the test, e.g. the duration of warm-up and target test. */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ManualBenchmarkTest {
long warmupDurationNs() default -1;
long targetTestDurationNs() default -1;
StatsReport statsReport() default @StatsReport(flags = DEFAULT_STATS_REPORT);
}
}

View File

@@ -16,7 +16,7 @@
package android.perftests.utils;
import androidx.test.InstrumentationRegistry;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -59,15 +59,15 @@ public class PerfManualStatusReporter implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
mState.configure(description.getAnnotation(ManualBenchmarkState.ManualBenchmarkTest.class));
return new Statement() {
@Override
public void evaluate() throws Throwable {
base.evaluate();
mState.sendFullStatusReport(InstrumentationRegistry.getInstrumentation(),
description.getMethodName());
mState.sendFullStatusReport(getInstrumentation(), description.getMethodName());
}
};
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.perftests.utils;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.WindowManager;
/**
* A simple activity used for testing, e.g. performance of activity switching, or as a base
* container of testing view.
*/
public class PerfTestActivity extends Activity {
public static final String INTENT_EXTRA_KEEP_SCREEN_ON = "keep_screen_on";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getBooleanExtra(INTENT_EXTRA_KEEP_SCREEN_ON, false)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
public static Intent createLaunchIntent(Context context) {
final Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClass(context, PerfTestActivity.class);
return intent;
}
}

View File

@@ -16,23 +16,27 @@
package android.perftests.utils;
import android.annotation.IntRange;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Stats {
private long mMedian, mMin, mMax, mPercentile90, mPercentile95;
private long mMedian, mMin, mMax;
private double mMean, mStandardDeviation;
private final List<Long> mValues;
/* Calculate stats in constructor. */
public Stats(List<Long> values) {
// make a copy since we're modifying it
values = new ArrayList<>(values);
final int size = values.size();
if (size < 2) {
throw new IllegalArgumentException("At least two results are necessary.");
}
// Make a copy since we're modifying it.
mValues = values = new ArrayList<>(values);
Collections.sort(values);
mMin = values.get(0);
@@ -40,8 +44,6 @@ public class Stats {
mMedian = size % 2 == 0 ? (values.get(size / 2) + values.get(size / 2 - 1)) / 2 :
values.get(size / 2);
mPercentile90 = getPercentile(values, 90);
mPercentile95 = getPercentile(values, 95);
for (int i = 0; i < size; ++i) {
long result = values.get(i);
@@ -56,6 +58,10 @@ public class Stats {
mStandardDeviation = Math.sqrt(mStandardDeviation / (double) (size - 1));
}
public int getSize() {
return mValues.size();
}
public double getMean() {
return mMean;
}
@@ -76,12 +82,8 @@ public class Stats {
return mStandardDeviation;
}
public long getPercentile90() {
return mPercentile90;
}
public long getPercentile95() {
return mPercentile95;
public long getPercentile(@IntRange(from = 0, to = 100) int percentile) {
return getPercentile(mValues, percentile);
}
private static long getPercentile(List<Long> values, int percentile) {

View File

@@ -1,30 +0,0 @@
/*
* 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.perftests.utils;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
public class StubActivity extends Activity {
public static Intent createLaunchIntent(Context context) {
final Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClass(context, StubActivity.class);
return intent;
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.perftests.utils;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.PackageInstaller;
import android.content.res.Resources;
import android.util.Log;
import org.junit.Assert;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Installs packages included within the assets directory.
*/
public class TestPackageInstaller {
private static final String LOG_TAG = "TestPackageInstaller";
private static final String BROADCAST_ACTION =
"com.android.perftests.core.ACTION_INSTALL_COMMIT";
private final Context mContext;
public TestPackageInstaller(Context context) {
mContext = context;
}
/**
* Installs an APK located at the specified path in the assets directory.
**/
public InstalledPackage installPackage(String resourceName) throws IOException,
InterruptedException {
Log.d(LOG_TAG, "Installing resource APK '" + resourceName + "'");
LocalBroadcastReceiver intentSender = new LocalBroadcastReceiver(mContext);
// Initialize the package install session.
PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setInstallAsInstantApp(false);
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
// Copy the apk to the install session.
try (OutputStream os = session.openWrite("TestPackageInstaller", 0, -1);
InputStream is = mContext.getResources().getAssets().openNonAsset(resourceName)) {
if (is == null) {
throw new IOException("Resource " + resourceName + " not found");
}
byte[] buffer = new byte[4096];
int n;
while ((n = is.read(buffer)) >= 0) {
os.write(buffer, 0, n);
}
}
session.commit(intentSender.getIntentSender(sessionId));
session.close();
// Retrieve the results of the installation.
Intent intent = intentSender.getIntentSenderResult();
int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
Assert.assertEquals(PackageInstaller.STATUS_SUCCESS, status);
String packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME);
return new InstalledPackage(sessionId, packageName);
}
public class InstalledPackage {
private int mSessionId;
private String mPackageName;
private InstalledPackage(int sessionId, String packageName) {
mSessionId = sessionId;
mPackageName = packageName;
}
public String getPackageName() {
return mPackageName;
}
public void uninstall() throws Exception {
Log.d(LOG_TAG, "Uninstalling package '" + mPackageName + "'");
LocalBroadcastReceiver intentSender = new LocalBroadcastReceiver(mContext);
PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
packageInstaller.uninstall(mPackageName, intentSender.getIntentSender(mSessionId));
int status = intentSender.getIntentSenderResult()
.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
Assert.assertEquals(PackageInstaller.STATUS_SUCCESS, status);
}
}
private class LocalBroadcastReceiver extends BroadcastReceiver {
private final BlockingQueue<Intent> mIntentSenderResults = new LinkedBlockingQueue<>();
private final Context mContext;
private LocalBroadcastReceiver(Context context) {
mContext = context;
}
@Override
public void onReceive(Context context, Intent intent) {
mIntentSenderResults.add(intent);
}
IntentSender getIntentSender(int sessionId) {
String action = BROADCAST_ACTION + "." + sessionId;
IntentFilter filter = new IntentFilter(action);
mContext.registerReceiver(this, filter);
Intent intent = new Intent(action);
PendingIntent pending = PendingIntent.getBroadcast(mContext, sessionId, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
return pending.getIntentSender();
}
Intent getIntentSenderResult() throws InterruptedException {
return mIntentSenderResults.take();
}
}
}

View File

@@ -0,0 +1,268 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.perftests.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
/**
* Utility to get the slice of tracing_mark_write S,F,B,E (Async: start, finish, Sync: begin, end).
* Use {@link #visit(String)} to process the trace in text form. The filtered results can be
* obtained by {@link #forAllSlices(BiConsumer)}.
*
* @see android.os.Trace
*/
public class TraceMarkParser {
/** All slices by the name of {@link TraceMarkLine}. */
private final Map<String, List<TraceMarkSlice>> mSlicesMap = new HashMap<>();
/** The nested depth of each task-pid. */
private final Map<String, Integer> mDepthMap = new HashMap<>();
/** The start trace lines that haven't matched the corresponding end. */
private final Map<String, TraceMarkLine> mPendingStarts = new HashMap<>();
private final Predicate<TraceMarkLine> mTraceLineFilter;
private static final long MICROS_PER_SECOND = 1000L * 1000L;
public TraceMarkParser(Predicate<TraceMarkLine> traceLineFilter) {
mTraceLineFilter = traceLineFilter;
}
/** Only accept the trace event with the given names. */
public TraceMarkParser(String... traceNames) {
this(line -> {
for (String name : traceNames) {
if (name.equals(line.name)) {
return true;
}
}
return false;
});
}
/** Computes {@link TraceMarkSlice} by the given trace line. */
public void visit(String textTraceLine) {
final TraceMarkLine line = TraceMarkLine.parse(textTraceLine);
if (line == null) {
return;
}
if (line.isAsync) {
// Async-trace contains name in the start and finish event.
if (mTraceLineFilter.test(line)) {
if (line.isBegin) {
mPendingStarts.put(line.name, line);
} else {
final TraceMarkLine start = mPendingStarts.remove(line.name);
if (start != null) {
addSlice(start, line);
}
}
}
return;
}
int depth = 1;
if (line.isBegin) {
final Integer existingDepth = mDepthMap.putIfAbsent(line.taskPid, 1);
if (existingDepth != null) {
mDepthMap.put(line.taskPid, depth = existingDepth + 1);
}
// Sync-trace only contains name in the begin event.
if (mTraceLineFilter.test(line)) {
mPendingStarts.put(getSyncPendingStartKey(line, depth), line);
}
} else {
final Integer existingDepth = mDepthMap.get(line.taskPid);
if (existingDepth != null) {
depth = existingDepth;
mDepthMap.put(line.taskPid, existingDepth - 1);
}
final TraceMarkLine begin = mPendingStarts.remove(getSyncPendingStartKey(line, depth));
if (begin != null) {
addSlice(begin, line);
}
}
}
private static String getSyncPendingStartKey(TraceMarkLine line, int depth) {
return line.taskPid + "@" + depth;
}
private void addSlice(TraceMarkLine begin, TraceMarkLine end) {
mSlicesMap.computeIfAbsent(
begin.name, k -> new ArrayList<>()).add(new TraceMarkSlice(begin, end));
}
public void forAllSlices(BiConsumer<String, List<TraceMarkSlice>> consumer) {
for (Map.Entry<String, List<TraceMarkSlice>> entry : mSlicesMap.entrySet()) {
consumer.accept(entry.getKey(), entry.getValue());
}
}
public void reset() {
mSlicesMap.clear();
mDepthMap.clear();
mPendingStarts.clear();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
forAllSlices((key, slices) -> {
double totalMs = 0;
for (TraceMarkSlice s : slices) {
totalMs += s.getDurationInSeconds() * 1000;
}
sb.append(key).append(" count=").append(slices.size()).append(" avg=")
.append(totalMs / slices.size()).append("ms\n");
});
if (!mPendingStarts.isEmpty()) {
sb.append("[Warning] Unresolved events:").append(mPendingStarts).append("\n");
}
return sb.toString();
}
static double microsecondToSeconds(long ms) {
return (ms * 1.0d) / MICROS_PER_SECOND;
}
public static class TraceMarkSlice {
public final TraceMarkLine begin;
public final TraceMarkLine end;
TraceMarkSlice(TraceMarkLine begin, TraceMarkLine end) {
this.begin = begin;
this.end = end;
}
public double getDurationInSeconds() {
return microsecondToSeconds(end.timestamp - begin.timestamp);
}
public long getDurationInMicroseconds() {
return end.timestamp - begin.timestamp;
}
}
// taskPid timestamp name
// # Async:
// Binder:129_F-349 ( 1296) [003] ...1 12.2776: tracing_mark_write: S|1296|launching: a.test|0
// android.anim-135 ( 1296) [005] ...1 12.3361: tracing_mark_write: F|1296|launching: a.test|0
// # Normal:
// Binder:129_6-315 ( 1296) [007] ...1 97.4576: tracing_mark_write: B|1296|relayoutWindow: xxx
// ... there may have other nested begin/end
// Binder:129_6-315 ( 1296) [007] ...1 97.4580: tracing_mark_write: E|1296
public static class TraceMarkLine {
static final String EVENT_KEYWORD = ": tracing_mark_write: ";
static final char ASYNC_START = 'S';
static final char ASYNC_FINISH = 'F';
static final char SYNC_BEGIN = 'B';
static final char SYNC_END = 'E';
public final String taskPid;
public final long timestamp; // in microseconds
public final String name;
public final boolean isAsync;
public final boolean isBegin;
TraceMarkLine(String rawLine, int typePos, int type) throws IllegalArgumentException {
taskPid = rawLine.substring(0, rawLine.indexOf('(')).trim();
final int timeEnd = rawLine.indexOf(':', taskPid.length());
if (timeEnd < 0) {
throw new IllegalArgumentException("Timestamp end not found");
}
final int timeBegin = rawLine.lastIndexOf(' ', timeEnd);
if (timeBegin < 0) {
throw new IllegalArgumentException("Timestamp start not found");
}
timestamp = parseMicroseconds(rawLine.substring(timeBegin, timeEnd));
isAsync = type == ASYNC_START || type == ASYNC_FINISH;
isBegin = type == ASYNC_START || type == SYNC_BEGIN;
if (!isAsync && !isBegin) {
name = "";
} else {
// Get the position of the second '|' from "S|1234|name".
final int nameBegin = rawLine.indexOf('|', typePos + 2) + 1;
if (nameBegin == 0) {
throw new IllegalArgumentException("Name begin not found");
}
if (isAsync) {
// Get the name from "S|1234|name|0".
name = rawLine.substring(nameBegin, rawLine.lastIndexOf('|'));
} else {
name = rawLine.substring(nameBegin);
}
}
}
static TraceMarkLine parse(String rawLine) {
final int eventPos = rawLine.indexOf(EVENT_KEYWORD);
if (eventPos < 0) {
return null;
}
final int typePos = eventPos + EVENT_KEYWORD.length();
if (typePos >= rawLine.length()) {
return null;
}
final int type = rawLine.charAt(typePos);
if (type != ASYNC_START && type != ASYNC_FINISH
&& type != SYNC_BEGIN && type != SYNC_END) {
return null;
}
try {
return new TraceMarkLine(rawLine, typePos, type);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return null;
}
/**
* Parse the timestamp from atrace output, the format will be like:
* 84962.920719 where the decimal part will be always exactly 6 digits.
* ^^^^^ ^^^^^^
* | |
* sec microsec
*/
static long parseMicroseconds(String line) {
int end = line.length();
long t = 0;
for (int i = 0; i < end; i++) {
char c = line.charAt(i);
if (c >= '0' && c <= '9') {
t = t * 10 + (c - '0');
}
}
return t;
}
@Override
public String toString() {
return "TraceMarkLine{pid=" + taskPid + " time="
+ microsecondToSeconds(timestamp) + " name=" + name
+ " async=" + isAsync + " begin=" + isBegin + "}";
}
}
}

View File

@@ -0,0 +1,26 @@
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
android_test {
name: "WmPerfTests",
srcs: ["src/**/*.java"],
static_libs: [
"androidx.test.rules",
"androidx.annotation_annotation",
"apct-perftests-utils",
],
test_suites: ["device-tests"],
platform_apis: true,
certificate: "platform",
}

View File

@@ -0,0 +1,32 @@
<?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.wm">
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.PerfTestActivity">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
</activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.perftests.wm"/>
</manifest>

View File

@@ -0,0 +1,33 @@
<?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 WmPerfTests 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="WmPerfTests.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.perftests.wm" />
<option name="hidden-api-checks" value="false"/>
</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" />
</metrics_collector>
</configuration>

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.wm;
import static android.perftests.utils.ManualBenchmarkState.StatsReport;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
import android.perftests.utils.PerfManualStatusReporter;
import android.perftests.utils.TraceMarkParser;
import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
import android.util.Log;
import androidx.test.filters.LargeTest;
import androidx.test.runner.lifecycle.Stage;
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.concurrent.TimeUnit;
/** Measure the performance of internal methods in window manager service by trace tag. */
@LargeTest
public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
private static final String TAG = InternalWindowOperationPerfTest.class.getSimpleName();
@Rule
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@Rule
public final PerfTestActivityRule mActivityRule = new PerfTestActivityRule();
private final TraceMarkParser mTraceMarkParser = new TraceMarkParser(
"applyPostLayoutPolicy",
"applySurfaceChanges",
"AppTransitionReady",
"closeSurfaceTransactiom",
"openSurfaceTransaction",
"performLayout",
"performSurfacePlacement",
"prepareSurfaces",
"updateInputWindows",
"WSA#startAnimation",
"activityIdle",
"activityPaused",
"activityStopped",
"activityDestroyed",
"finishActivity",
"startActivityInner");
@Test
@ManualBenchmarkTest(
targetTestDurationNs = 20 * TIME_1_S_IN_NS,
statsReport = @StatsReport(
flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
| StatsReport.FLAG_MAX | StatsReport.FLAG_COEFFICIENT_VAR))
public void testLaunchAndFinishActivity() throws Throwable {
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
long measuredTimeNs = 0;
boolean isTraceStarted = false;
while (state.keepRunning(measuredTimeNs)) {
if (!isTraceStarted && !state.isWarmingUp()) {
startAsyncAtrace();
isTraceStarted = true;
}
final long startTime = SystemClock.elapsedRealtimeNanos();
mActivityRule.launchActivity();
mActivityRule.finishActivity();
mActivityRule.waitForIdleSync(Stage.DESTROYED);
measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
}
stopAsyncAtrace();
mTraceMarkParser.forAllSlices((key, slices) -> {
for (TraceMarkSlice slice : slices) {
state.addExtraResult(key, (long) (slice.getDurationInSeconds() * NANOS_PER_S));
}
});
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);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
mTraceMarkParser.visit(line);
}
}
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.wm;
import static android.perftests.utils.ManualBenchmarkState.StatsReport;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.hamcrest.core.AnyOf.anyOf;
import static org.hamcrest.core.Is.is;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
import android.perftests.utils.PerfManualStatusReporter;
import android.util.Pair;
import android.view.IRecentsAnimationController;
import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationTarget;
import androidx.test.filters.LargeTest;
import androidx.test.runner.lifecycle.Stage;
import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@RunWith(Parameterized.class)
@LargeTest
public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
private static Intent sRecentsIntent;
@Rule
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@Rule
public final PerfTestActivityRule mActivityRule =
new PerfTestActivityRule(true /* launchActivity */);
private long mMeasuredTimeNs;
/**
* Used to skip each test method if there is error. It cannot be raised in static setup because
* that will break the amount of target method count.
*/
private static Exception sSetUpClassException;
@Parameterized.Parameter(0)
public int intervalBetweenOperations;
@Parameterized.Parameters(name = "interval{0}ms")
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][] {
{ 0 },
{ 100 },
{ 300 },
});
}
@BeforeClass
public static void setUpClass() {
// Get the permission to invoke startRecentsActivity.
sUiAutomation.adoptShellPermissionIdentity();
final Context context = getInstrumentation().getContext();
final PackageManager pm = context.getPackageManager();
final ComponentName defaultHome = pm.getHomeActivities(new ArrayList<>());
try {
final ComponentName recentsComponent =
ComponentName.unflattenFromString(context.getResources().getString(
com.android.internal.R.string.config_recentsComponentName));
final int enabledState = pm.getComponentEnabledSetting(recentsComponent);
Assume.assumeThat(enabledState, anyOf(
is(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT),
is(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)));
final boolean homeIsRecents =
recentsComponent.getPackageName().equals(defaultHome.getPackageName());
sRecentsIntent =
new Intent().setComponent(homeIsRecents ? defaultHome : recentsComponent);
} catch (Exception e) {
sSetUpClassException = e;
}
}
@AfterClass
public static void tearDownClass() {
sSetUpClassException = null;
try {
// Recents activity may stop app switches. Restore the state to avoid affecting
// the next test.
ActivityManager.resumeAppSwitches();
} catch (RemoteException ignored) {
}
sUiAutomation.dropShellPermissionIdentity();
}
@Before
public void setUp() {
Assume.assumeNoException(sSetUpClassException);
}
/** Simulate the timing of touch. */
private void makeInterval() {
SystemClock.sleep(intervalBetweenOperations);
}
/**
* <pre>
* Steps:
* (1) Start recents activity (only make it visible).
* (2) Finish animation, take turns to execute (a), (b).
* (a) Move recents activity to top.
* ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_TOP})
* Move test app to top by startActivityFromRecents.
* (b) Cancel (it is similar to swipe a little distance and give up to enter recents).
* ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_ORIGINAL_POSITION})
* (3) Loop (1).
* </pre>
*/
@Test
@ManualBenchmarkTest(
warmupDurationNs = TIME_1_S_IN_NS,
targetTestDurationNs = TIME_5_S_IN_NS,
statsReport = @StatsReport(flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
| StatsReport.FLAG_COEFFICIENT_VAR))
public void testRecentsAnimation() throws Throwable {
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final IActivityTaskManager atm = ActivityTaskManager.getService();
final ArrayList<Pair<String, Boolean>> finishCases = new ArrayList<>();
// Real launch the recents activity.
finishCases.add(new Pair<>("finishMoveToTop", true));
// Return to the original top.
finishCases.add(new Pair<>("finishCancel", false));
// Ensure startRecentsActivity won't be called before finishing the animation.
final Semaphore recentsSemaphore = new Semaphore(1);
final int testActivityTaskId = mActivityRule.getActivity().getTaskId();
final IRecentsAnimationRunner.Stub anim = new IRecentsAnimationRunner.Stub() {
int mIteration;
@Override
public void onAnimationStart(IRecentsAnimationController controller,
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
Rect homeContentInsets, Rect minimizedHomeBounds) throws RemoteException {
final Pair<String, Boolean> finishCase = finishCases.get(mIteration++ % 2);
final boolean moveRecentsToTop = finishCase.second;
makeInterval();
long startTime = SystemClock.elapsedRealtimeNanos();
controller.finish(moveRecentsToTop, false /* sendUserLeaveHint */);
final long elapsedTimeNsOfFinish = SystemClock.elapsedRealtimeNanos() - startTime;
mMeasuredTimeNs += elapsedTimeNsOfFinish;
state.addExtraResult(finishCase.first, elapsedTimeNsOfFinish);
if (moveRecentsToTop) {
mActivityRule.waitForIdleSync(Stage.STOPPED);
startTime = SystemClock.elapsedRealtimeNanos();
atm.startActivityFromRecents(testActivityTaskId, null /* options */);
final long elapsedTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
mMeasuredTimeNs += elapsedTimeNs;
state.addExtraResult("startFromRecents", elapsedTimeNs);
mActivityRule.waitForIdleSync(Stage.RESUMED);
}
makeInterval();
recentsSemaphore.release();
}
@Override
public void onAnimationCanceled(TaskSnapshot taskSnapshot) throws RemoteException {
Assume.assumeNoException(
new AssertionError("onAnimationCanceled should not be called"));
}
@Override
public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException {
/* no-op */
}
};
recentsSemaphore.tryAcquire();
while (state.keepRunning(mMeasuredTimeNs)) {
mMeasuredTimeNs = 0;
final long startTime = SystemClock.elapsedRealtimeNanos();
atm.startRecentsActivity(sRecentsIntent, null /* unused */, anim);
final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime;
mMeasuredTimeNs += elapsedTimeNsOfStart;
state.addExtraResult("start", elapsedTimeNsOfStart);
// Ensure the animation callback is done.
Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
}
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.wm;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
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.util.MergedConfiguration;
import android.view.DisplayCutout;
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.widget.LinearLayout;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
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.function.IntSupplier;
@RunWith(Parameterized.class)
@LargeTest
public class RelayoutPerfTest extends WindowManagerPerfTestBase {
private int mIteration;
@Rule
public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Rule
public final ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
/** This is only a placement to match the input parameters from {@link #getParameters}. */
@Parameterized.Parameter(0)
public String testName;
/** The visibilities to loop for relayout. */
@Parameterized.Parameter(1)
public int[] visibilities;
/**
* Each row will be mapped into {@link #testName} and {@link #visibilities} of a new test
* instance according to the index of the parameter.
*/
@Parameterized.Parameters(name = "{0}")
public static Collection<Object[]> getParameters() {
return Arrays.asList(new Object[][] {
{ "Visible", new int[] { View.VISIBLE } },
{ "Invisible~Visible", new int[] { View.INVISIBLE, View.VISIBLE } },
{ "Gone~Visible", new int[] { View.GONE, View.VISIBLE } },
{ "Gone~Invisible", new int[] { View.GONE, View.INVISIBLE } }
});
}
@Test
public void testRelayout() throws Throwable {
final Activity activity = mActivityRule.getActivity();
final ContentView contentView = new ContentView(activity);
mActivityRule.runOnUiThread(() -> activity.setContentView(contentView));
getInstrumentation().waitForIdleSync();
final RelayoutRunner relayoutRunner = new RelayoutRunner(activity, contentView.getWindow(),
() -> visibilities[mIteration++ % visibilities.length]);
relayoutRunner.runBenchmark(mPerfStatusReporter.getBenchmarkState());
}
/** A dummy view to get IWindow. */
private static class ContentView extends LinearLayout {
ContentView(Context context) {
super(context);
}
@Override
protected IWindow getWindow() {
return super.getWindow();
}
}
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 MergedConfiguration mOutMergedConfiguration = new MergedConfiguration();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
final IWindow mWindow;
final View mView;
final WindowManager.LayoutParams mParams;
final int mWidth;
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;
RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) {
mWindow = window;
mView = activity.getWindow().getDecorView();
mParams = (WindowManager.LayoutParams) mView.getLayoutParams();
mWidth = mView.getMeasuredWidth();
mHeight = mView.getMeasuredHeight();
mOutSurfaceControl = mView.getViewRootImpl().getSurfaceControl();
mViewVisibility = visibilitySupplier;
}
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);
}
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.wm;
import static android.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;
import android.view.InsetsState;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import androidx.test.filters.LargeTest;
import com.android.internal.view.BaseIWindow;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
@LargeTest
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();
}
@AfterClass
public static void tearDownClass() {
sUiAutomation.dropShellPermissionIdentity();
}
/** The last {@link #PROFILED_ITERATIONS} will provide the information of method profiling. */
@Override
public void onStart(int iteration) {
startProfiling(WindowAddRemovePerfTest.class.getSimpleName()
+ "_MethodTracing_" + iteration + ".trace");
}
@Override
public void onFinished(int iteration) {
stopProfiling();
}
@Test
@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);
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 mOutInsetsState = new InsetsState();
final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
TestWindow() {
mLayoutParams.setTitle(TestWindow.class.getName());
mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
// Simulate as common phone window.
mLayoutParams.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
}
void runBenchmark(ManualBenchmarkState state) throws RemoteException {
final IWindowSession session = WindowManagerGlobal.getWindowSession();
long elapsedTimeNs = 0;
while (state.keepRunning(elapsedTimeNs)) {
// InputChannel cannot be reused.
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);
final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
state.addExtraResult("add", elapsedTimeNsOfAdd);
startTime = SystemClock.elapsedRealtimeNanos();
session.remove(this);
final long elapsedTimeNsOfRemove = SystemClock.elapsedRealtimeNanos() - startTime;
state.addExtraResult("remove", elapsedTimeNsOfRemove);
elapsedTimeNs = elapsedTimeNsOfAdd + elapsedTimeNsOfRemove;
inputChannel.dispose();
}
}
}
}

View File

@@ -0,0 +1,216 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.wm;
import static 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 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;
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
* trace to external storage.
*/
static final File BASE_OUT_PATH = new File("/data/local/CorePerfTests");
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));
}
/**
* Executes shell command with reading the output. It may also used to block until the current
* command is completed.
*/
static ByteArrayOutputStream executeShellCommand(String command) {
final ParcelFileDescriptor pfd = 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);
private final LifecycleListener mLifecycleListener = new LifecycleListener();
PerfTestActivityRule() {
this(false /* launchActivity */);
}
PerfTestActivityRule(boolean launchActivity) {
super(PerfTestActivity.class, false /* initialTouchMode */, launchActivity);
}
@Override
public Statement apply(Statement base, Description description) {
final Statement wrappedStatement = new Statement() {
@Override
public void evaluate() throws Throwable {
ActivityLifecycleMonitorRegistry.getInstance()
.addLifecycleCallback(mLifecycleListener);
base.evaluate();
ActivityLifecycleMonitorRegistry.getInstance()
.removeLifecycleCallback(mLifecycleListener);
}
};
return super.apply(wrappedStatement, description);
}
@Override
protected Intent getActivityIntent() {
return mStartIntent;
}
@Override
public PerfTestActivity launchActivity(Intent intent) {
final PerfTestActivity activity = super.launchActivity(intent);
mLifecycleListener.setTargetActivity(activity);
return activity;
}
PerfTestActivity launchActivity() {
return launchActivity(mStartIntent);
}
void waitForIdleSync(Stage state) {
mLifecycleListener.waitForIdleSync(state);
}
}
static class LifecycleListener implements ActivityLifecycleCallback {
private Activity mTargetActivity;
private Stage mWaitingStage;
private Stage mReceivedStage;
void setTargetActivity(Activity activity) {
mTargetActivity = activity;
mReceivedStage = mWaitingStage = null;
}
void waitForIdleSync(Stage stage) {
synchronized (this) {
if (stage != mReceivedStage) {
mWaitingStage = stage;
try {
wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
} catch (InterruptedException impossible) { }
}
mWaitingStage = mReceivedStage = null;
}
getInstrumentation().waitForIdleSync();
}
@Override
public void onActivityLifecycleChanged(Activity activity, Stage stage) {
if (mTargetActivity != activity) {
return;
}
synchronized (this) {
mReceivedStage = stage;
if (mWaitingStage == mReceivedStage) {
notifyAll();
}
}
}
}
}

View File

@@ -106,7 +106,10 @@ java_defaults {
stubs_library_visibility: ["//visibility:public"],
// Hide impl library and stub sources
impl_library_visibility: [":__package__"],
impl_library_visibility: [
":__package__",
"//frameworks/base", // For framework-all
],
stubs_source_visibility: ["//visibility:private"],
// Collates API usages from each module for further analysis.
@@ -158,6 +161,9 @@ stubs_defaults {
api_file: "api/current.txt",
removed_api_file: "api/removed.txt",
},
api_lint: {
enabled: true,
},
},
dist: {
targets: ["sdk", "win_sdk"],
@@ -181,6 +187,9 @@ stubs_defaults {
api_file: "api/system-current.txt",
removed_api_file: "api/system-removed.txt",
},
api_lint: {
enabled: true,
},
},
dist: {
targets: ["sdk", "win_sdk"],
@@ -193,6 +202,7 @@ java_defaults {
installable: false,
sdk_version: "module_current",
libs: [ "stub-annotations" ],
java_version: "1.8",
dist: {
targets: ["sdk", "win_sdk"],
dir: "apistubs/android/public",
@@ -204,6 +214,7 @@ java_defaults {
installable: false,
sdk_version: "module_current",
libs: [ "stub-annotations" ],
java_version: "1.8",
dist: {
targets: ["sdk", "win_sdk"],
dir: "apistubs/android/system",
@@ -215,6 +226,7 @@ java_defaults {
installable: false,
sdk_version: "module_current",
libs: [ "stub-annotations" ],
java_version: "1.8",
dist: {
targets: ["sdk", "win_sdk"],
dir: "apistubs/android/module-lib",
@@ -246,6 +258,9 @@ stubs_defaults {
api_file: "api/module-lib-current.txt",
removed_api_file: "api/module-lib-removed.txt",
},
api_lint: {
enabled: true,
},
},
dist: {
targets: ["sdk", "win_sdk"],
@@ -280,6 +295,9 @@ stubs_defaults {
api_file: "api/current.txt",
removed_api_file: "api/removed.txt",
},
api_lint: {
enabled: true,
},
},
dist: {
targets: ["sdk", "win_sdk"],

4
apex/blobstore/OWNERS Normal file
View File

@@ -0,0 +1,4 @@
set noparent
sudheersai@google.com
yamasani@google.com

View File

@@ -0,0 +1,18 @@
{
"presubmit": [
{
"name": "CtsBlobStoreTestCases"
},
{
"name": "CtsBlobStoreHostTestCases"
},
{
"name": "FrameworksMockingServicesTests",
"options": [
{
"include-filter": "com.android.server.blob"
}
]
}
]
}

View File

@@ -0,0 +1,40 @@
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
filegroup {
name: "framework-blobstore-sources",
srcs: [
"java/**/*.java",
"java/**/*.aidl"
],
path: "java",
}
java_library {
name: "blobstore-framework",
installable: false,
compile_dex: true,
sdk_version: "core_platform",
srcs: [
":framework-blobstore-sources",
],
aidl: {
export_include_dirs: [
"java",
],
},
libs: [
"framework-minus-apex",
],
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 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.app.blob;
/** {@hide} */
parcelable BlobHandle;

View File

@@ -0,0 +1,305 @@
/*
* Copyright 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.app.blob;
import static android.app.blob.XmlTags.ATTR_ALGO;
import static android.app.blob.XmlTags.ATTR_DIGEST;
import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME;
import static android.app.blob.XmlTags.ATTR_LABEL;
import static android.app.blob.XmlTags.ATTR_TAG;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Base64;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
/**
* An identifier to represent a blob.
*/
// TODO: use datagen tool?
public final class BlobHandle implements Parcelable {
/** @hide */
public static final String ALGO_SHA_256 = "SHA-256";
private static final String[] SUPPORTED_ALGOS = {
ALGO_SHA_256
};
private static final int LIMIT_BLOB_TAG_LENGTH = 128; // characters
private static final int LIMIT_BLOB_LABEL_LENGTH = 100; // characters
/**
* Cyrptographically secure hash algorithm used to generate hash of the blob this handle is
* representing.
*
* @hide
*/
@NonNull public final String algorithm;
/**
* Hash of the blob this handle is representing using {@link #algorithm}.
*
* @hide
*/
@NonNull public final byte[] digest;
/**
* Label of the blob that can be surfaced to the user.
* @hide
*/
@NonNull public final CharSequence label;
/**
* Time in milliseconds after which the blob should be invalidated and not
* allowed to be accessed by any other app, in {@link System#currentTimeMillis()} timebase.
*
* @hide
*/
@CurrentTimeMillisLong public final long expiryTimeMillis;
/**
* An opaque {@link String} associated with the blob.
*
* @hide
*/
@NonNull public final String tag;
private BlobHandle(String algorithm, byte[] digest, CharSequence label, long expiryTimeMillis,
String tag) {
this.algorithm = algorithm;
this.digest = digest;
this.label = label;
this.expiryTimeMillis = expiryTimeMillis;
this.tag = tag;
}
private BlobHandle(Parcel in) {
this.algorithm = in.readString();
this.digest = in.createByteArray();
this.label = in.readCharSequence();
this.expiryTimeMillis = in.readLong();
this.tag = in.readString();
}
/** @hide */
public static @NonNull BlobHandle create(@NonNull String algorithm, @NonNull byte[] digest,
@NonNull CharSequence label, @CurrentTimeMillisLong long expiryTimeMillis,
@NonNull String tag) {
final BlobHandle handle = new BlobHandle(algorithm, digest, label, expiryTimeMillis, tag);
handle.assertIsValid();
return handle;
}
/**
* Create a new blob identifier.
*
* <p> For two objects of {@link BlobHandle} to be considered equal, the following arguments
* must be equal:
* <ul>
* <li> {@code digest}
* <li> {@code label}
* <li> {@code expiryTimeMillis}
* <li> {@code tag}
* </ul>
*
* @param digest the SHA-256 hash of the blob this is representing.
* @param label a label indicating what the blob is, that can be surfaced to the user.
* The length of the label cannot be more than 100 characters. It is recommended
* to keep this brief. This may be truncated and ellipsized if it is too long
* to be displayed to the user.
* @param expiryTimeMillis the time in secs after which the blob should be invalidated and not
* allowed to be accessed by any other app,
* in {@link System#currentTimeMillis()} timebase or {@code 0} to
* indicate that there is no expiry time associated with this blob.
* @param tag an opaque {@link String} associated with the blob. The length of the tag
* cannot be more than 128 characters.
*
* @return a new instance of {@link BlobHandle} object.
*/
public static @NonNull BlobHandle createWithSha256(@NonNull byte[] digest,
@NonNull CharSequence label, @CurrentTimeMillisLong long expiryTimeMillis,
@NonNull String tag) {
return create(ALGO_SHA_256, digest, label, expiryTimeMillis, tag);
}
/**
* Returns the SHA-256 hash of the blob that this object is representing.
*
* @see #createWithSha256(byte[], CharSequence, long, String)
*/
public @NonNull byte[] getSha256Digest() {
return digest;
}
/**
* Returns the label associated with the blob that this object is representing.
*
* @see #createWithSha256(byte[], CharSequence, long, String)
*/
public @NonNull CharSequence getLabel() {
return label;
}
/**
* Returns the expiry time in milliseconds of the blob that this object is representing, in
* {@link System#currentTimeMillis()} timebase.
*
* @see #createWithSha256(byte[], CharSequence, long, String)
*/
public @CurrentTimeMillisLong long getExpiryTimeMillis() {
return expiryTimeMillis;
}
/**
* Returns the opaque {@link String} associated with the blob this object is representing.
*
* @see #createWithSha256(byte[], CharSequence, long, String)
*/
public @NonNull String getTag() {
return tag;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(algorithm);
dest.writeByteArray(digest);
dest.writeCharSequence(label);
dest.writeLong(expiryTimeMillis);
dest.writeString(tag);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof BlobHandle)) {
return false;
}
final BlobHandle other = (BlobHandle) obj;
return this.algorithm.equals(other.algorithm)
&& Arrays.equals(this.digest, other.digest)
&& this.label.toString().equals(other.label.toString())
&& this.expiryTimeMillis == other.expiryTimeMillis
&& this.tag.equals(other.tag);
}
@Override
public int hashCode() {
return Objects.hash(algorithm, Arrays.hashCode(digest), label, expiryTimeMillis, tag);
}
/** @hide */
public void dump(IndentingPrintWriter fout, boolean dumpFull) {
if (dumpFull) {
fout.println("algo: " + algorithm);
fout.println("digest: " + (dumpFull ? encodeDigest(digest) : safeDigest(digest)));
fout.println("label: " + label);
fout.println("expiryMs: " + expiryTimeMillis);
fout.println("tag: " + tag);
} else {
fout.println(toString());
}
}
/** @hide */
public void assertIsValid() {
Preconditions.checkArgumentIsSupported(SUPPORTED_ALGOS, algorithm);
Preconditions.checkByteArrayNotEmpty(digest, "digest");
Preconditions.checkStringNotEmpty(label, "label must not be null");
Preconditions.checkArgument(label.length() <= LIMIT_BLOB_LABEL_LENGTH, "label too long");
Preconditions.checkArgumentNonnegative(expiryTimeMillis,
"expiryTimeMillis must not be negative");
Preconditions.checkStringNotEmpty(tag, "tag must not be null");
Preconditions.checkArgument(tag.length() <= LIMIT_BLOB_TAG_LENGTH, "tag too long");
}
@Override
public String toString() {
return "BlobHandle {"
+ "algo:" + algorithm + ","
+ "digest:" + safeDigest(digest) + ","
+ "label:" + label + ","
+ "expiryMs:" + expiryTimeMillis + ","
+ "tag:" + tag
+ "}";
}
/** @hide */
public static String safeDigest(@NonNull byte[] digest) {
final String digestStr = encodeDigest(digest);
return digestStr.substring(0, 2) + ".." + digestStr.substring(digestStr.length() - 2);
}
private static String encodeDigest(@NonNull byte[] digest) {
return Base64.encodeToString(digest, Base64.NO_WRAP);
}
/** @hide */
public boolean isExpired() {
return expiryTimeMillis != 0 && expiryTimeMillis < System.currentTimeMillis();
}
public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() {
@Override
public @NonNull BlobHandle createFromParcel(@NonNull Parcel source) {
return new BlobHandle(source);
}
@Override
public @NonNull BlobHandle[] newArray(int size) {
return new BlobHandle[size];
}
};
/** @hide */
public void writeToXml(@NonNull XmlSerializer out) throws IOException {
XmlUtils.writeStringAttribute(out, ATTR_ALGO, algorithm);
XmlUtils.writeByteArrayAttribute(out, ATTR_DIGEST, digest);
XmlUtils.writeStringAttribute(out, ATTR_LABEL, label);
XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis);
XmlUtils.writeStringAttribute(out, ATTR_TAG, tag);
}
/** @hide */
@NonNull
public static BlobHandle createFromXml(@NonNull XmlPullParser in) throws IOException {
final String algo = XmlUtils.readStringAttribute(in, ATTR_ALGO);
final byte[] digest = XmlUtils.readByteArrayAttribute(in, ATTR_DIGEST);
final CharSequence label = XmlUtils.readStringAttribute(in, ATTR_LABEL);
final long expiryTimeMs = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME);
final String tag = XmlUtils.readStringAttribute(in, ATTR_TAG);
return BlobHandle.create(algo, digest, label, expiryTimeMs, tag);
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 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.app.blob;
/** {@hide} */
parcelable BlobInfo;

View File

@@ -0,0 +1,127 @@
/*
* 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.app.blob;
import static android.text.format.Formatter.FLAG_IEC_UNITS;
import android.annotation.NonNull;
import android.app.AppGlobals;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.format.Formatter;
import java.util.Collections;
import java.util.List;
/**
* Class to provide information about a shared blob.
*
* @hide
*/
public final class BlobInfo implements Parcelable {
private final long mId;
private final long mExpiryTimeMs;
private final CharSequence mLabel;
private final long mSizeBytes;
private final List<LeaseInfo> mLeaseInfos;
public BlobInfo(long id, long expiryTimeMs, CharSequence label, long sizeBytes,
List<LeaseInfo> leaseInfos) {
mId = id;
mExpiryTimeMs = expiryTimeMs;
mLabel = label;
mSizeBytes = sizeBytes;
mLeaseInfos = leaseInfos;
}
private BlobInfo(Parcel in) {
mId = in.readLong();
mExpiryTimeMs = in.readLong();
mLabel = in.readCharSequence();
mSizeBytes = in.readLong();
mLeaseInfos = in.readArrayList(null /* classloader */);
}
public long getId() {
return mId;
}
public long getExpiryTimeMs() {
return mExpiryTimeMs;
}
public CharSequence getLabel() {
return mLabel;
}
public long getSizeBytes() {
return mSizeBytes;
}
public List<LeaseInfo> getLeases() {
return Collections.unmodifiableList(mLeaseInfos);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeLong(mId);
dest.writeLong(mExpiryTimeMs);
dest.writeCharSequence(mLabel);
dest.writeLong(mSizeBytes);
dest.writeList(mLeaseInfos);
}
@Override
public String toString() {
return toShortString();
}
private String toShortString() {
return "BlobInfo {"
+ "id: " + mId + ","
+ "expiryMs: " + mExpiryTimeMs + ","
+ "label: " + mLabel + ","
+ "size: " + formatBlobSize(mSizeBytes) + ","
+ "leases: " + LeaseInfo.toShortString(mLeaseInfos) + ","
+ "}";
}
private static String formatBlobSize(long sizeBytes) {
return Formatter.formatFileSize(AppGlobals.getInitialApplication(),
sizeBytes, FLAG_IEC_UNITS);
}
@Override
public int describeContents() {
return 0;
}
@NonNull
public static final Creator<BlobInfo> CREATOR = new Creator<BlobInfo>() {
@Override
@NonNull
public BlobInfo createFromParcel(Parcel source) {
return new BlobInfo(source);
}
@Override
@NonNull
public BlobInfo[] newArray(int size) {
return new BlobInfo[size];
}
};
}

View File

@@ -0,0 +1,943 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.blob;
import android.annotation.BytesLong;
import android.annotation.CallbackExecutor;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.IdRes;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
import android.os.LimitExceededException;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import com.android.internal.util.function.pooled.PooledLambda;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
/**
* This class provides access to the blob store managed by the system.
*
* <p> Apps can publish and access a data blob using a {@link BlobHandle} object which can
* be created with {@link BlobHandle#createWithSha256(byte[], CharSequence, long, String)}.
* This {@link BlobHandle} object encapsulates the following pieces of information used for
* identifying the blobs:
* <ul>
* <li> {@link BlobHandle#getSha256Digest()}
* <li> {@link BlobHandle#getLabel()}
* <li> {@link BlobHandle#getExpiryTimeMillis()}
* <li> {@link BlobHandle#getTag()}
* </ul>
* For two {@link BlobHandle} objects to be considered identical, all these pieces of information
* must be equal.
*
* <p> For contributing a new data blob, an app needs to create a session using
* {@link BlobStoreManager#createSession(BlobHandle)} and then open this session for writing using
* {@link BlobStoreManager#openSession(long)}.
*
* <p> The following code snippet shows how to create and open a session for writing:
* <pre class="prettyprint">
* final long sessionId = blobStoreManager.createSession(blobHandle);
* try (BlobStoreManager.Session session = blobStoreManager.openSession(sessionId)) {
* try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(
* session.openWrite(offsetBytes, lengthBytes))) {
* writeData(out);
* }
* }
* </pre>
*
* <p> If all the data could not be written in a single attempt, apps can close this session
* and re-open it again using the session id obtained via
* {@link BlobStoreManager#createSession(BlobHandle)}. Note that the session data is persisted
* and can be re-opened for completing the data contribution, even across device reboots.
*
* <p> After the data is written to the session, it can be committed using
* {@link Session#commit(Executor, Consumer)}. Until the session is committed, data written
* to the session will not be shared with any app.
*
* <p class="note"> Once a session is committed using {@link Session#commit(Executor, Consumer)},
* any data written as part of this session is sealed and cannot be modified anymore.
*
* <p> Before committing the session, apps can indicate which apps are allowed to access the
* contributed data using one or more of the following access modes:
* <ul>
* <li> {@link Session#allowPackageAccess(String, byte[])} which will allow whitelisting
* specific packages to access the blobs.
* <li> {@link Session#allowSameSignatureAccess()} which will allow only apps which are signed
* with the same certificate as the app which contributed the blob to access it.
* <li> {@link Session#allowPublicAccess()} which will allow any app on the device to access
* the blob.
* </ul>
*
* <p> The following code snippet shows how to specify the access mode and commit the session:
* <pre class="prettyprint">
* try (BlobStoreManager.Session session = blobStoreManager.openSession(sessionId)) {
* try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(
* session.openWrite(offsetBytes, lengthBytes))) {
* writeData(out);
* }
* session.allowSameSignatureAccess();
* session.allowPackageAccess(packageName, certificate);
* session.commit(executor, callback);
* }
* </pre>
*
* <p> Apps that satisfy at least one of the access mode constraints specified by the publisher
* of the data blob will be able to access it.
*
* <p> A data blob published without specifying any of
* these access modes will be considered private and only the app that contributed the data
* blob will be allowed to access it. This is still useful for overall device system health as
* the System can try to keep one copy of data blob on disk when multiple apps contribute the
* same data.
*
* <p class="note"> It is strongly recommended that apps use one of
* {@link Session#allowPackageAccess(String, byte[])} or {@link Session#allowSameSignatureAccess()}
* when they know, ahead of time, the set of apps they would like to share the blobs with.
* {@link Session#allowPublicAccess()} is meant for publicly available data committed from
* libraries and SDKs.
*
* <p> Once a data blob is committed with {@link Session#commit(Executor, Consumer)}, it
* can be accessed using {@link BlobStoreManager#openBlob(BlobHandle)}, assuming the caller
* satisfies constraints of any of the access modes associated with that data blob. An app may
* acquire a lease on a blob with {@link BlobStoreManager#acquireLease(BlobHandle, int)} and
* release the lease with {@link BlobStoreManager#releaseLease(BlobHandle)}. A blob will not be
* deleted from the system while there is at least one app leasing it.
*
* <p> The following code snippet shows how to access the data blob:
* <pre class="prettyprint">
* try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(
* blobStoreManager.openBlob(blobHandle)) {
* useData(in);
* }
* </pre>
*/
@SystemService(Context.BLOB_STORE_SERVICE)
public class BlobStoreManager {
/** @hide */
public static final int COMMIT_RESULT_SUCCESS = 0;
/** @hide */
public static final int COMMIT_RESULT_ERROR = 1;
/** @hide */
public static final int INVALID_RES_ID = -1;
private final Context mContext;
private final IBlobStoreManager mService;
/** @hide */
public BlobStoreManager(@NonNull Context context, @NonNull IBlobStoreManager service) {
mContext = context;
mService = service;
}
/**
* Create a new session using the given {@link BlobHandle}, returning a unique id
* that represents the session. Once created, the session can be opened
* multiple times across multiple device boots.
*
* <p> The system may automatically destroy sessions that have not been
* finalized (either committed or abandoned) within a reasonable period of
* time, typically about a week.
*
* <p> If an app is planning to acquire a lease on this data (using
* {@link #acquireLease(BlobHandle, int)} or one of it's other variants) after committing
* this data (using {@link Session#commit(Executor, Consumer)}), it is recommended that
* the app checks the remaining quota for acquiring a lease first using
* {@link #getRemainingLeaseQuotaBytes()} and can skip contributing this data if needed.
*
* @param blobHandle the {@link BlobHandle} identifier for which a new session
* needs to be created.
* @return positive, non-zero unique id that represents the created session.
* This id remains consistent across device reboots until the
* session is finalized. IDs are not reused during a given boot.
*
* @throws IOException when there is an I/O error while creating the session.
* @throws SecurityException when the caller is not allowed to create a session, such
* as when called from an Instant app.
* @throws IllegalArgumentException when {@code blobHandle} is invalid.
* @throws LimitExceededException when a new session could not be created, such as when the
* caller is trying to create too many sessions.
*/
public @IntRange(from = 1) long createSession(@NonNull BlobHandle blobHandle)
throws IOException {
try {
return mService.createSession(blobHandle, mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
e.maybeRethrow(LimitExceededException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Open an existing session to actively perform work.
*
* @param sessionId a unique id obtained via {@link #createSession(BlobHandle)} that
* represents a particular session.
* @return the {@link Session} object corresponding to the {@code sessionId}.
*
* @throws IOException when there is an I/O error while opening the session.
* @throws SecurityException when the caller does not own the session, or
* the session does not exist or is invalid.
*/
public @NonNull Session openSession(@IntRange(from = 1) long sessionId) throws IOException {
try {
return new Session(mService.openSession(sessionId, mContext.getOpPackageName()));
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Abandons an existing session and deletes any data that was written to that session so far.
*
* @param sessionId a unique id obtained via {@link #createSession(BlobHandle)} that
* represents a particular session.
*
* @throws IOException when there is an I/O error while deleting the session.
* @throws SecurityException when the caller does not own the session, or
* the session does not exist or is invalid.
*/
public void abandonSession(@IntRange(from = 1) long sessionId) throws IOException {
try {
mService.abandonSession(sessionId, mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Opens an existing blob for reading from the blob store managed by the system.
*
* @param blobHandle the {@link BlobHandle} representing the blob that the caller
* wants to access.
* @return a {@link ParcelFileDescriptor} that can be used to read the blob content.
*
* @throws IOException when there is an I/O while opening the blob for read.
* @throws IllegalArgumentException when {@code blobHandle} is invalid.
* @throws SecurityException when the blob represented by the {@code blobHandle} does not
* exist or the caller does not have access to it.
*/
public @NonNull ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle)
throws IOException {
try {
return mService.openBlob(blobHandle, mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
* system that the caller wants the blob to be kept around.
*
* <p> Any active leases will be automatically released when the blob's expiry time
* ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
*
* <p> This lease information is persisted and calling this more than once will result in
* latest lease overriding any previous lease.
*
* <p> When an app acquires a lease on a blob, the System will try to keep this
* blob around but note that it can still be deleted if it was requested by the user.
*
* <p> In case the resource name for the {@code descriptionResId} is modified as part of
* an app update, apps should re-acquire the lease with the new resource id.
*
* @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
* acquire a lease for.
* @param descriptionResId the resource id for a short description string that can be surfaced
* to the user explaining what the blob is used for.
* @param leaseExpiryTimeMillis the time in milliseconds after which the lease can be
* automatically released, in {@link System#currentTimeMillis()}
* timebase. If its value is {@code 0}, then the behavior of this
* API is identical to {@link #acquireLease(BlobHandle, int)}
* where clients have to explicitly call
* {@link #releaseLease(BlobHandle)} when they don't
* need the blob anymore.
*
* @throws IOException when there is an I/O error while acquiring a lease to the blob.
* @throws SecurityException when the blob represented by the {@code blobHandle} does not
* exist or the caller does not have access to it.
* @throws IllegalArgumentException when {@code blobHandle} is invalid or
* if the {@code leaseExpiryTimeMillis} is greater than the
* {@link BlobHandle#getExpiryTimeMillis()}.
* @throws LimitExceededException when a lease could not be acquired, such as when the
* caller is trying to acquire too many leases or acquire
* leases on too much data. Apps can avoid this by checking
* the remaining quota using
* {@link #getRemainingLeaseQuotaBytes()} before trying to
* acquire a lease.
*
* @see #acquireLease(BlobHandle, int)
* @see #acquireLease(BlobHandle, CharSequence)
*/
public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId,
@CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException {
try {
mService.acquireLease(blobHandle, descriptionResId, null, leaseExpiryTimeMillis,
mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
e.maybeRethrow(LimitExceededException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
* system that the caller wants the blob to be kept around.
*
* <p> This is a variant of {@link #acquireLease(BlobHandle, int, long)} taking a
* {@link CharSequence} for {@code description}. It is highly recommended that callers only
* use this when a valid resource ID for {@code description} could not be provided. Otherwise,
* apps should prefer using {@link #acquireLease(BlobHandle, int)} which will allow
* {@code description} to be localized.
*
* <p> Any active leases will be automatically released when the blob's expiry time
* ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
*
* <p> This lease information is persisted and calling this more than once will result in
* latest lease overriding any previous lease.
*
* <p> When an app acquires a lease on a blob, the System will try to keep this
* blob around but note that it can still be deleted if it was requested by the user.
*
* @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
* acquire a lease for.
* @param description a short description string that can be surfaced
* to the user explaining what the blob is used for. It is recommended to
* keep this description brief. This may be truncated and ellipsized
* if it is too long to be displayed to the user.
* @param leaseExpiryTimeMillis the time in milliseconds after which the lease can be
* automatically released, in {@link System#currentTimeMillis()}
* timebase. If its value is {@code 0}, then the behavior of this
* API is identical to {@link #acquireLease(BlobHandle, int)}
* where clients have to explicitly call
* {@link #releaseLease(BlobHandle)} when they don't
* need the blob anymore.
*
* @throws IOException when there is an I/O error while acquiring a lease to the blob.
* @throws SecurityException when the blob represented by the {@code blobHandle} does not
* exist or the caller does not have access to it.
* @throws IllegalArgumentException when {@code blobHandle} is invalid or
* if the {@code leaseExpiryTimeMillis} is greater than the
* {@link BlobHandle#getExpiryTimeMillis()}.
* @throws LimitExceededException when a lease could not be acquired, such as when the
* caller is trying to acquire too many leases or acquire
* leases on too much data. Apps can avoid this by checking
* the remaining quota using
* {@link #getRemainingLeaseQuotaBytes()} before trying to
* acquire a lease.
*
* @see #acquireLease(BlobHandle, int, long)
* @see #acquireLease(BlobHandle, CharSequence)
*/
public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description,
@CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException {
try {
mService.acquireLease(blobHandle, INVALID_RES_ID, description, leaseExpiryTimeMillis,
mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
e.maybeRethrow(LimitExceededException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
* system that the caller wants the blob to be kept around.
*
* <p> This is similar to {@link #acquireLease(BlobHandle, int, long)} except clients don't
* have to specify the lease expiry time upfront using this API and need to explicitly
* release the lease using {@link #releaseLease(BlobHandle)} when they no longer like to keep
* a blob around.
*
* <p> Any active leases will be automatically released when the blob's expiry time
* ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
*
* <p> This lease information is persisted and calling this more than once will result in
* latest lease overriding any previous lease.
*
* <p> When an app acquires a lease on a blob, the System will try to keep this
* blob around but note that it can still be deleted if it was requested by the user.
*
* <p> In case the resource name for the {@code descriptionResId} is modified as part of
* an app update, apps should re-acquire the lease with the new resource id.
*
* @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
* acquire a lease for.
* @param descriptionResId the resource id for a short description string that can be surfaced
* to the user explaining what the blob is used for.
*
* @throws IOException when there is an I/O error while acquiring a lease to the blob.
* @throws SecurityException when the blob represented by the {@code blobHandle} does not
* exist or the caller does not have access to it.
* @throws IllegalArgumentException when {@code blobHandle} is invalid.
* @throws LimitExceededException when a lease could not be acquired, such as when the
* caller is trying to acquire too many leases or acquire
* leases on too much data. Apps can avoid this by checking
* the remaining quota using
* {@link #getRemainingLeaseQuotaBytes()} before trying to
* acquire a lease.
*
* @see #acquireLease(BlobHandle, int, long)
* @see #acquireLease(BlobHandle, CharSequence, long)
*/
public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId)
throws IOException {
acquireLease(blobHandle, descriptionResId, 0);
}
/**
* Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
* system that the caller wants the blob to be kept around.
*
* <p> This is a variant of {@link #acquireLease(BlobHandle, int)} taking a {@link CharSequence}
* for {@code description}. It is highly recommended that callers only use this when a valid
* resource ID for {@code description} could not be provided. Otherwise, apps should prefer
* using {@link #acquireLease(BlobHandle, int)} which will allow {@code description} to be
* localized.
*
* <p> This is similar to {@link #acquireLease(BlobHandle, CharSequence, long)} except clients
* don't have to specify the lease expiry time upfront using this API and need to explicitly
* release the lease using {@link #releaseLease(BlobHandle)} when they no longer like to keep
* a blob around.
*
* <p> Any active leases will be automatically released when the blob's expiry time
* ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
*
* <p> This lease information is persisted and calling this more than once will result in
* latest lease overriding any previous lease.
*
* <p> When an app acquires a lease on a blob, the System will try to keep this
* blob around but note that it can still be deleted if it was requested by the user.
*
* @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
* acquire a lease for.
* @param description a short description string that can be surfaced
* to the user explaining what the blob is used for. It is recommended to
* keep this description brief. This may be truncated and
* ellipsized if it is too long to be displayed to the user.
*
* @throws IOException when there is an I/O error while acquiring a lease to the blob.
* @throws SecurityException when the blob represented by the {@code blobHandle} does not
* exist or the caller does not have access to it.
* @throws IllegalArgumentException when {@code blobHandle} is invalid.
* @throws LimitExceededException when a lease could not be acquired, such as when the
* caller is trying to acquire too many leases or acquire
* leases on too much data. Apps can avoid this by checking
* the remaining quota using
* {@link #getRemainingLeaseQuotaBytes()} before trying to
* acquire a lease.
*
* @see #acquireLease(BlobHandle, int)
* @see #acquireLease(BlobHandle, CharSequence, long)
*/
public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description)
throws IOException {
acquireLease(blobHandle, description, 0);
}
/**
* Release any active lease to the blob represented by {@code blobHandle} which is
* currently held by the caller.
*
* @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
* release the lease for.
*
* @throws IOException when there is an I/O error while releasing the release to the blob.
* @throws SecurityException when the blob represented by the {@code blobHandle} does not
* exist or the caller does not have access to it.
* @throws IllegalArgumentException when {@code blobHandle} is invalid.
*/
public void releaseLease(@NonNull BlobHandle blobHandle) throws IOException {
try {
mService.releaseLease(blobHandle, mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return the remaining quota size for acquiring a lease (in bytes) which indicates the
* remaining amount of data that an app can acquire a lease on before the System starts
* rejecting lease requests.
*
* If an app wants to acquire a lease on a blob but the remaining quota size is not sufficient,
* then it can try releasing leases on any older blobs which are not needed anymore.
*
* @return the remaining quota size for acquiring a lease.
*/
public @IntRange(from = 0) long getRemainingLeaseQuotaBytes() {
try {
return mService.getRemainingLeaseQuotaBytes(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Wait until any pending tasks (like persisting data to disk) have finished.
*
* @hide
*/
@TestApi
public void waitForIdle(long timeoutMillis) throws InterruptedException, TimeoutException {
try {
final CountDownLatch countDownLatch = new CountDownLatch(1);
mService.waitForIdle(new RemoteCallback((result) -> countDownLatch.countDown()));
if (!countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
throw new TimeoutException("Timed out waiting for service to become idle");
}
} catch (ParcelableException e) {
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
@NonNull
public List<BlobInfo> queryBlobsForUser(@NonNull UserHandle user) throws IOException {
try {
return mService.queryBlobsForUser(user.getIdentifier());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
public void deleteBlob(@NonNull BlobInfo blobInfo) throws IOException {
try {
mService.deleteBlob(blobInfo.getId());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return the {@link BlobHandle BlobHandles} corresponding to the data blobs that
* the calling app currently has a lease on.
*
* @return a list of {@link BlobHandle BlobHandles} that the caller has a lease on.
*/
@NonNull
public List<BlobHandle> getLeasedBlobs() throws IOException {
try {
return mService.getLeasedBlobs(mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return {@link LeaseInfo} representing a lease acquired using
* {@link #acquireLease(BlobHandle, int)} or one of it's other variants,
* or {@code null} if there is no lease acquired.
*
* @throws SecurityException when the blob represented by the {@code blobHandle} does not
* exist or the caller does not have access to it.
* @throws IllegalArgumentException when {@code blobHandle} is invalid.
*
* @hide
*/
@TestApi
@Nullable
public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle) throws IOException {
try {
return mService.getLeaseInfo(blobHandle, mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Represents an ongoing session of a blob's contribution to the blob store managed by the
* system.
*
* <p> Clients that want to contribute a blob need to first create a {@link Session} using
* {@link #createSession(BlobHandle)} and once the session is created, clients can open and
* close this session multiple times using {@link #openSession(long)} and
* {@link Session#close()} before committing it using
* {@link Session#commit(Executor, Consumer)}, at which point system will take
* ownership of the blob and the client can no longer make any modifications to the blob's
* content.
*/
public static class Session implements Closeable {
private final IBlobStoreSession mSession;
private Session(@NonNull IBlobStoreSession session) {
mSession = session;
}
/**
* Opens a file descriptor to write a blob into the session.
*
* <p> The returned file descriptor will start writing data at the requested offset
* in the underlying file, which can be used to resume a partially
* written file. If a valid file length is specified, the system will
* preallocate the underlying disk space to optimize placement on disk.
* It is strongly recommended to provide a valid file length when known.
*
* @param offsetBytes offset into the file to begin writing at, or 0 to
* start at the beginning of the file.
* @param lengthBytes total size of the file being written, used to
* preallocate the underlying disk space, or -1 if unknown.
* The system may clear various caches as needed to allocate
* this space.
*
* @return a {@link ParcelFileDescriptor} for writing to the blob file.
*
* @throws IOException when there is an I/O error while opening the file to write.
* @throws SecurityException when the caller is not the owner of the session.
* @throws IllegalStateException when the caller tries to write to the file after it is
* abandoned (using {@link #abandon()})
* or committed (using {@link #commit})
* or closed (using {@link #close()}).
*/
public @NonNull ParcelFileDescriptor openWrite(@BytesLong long offsetBytes,
@BytesLong long lengthBytes) throws IOException {
try {
final ParcelFileDescriptor pfd = mSession.openWrite(offsetBytes, lengthBytes);
pfd.seekTo(offsetBytes);
return pfd;
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Opens a file descriptor to read the blob content already written into this session.
*
* @return a {@link ParcelFileDescriptor} for reading from the blob file.
*
* @throws IOException when there is an I/O error while opening the file to read.
* @throws SecurityException when the caller is not the owner of the session.
* @throws IllegalStateException when the caller tries to read the file after it is
* abandoned (using {@link #abandon()})
* or closed (using {@link #close()}).
*/
public @NonNull ParcelFileDescriptor openRead() throws IOException {
try {
return mSession.openRead();
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Gets the size of the blob file that was written to the session so far.
*
* @return the size of the blob file so far.
*
* @throws IOException when there is an I/O error while opening the file to read.
* @throws SecurityException when the caller is not the owner of the session.
* @throws IllegalStateException when the caller tries to get the file size after it is
* abandoned (using {@link #abandon()})
* or closed (using {@link #close()}).
*/
public @BytesLong long getSize() throws IOException {
try {
return mSession.getSize();
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Close this session. It can be re-opened for writing/reading if it has not been
* abandoned (using {@link #abandon}) or committed (using {@link #commit}).
*
* @throws IOException when there is an I/O error while closing the session.
* @throws SecurityException when the caller is not the owner of the session.
*/
public void close() throws IOException {
try {
mSession.close();
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Abandon this session and delete any data that was written to this session so far.
*
* @throws IOException when there is an I/O error while abandoning the session.
* @throws SecurityException when the caller is not the owner of the session.
* @throws IllegalStateException when the caller tries to abandon a session which was
* already finalized.
*/
public void abandon() throws IOException {
try {
mSession.abandon();
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Allow {@code packageName} with a particular signing certificate to access this blob
* data once it is committed using a {@link BlobHandle} representing the blob.
*
* <p> This needs to be called before committing the blob using
* {@link #commit(Executor, Consumer)}.
*
* @param packageName the name of the package which should be allowed to access the blob.
* @param certificate the input bytes representing a certificate of type
* {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
*
* @throws IOException when there is an I/O error while changing the access.
* @throws SecurityException when the caller is not the owner of the session.
* @throws IllegalStateException when the caller tries to change access for a blob which is
* already committed.
* @throws LimitExceededException when the caller tries to explicitly allow too
* many packages using this API.
*/
public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate)
throws IOException {
try {
mSession.allowPackageAccess(packageName, certificate);
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
e.maybeRethrow(LimitExceededException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns {@code true} if access has been allowed for a {@code packageName} using either
* {@link #allowPackageAccess(String, byte[])}.
* Otherwise, {@code false}.
*
* @param packageName the name of the package to check the access for.
* @param certificate the input bytes representing a certificate of type
* {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
*
* @throws IOException when there is an I/O error while getting the access type.
* @throws IllegalStateException when the caller tries to get access type from a session
* which is closed or abandoned.
*/
public boolean isPackageAccessAllowed(@NonNull String packageName,
@NonNull byte[] certificate) throws IOException {
try {
return mSession.isPackageAccessAllowed(packageName, certificate);
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Allow packages which are signed with the same certificate as the caller to access this
* blob data once it is committed using a {@link BlobHandle} representing the blob.
*
* <p> This needs to be called before committing the blob using
* {@link #commit(Executor, Consumer)}.
*
* @throws IOException when there is an I/O error while changing the access.
* @throws SecurityException when the caller is not the owner of the session.
* @throws IllegalStateException when the caller tries to change access for a blob which is
* already committed.
*/
public void allowSameSignatureAccess() throws IOException {
try {
mSession.allowSameSignatureAccess();
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns {@code true} if access has been allowed for packages signed with the same
* certificate as the caller by using {@link #allowSameSignatureAccess()}.
* Otherwise, {@code false}.
*
* @throws IOException when there is an I/O error while getting the access type.
* @throws IllegalStateException when the caller tries to get access type from a session
* which is closed or abandoned.
*/
public boolean isSameSignatureAccessAllowed() throws IOException {
try {
return mSession.isSameSignatureAccessAllowed();
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Allow any app on the device to access this blob data once it is committed using
* a {@link BlobHandle} representing the blob.
*
* <p><strong>Note:</strong> This is only meant to be used from libraries and SDKs where
* the apps which we want to allow access is not known ahead of time.
* If a blob is being committed to be shared with a particular set of apps, it is highly
* recommended to use {@link #allowPackageAccess(String, byte[])} instead.
*
* <p> This needs to be called before committing the blob using
* {@link #commit(Executor, Consumer)}.
*
* @throws IOException when there is an I/O error while changing the access.
* @throws SecurityException when the caller is not the owner of the session.
* @throws IllegalStateException when the caller tries to change access for a blob which is
* already committed.
*/
public void allowPublicAccess() throws IOException {
try {
mSession.allowPublicAccess();
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns {@code true} if public access has been allowed by using
* {@link #allowPublicAccess()}. Otherwise, {@code false}.
*
* @throws IOException when there is an I/O error while getting the access type.
* @throws IllegalStateException when the caller tries to get access type from a session
* which is closed or abandoned.
*/
public boolean isPublicAccessAllowed() throws IOException {
try {
return mSession.isPublicAccessAllowed();
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Commit the file that was written so far to this session to the blob store maintained by
* the system.
*
* <p> Once this method is called, the session is finalized and no additional
* mutations can be performed on the session. If the device reboots
* before the session has been finalized, you may commit the session again.
*
* <p> Note that this commit operation will fail if the hash of the data written so far
* to this session does not match with the one used for
* {@link BlobHandle#createWithSha256(byte[], CharSequence, long, String)} BlobHandle}
* associated with this session.
*
* <p> Committing the same data more than once will result in replacing the corresponding
* access mode (via calling one of {@link #allowPackageAccess(String, byte[])},
* {@link #allowSameSignatureAccess()}, etc) with the latest one.
*
* @param executor the executor on which result callback will be invoked.
* @param resultCallback a callback to receive the commit result. when the result is
* {@code 0}, it indicates success. Otherwise, failure.
*
* @throws IOException when there is an I/O error while committing the session.
* @throws SecurityException when the caller is not the owner of the session.
* @throws IllegalArgumentException when the passed parameters are not valid.
* @throws IllegalStateException when the caller tries to commit a session which was
* already finalized.
*/
public void commit(@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<Integer> resultCallback) throws IOException {
try {
mSession.commit(new IBlobCommitCallback.Stub() {
public void onResult(int result) {
executor.execute(PooledLambda.obtainRunnable(
Consumer::accept, resultCallback, result));
}
});
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.blob;
import android.app.SystemServiceRegistry;
import android.content.Context;
/**
* This is where the BlobStoreManagerService wrapper is registered.
*
* @hide
*/
public class BlobStoreManagerFrameworkInitializer {
/** Register the BlobStoreManager wrapper class */
public static void initialize() {
SystemServiceRegistry.registerContextAwareService(
Context.BLOB_STORE_SERVICE, BlobStoreManager.class,
(context, service) ->
new BlobStoreManager(context, IBlobStoreManager.Stub.asInterface(service)));
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 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.app.blob;
/** {@hide} */
oneway interface IBlobCommitCallback {
void onResult(int result);
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.blob;
import android.app.blob.BlobHandle;
import android.app.blob.BlobInfo;
import android.app.blob.IBlobStoreSession;
import android.app.blob.LeaseInfo;
import android.os.RemoteCallback;
/** {@hide} */
interface IBlobStoreManager {
long createSession(in BlobHandle handle, in String packageName);
IBlobStoreSession openSession(long sessionId, in String packageName);
ParcelFileDescriptor openBlob(in BlobHandle handle, in String packageName);
void abandonSession(long sessionId, in String packageName);
void acquireLease(in BlobHandle handle, int descriptionResId, in CharSequence description,
long leaseTimeoutMillis, in String packageName);
void releaseLease(in BlobHandle handle, in String packageName);
long getRemainingLeaseQuotaBytes(String packageName);
void waitForIdle(in RemoteCallback callback);
List<BlobInfo> queryBlobsForUser(int userId);
void deleteBlob(long blobId);
List<BlobHandle> getLeasedBlobs(in String packageName);
LeaseInfo getLeaseInfo(in BlobHandle blobHandle, in String packageName);
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 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.app.blob;
import android.app.blob.IBlobCommitCallback;
import android.os.ParcelFileDescriptor;
/** {@hide} */
interface IBlobStoreSession {
ParcelFileDescriptor openWrite(long offsetBytes, long lengthBytes);
ParcelFileDescriptor openRead();
void allowPackageAccess(in String packageName, in byte[] certificate);
void allowSameSignatureAccess();
void allowPublicAccess();
boolean isPackageAccessAllowed(in String packageName, in byte[] certificate);
boolean isSameSignatureAccessAllowed();
boolean isPublicAccessAllowed();
long getSize();
void close();
void abandon();
void commit(in IBlobCommitCallback callback);
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 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.app.blob;
/** {@hide} */
parcelable LeaseInfo;

View File

@@ -0,0 +1,130 @@
/*
* 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.app.blob;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.List;
/**
* Class to provide information about a lease (acquired using
* {@link BlobStoreManager#acquireLease(BlobHandle, int)} or one of it's variants)
* for a shared blob.
*
* @hide
*/
@TestApi
public final class LeaseInfo implements Parcelable {
private final String mPackageName;
private final long mExpiryTimeMillis;
private final int mDescriptionResId;
private final CharSequence mDescription;
public LeaseInfo(@NonNull String packageName, @CurrentTimeMillisLong long expiryTimeMs,
@IdRes int descriptionResId, @Nullable CharSequence description) {
mPackageName = packageName;
mExpiryTimeMillis = expiryTimeMs;
mDescriptionResId = descriptionResId;
mDescription = description;
}
private LeaseInfo(Parcel in) {
mPackageName = in.readString();
mExpiryTimeMillis = in.readLong();
mDescriptionResId = in.readInt();
mDescription = in.readCharSequence();
}
@NonNull
public String getPackageName() {
return mPackageName;
}
@CurrentTimeMillisLong
public long getExpiryTimeMillis() {
return mExpiryTimeMillis;
}
@IdRes
public int getDescriptionResId() {
return mDescriptionResId;
}
@Nullable
public CharSequence getDescription() {
return mDescription;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mPackageName);
dest.writeLong(mExpiryTimeMillis);
dest.writeInt(mDescriptionResId);
dest.writeCharSequence(mDescription);
}
@Override
public String toString() {
return "LeaseInfo {"
+ "package: " + mPackageName + ","
+ "expiryMs: " + mExpiryTimeMillis + ","
+ "descriptionResId: " + mDescriptionResId + ","
+ "description: " + mDescription + ","
+ "}";
}
private String toShortString() {
return mPackageName;
}
static String toShortString(List<LeaseInfo> leaseInfos) {
final StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0, size = leaseInfos.size(); i < size; ++i) {
sb.append(leaseInfos.get(i).toShortString());
sb.append(",");
}
sb.append("]");
return sb.toString();
}
@Override
public int describeContents() {
return 0;
}
@NonNull
public static final Creator<LeaseInfo> CREATOR = new Creator<LeaseInfo>() {
@Override
@NonNull
public LeaseInfo createFromParcel(Parcel source) {
return new LeaseInfo(source);
}
@Override
@NonNull
public LeaseInfo[] newArray(int size) {
return new LeaseInfo[size];
}
};
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 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.app.blob;
/** @hide */
public final class XmlTags {
public static final String ATTR_VERSION = "v";
public static final String TAG_SESSIONS = "ss";
public static final String TAG_BLOBS = "bs";
// For BlobStoreSession
public static final String TAG_SESSION = "s";
public static final String ATTR_ID = "id";
public static final String ATTR_PACKAGE = "p";
public static final String ATTR_UID = "u";
public static final String ATTR_CREATION_TIME_MS = "crt";
// For BlobMetadata
public static final String TAG_BLOB = "b";
public static final String ATTR_USER_ID = "us";
// For BlobAccessMode
public static final String TAG_ACCESS_MODE = "am";
public static final String ATTR_TYPE = "t";
public static final String TAG_WHITELISTED_PACKAGE = "wl";
public static final String ATTR_CERTIFICATE = "ct";
// For BlobHandle
public static final String TAG_BLOB_HANDLE = "bh";
public static final String ATTR_ALGO = "al";
public static final String ATTR_DIGEST = "dg";
public static final String ATTR_LABEL = "lbl";
public static final String ATTR_EXPIRY_TIME = "ex";
public static final String ATTR_TAG = "tg";
// For committer
public static final String TAG_COMMITTER = "c";
public static final String ATTR_COMMIT_TIME_MS = "cmt";
// For leasee
public static final String TAG_LEASEE = "l";
public static final String ATTR_DESCRIPTION_RES_NAME = "rn";
public static final String ATTR_DESCRIPTION = "d";
}

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
java_library {
name: "service-blobstore",
installable: true,
srcs: [
"java/**/*.java",
],
libs: [
"framework",
"services.core",
"services.usage",
],
}

View File

@@ -0,0 +1,221 @@
/*
* Copyright 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.blob;
import static android.app.blob.XmlTags.ATTR_CERTIFICATE;
import static android.app.blob.XmlTags.ATTR_PACKAGE;
import static android.app.blob.XmlTags.ATTR_TYPE;
import static android.app.blob.XmlTags.TAG_WHITELISTED_PACKAGE;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.ArraySet;
import android.util.Base64;
import android.util.DebugUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Objects;
/**
* Class for representing how a blob can be shared.
*
* Note that this class is not thread-safe, callers need to take care of synchronizing access.
*/
class BlobAccessMode {
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
ACCESS_TYPE_PRIVATE,
ACCESS_TYPE_PUBLIC,
ACCESS_TYPE_SAME_SIGNATURE,
ACCESS_TYPE_WHITELIST,
})
@interface AccessType {}
public static final int ACCESS_TYPE_PRIVATE = 1 << 0;
public static final int ACCESS_TYPE_PUBLIC = 1 << 1;
public static final int ACCESS_TYPE_SAME_SIGNATURE = 1 << 2;
public static final int ACCESS_TYPE_WHITELIST = 1 << 3;
private int mAccessType = ACCESS_TYPE_PRIVATE;
private final ArraySet<PackageIdentifier> mWhitelistedPackages = new ArraySet<>();
void allow(BlobAccessMode other) {
if ((other.mAccessType & ACCESS_TYPE_WHITELIST) != 0) {
mWhitelistedPackages.addAll(other.mWhitelistedPackages);
}
mAccessType |= other.mAccessType;
}
void allowPublicAccess() {
mAccessType |= ACCESS_TYPE_PUBLIC;
}
void allowSameSignatureAccess() {
mAccessType |= ACCESS_TYPE_SAME_SIGNATURE;
}
void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) {
mAccessType |= ACCESS_TYPE_WHITELIST;
mWhitelistedPackages.add(PackageIdentifier.create(packageName, certificate));
}
boolean isPublicAccessAllowed() {
return (mAccessType & ACCESS_TYPE_PUBLIC) != 0;
}
boolean isSameSignatureAccessAllowed() {
return (mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0;
}
boolean isPackageAccessAllowed(@NonNull String packageName, @NonNull byte[] certificate) {
if ((mAccessType & ACCESS_TYPE_WHITELIST) == 0) {
return false;
}
return mWhitelistedPackages.contains(PackageIdentifier.create(packageName, certificate));
}
boolean isAccessAllowedForCaller(Context context,
@NonNull String callingPackage, @NonNull String committerPackage) {
if ((mAccessType & ACCESS_TYPE_PUBLIC) != 0) {
return true;
}
final PackageManager pm = context.getPackageManager();
if ((mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0) {
if (pm.checkSignatures(committerPackage, callingPackage)
== PackageManager.SIGNATURE_MATCH) {
return true;
}
}
if ((mAccessType & ACCESS_TYPE_WHITELIST) != 0) {
for (int i = 0; i < mWhitelistedPackages.size(); ++i) {
final PackageIdentifier packageIdentifier = mWhitelistedPackages.valueAt(i);
if (packageIdentifier.packageName.equals(callingPackage)
&& pm.hasSigningCertificate(callingPackage, packageIdentifier.certificate,
PackageManager.CERT_INPUT_SHA256)) {
return true;
}
}
}
return false;
}
int getAccessType() {
return mAccessType;
}
int getNumWhitelistedPackages() {
return mWhitelistedPackages.size();
}
void dump(IndentingPrintWriter fout) {
fout.println("accessType: " + DebugUtils.flagsToString(
BlobAccessMode.class, "ACCESS_TYPE_", mAccessType));
fout.print("Whitelisted pkgs:");
if (mWhitelistedPackages.isEmpty()) {
fout.println(" (Empty)");
} else {
fout.increaseIndent();
for (int i = 0, count = mWhitelistedPackages.size(); i < count; ++i) {
fout.println(mWhitelistedPackages.valueAt(i).toString());
}
fout.decreaseIndent();
}
}
void writeToXml(@NonNull XmlSerializer out) throws IOException {
XmlUtils.writeIntAttribute(out, ATTR_TYPE, mAccessType);
for (int i = 0, count = mWhitelistedPackages.size(); i < count; ++i) {
out.startTag(null, TAG_WHITELISTED_PACKAGE);
final PackageIdentifier packageIdentifier = mWhitelistedPackages.valueAt(i);
XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageIdentifier.packageName);
XmlUtils.writeByteArrayAttribute(out, ATTR_CERTIFICATE, packageIdentifier.certificate);
out.endTag(null, TAG_WHITELISTED_PACKAGE);
}
}
@NonNull
static BlobAccessMode createFromXml(@NonNull XmlPullParser in)
throws IOException, XmlPullParserException {
final BlobAccessMode blobAccessMode = new BlobAccessMode();
final int accessType = XmlUtils.readIntAttribute(in, ATTR_TYPE);
blobAccessMode.mAccessType = accessType;
final int depth = in.getDepth();
while (XmlUtils.nextElementWithin(in, depth)) {
if (TAG_WHITELISTED_PACKAGE.equals(in.getName())) {
final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE);
final byte[] certificate = XmlUtils.readByteArrayAttribute(in, ATTR_CERTIFICATE);
blobAccessMode.allowPackageAccess(packageName, certificate);
}
}
return blobAccessMode;
}
private static final class PackageIdentifier {
public final String packageName;
public final byte[] certificate;
private PackageIdentifier(@NonNull String packageName, @NonNull byte[] certificate) {
this.packageName = packageName;
this.certificate = certificate;
}
public static PackageIdentifier create(@NonNull String packageName,
@NonNull byte[] certificate) {
return new PackageIdentifier(packageName, certificate);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof PackageIdentifier)) {
return false;
}
final PackageIdentifier other = (PackageIdentifier) obj;
return this.packageName.equals(other.packageName)
&& Arrays.equals(this.certificate, other.certificate);
}
@Override
public int hashCode() {
return Objects.hash(packageName, Arrays.hashCode(certificate));
}
@Override
public String toString() {
return "[" + packageName + ", "
+ Base64.encodeToString(certificate, Base64.NO_WRAP) + "]";
}
}
}

View File

@@ -0,0 +1,824 @@
/*
* Copyright 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.blob;
import static android.app.blob.XmlTags.ATTR_COMMIT_TIME_MS;
import static android.app.blob.XmlTags.ATTR_DESCRIPTION;
import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME;
import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME;
import static android.app.blob.XmlTags.ATTR_ID;
import static android.app.blob.XmlTags.ATTR_PACKAGE;
import static android.app.blob.XmlTags.ATTR_UID;
import static android.app.blob.XmlTags.ATTR_USER_ID;
import static android.app.blob.XmlTags.TAG_ACCESS_MODE;
import static android.app.blob.XmlTags.TAG_BLOB_HANDLE;
import static android.app.blob.XmlTags.TAG_COMMITTER;
import static android.app.blob.XmlTags.TAG_LEASEE;
import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.O_RDONLY;
import static android.text.format.Formatter.FLAG_IEC_UNITS;
import static android.text.format.Formatter.formatFileSize;
import static com.android.server.blob.BlobStoreConfig.TAG;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC;
import static com.android.server.blob.BlobStoreConfig.hasLeaseWaitTimeElapsed;
import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
import static com.android.server.blob.BlobStoreUtils.getPackageResources;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.blob.BlobHandle;
import android.app.blob.LeaseInfo;
import android.content.Context;
import android.content.res.ResourceId;
import android.content.res.Resources;
import android.os.ParcelFileDescriptor;
import android.os.RevocableFileDescriptor;
import android.os.UserHandle;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.StatsEvent;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import com.android.server.blob.BlobStoreManagerService.DumpArgs;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Objects;
import java.util.function.Consumer;
class BlobMetadata {
private final Object mMetadataLock = new Object();
private final Context mContext;
private final long mBlobId;
private final BlobHandle mBlobHandle;
private final int mUserId;
@GuardedBy("mMetadataLock")
private final ArraySet<Committer> mCommitters = new ArraySet<>();
@GuardedBy("mMetadataLock")
private final ArraySet<Leasee> mLeasees = new ArraySet<>();
/**
* Contains packageName -> {RevocableFileDescriptors}.
*
* Keep track of RevocableFileDescriptors given to clients which are not yet revoked/closed so
* that when clients access is revoked or the blob gets deleted, we can be sure that clients
* do not have any reference to the blob and the space occupied by the blob can be freed.
*/
@GuardedBy("mRevocableFds")
private final ArrayMap<String, ArraySet<RevocableFileDescriptor>> mRevocableFds =
new ArrayMap<>();
// Do not access this directly, instead use #getBlobFile().
private File mBlobFile;
BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) {
mContext = context;
this.mBlobId = blobId;
this.mBlobHandle = blobHandle;
this.mUserId = userId;
}
long getBlobId() {
return mBlobId;
}
BlobHandle getBlobHandle() {
return mBlobHandle;
}
int getUserId() {
return mUserId;
}
void addOrReplaceCommitter(@NonNull Committer committer) {
synchronized (mMetadataLock) {
// We need to override the committer data, so first remove any existing
// committer before adding the new one.
mCommitters.remove(committer);
mCommitters.add(committer);
}
}
void setCommitters(ArraySet<Committer> committers) {
synchronized (mMetadataLock) {
mCommitters.clear();
mCommitters.addAll(committers);
}
}
void removeCommitter(@NonNull String packageName, int uid) {
synchronized (mMetadataLock) {
mCommitters.removeIf((committer) ->
committer.uid == uid && committer.packageName.equals(packageName));
}
}
void removeCommitter(@NonNull Committer committer) {
synchronized (mMetadataLock) {
mCommitters.remove(committer);
}
}
void removeCommittersFromUnknownPkgs(SparseArray<String> knownPackages) {
synchronized (mMetadataLock) {
mCommitters.removeIf(committer ->
!committer.packageName.equals(knownPackages.get(committer.uid)));
}
}
@Nullable
Committer getExistingCommitter(@NonNull String packageName, int uid) {
synchronized (mCommitters) {
for (int i = 0, size = mCommitters.size(); i < size; ++i) {
final Committer committer = mCommitters.valueAt(i);
if (committer.uid == uid && committer.packageName.equals(packageName)) {
return committer;
}
}
}
return null;
}
void addOrReplaceLeasee(String callingPackage, int callingUid, int descriptionResId,
CharSequence description, long leaseExpiryTimeMillis) {
synchronized (mMetadataLock) {
// We need to override the leasee data, so first remove any existing
// leasee before adding the new one.
final Leasee leasee = new Leasee(mContext, callingPackage, callingUid,
descriptionResId, description, leaseExpiryTimeMillis);
mLeasees.remove(leasee);
mLeasees.add(leasee);
}
}
void setLeasees(ArraySet<Leasee> leasees) {
synchronized (mMetadataLock) {
mLeasees.clear();
mLeasees.addAll(leasees);
}
}
void removeLeasee(String packageName, int uid) {
synchronized (mMetadataLock) {
mLeasees.removeIf((leasee) ->
leasee.uid == uid && leasee.packageName.equals(packageName));
}
}
void removeLeaseesFromUnknownPkgs(SparseArray<String> knownPackages) {
synchronized (mMetadataLock) {
mLeasees.removeIf(leasee ->
!leasee.packageName.equals(knownPackages.get(leasee.uid)));
}
}
void removeExpiredLeases() {
synchronized (mMetadataLock) {
mLeasees.removeIf(leasee -> !leasee.isStillValid());
}
}
boolean hasValidLeases() {
synchronized (mMetadataLock) {
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
if (mLeasees.valueAt(i).isStillValid()) {
return true;
}
}
return false;
}
}
long getSize() {
return getBlobFile().length();
}
boolean isAccessAllowedForCaller(@NonNull String callingPackage, int callingUid) {
// Don't allow the blob to be accessed after it's expiry time has passed.
if (getBlobHandle().isExpired()) {
return false;
}
synchronized (mMetadataLock) {
// Check if packageName already holds a lease on the blob.
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
final Leasee leasee = mLeasees.valueAt(i);
if (leasee.isStillValid() && leasee.equals(callingPackage, callingUid)) {
return true;
}
}
for (int i = 0, size = mCommitters.size(); i < size; ++i) {
final Committer committer = mCommitters.valueAt(i);
// Check if the caller is the same package that committed the blob.
if (committer.equals(callingPackage, callingUid)) {
return true;
}
// Check if the caller is allowed access as per the access mode specified
// by the committer.
if (committer.blobAccessMode.isAccessAllowedForCaller(mContext,
callingPackage, committer.packageName)) {
return true;
}
}
}
return false;
}
boolean isACommitter(@NonNull String packageName, int uid) {
synchronized (mMetadataLock) {
return isAnAccessor(mCommitters, packageName, uid);
}
}
boolean isALeasee(@Nullable String packageName, int uid) {
synchronized (mMetadataLock) {
final Leasee leasee = getAccessor(mLeasees, packageName, uid);
return leasee != null && leasee.isStillValid();
}
}
private static <T extends Accessor> boolean isAnAccessor(@NonNull ArraySet<T> accessors,
@Nullable String packageName, int uid) {
// Check if the package is an accessor of the data blob.
return getAccessor(accessors, packageName, uid) != null;
}
private static <T extends Accessor> T getAccessor(@NonNull ArraySet<T> accessors,
@Nullable String packageName, int uid) {
// Check if the package is an accessor of the data blob.
for (int i = 0, size = accessors.size(); i < size; ++i) {
final Accessor accessor = accessors.valueAt(i);
if (packageName != null && uid != INVALID_UID
&& accessor.equals(packageName, uid)) {
return (T) accessor;
} else if (packageName != null && accessor.packageName.equals(packageName)) {
return (T) accessor;
} else if (uid != INVALID_UID && accessor.uid == uid) {
return (T) accessor;
}
}
return null;
}
boolean isALeasee(@NonNull String packageName) {
return isALeasee(packageName, INVALID_UID);
}
boolean isALeasee(int uid) {
return isALeasee(null, uid);
}
boolean hasOtherLeasees(@NonNull String packageName) {
return hasOtherLeasees(packageName, INVALID_UID);
}
boolean hasOtherLeasees(int uid) {
return hasOtherLeasees(null, uid);
}
private boolean hasOtherLeasees(@Nullable String packageName, int uid) {
synchronized (mMetadataLock) {
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
final Leasee leasee = mLeasees.valueAt(i);
if (!leasee.isStillValid()) {
continue;
}
// TODO: Also exclude packages which are signed with same cert?
if (packageName != null && uid != INVALID_UID
&& !leasee.equals(packageName, uid)) {
return true;
} else if (packageName != null && !leasee.packageName.equals(packageName)) {
return true;
} else if (uid != INVALID_UID && leasee.uid != uid) {
return true;
}
}
}
return false;
}
@Nullable
LeaseInfo getLeaseInfo(@NonNull String packageName, int uid) {
synchronized (mMetadataLock) {
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
final Leasee leasee = mLeasees.valueAt(i);
if (!leasee.isStillValid()) {
continue;
}
if (leasee.uid == uid && leasee.packageName.equals(packageName)) {
final int descriptionResId = leasee.descriptionResEntryName == null
? Resources.ID_NULL
: BlobStoreUtils.getDescriptionResourceId(
mContext, leasee.descriptionResEntryName, leasee.packageName,
UserHandle.getUserId(leasee.uid));
return new LeaseInfo(packageName, leasee.expiryTimeMillis,
descriptionResId, leasee.description);
}
}
}
return null;
}
void forEachLeasee(Consumer<Leasee> consumer) {
synchronized (mMetadataLock) {
mLeasees.forEach(consumer);
}
}
File getBlobFile() {
if (mBlobFile == null) {
mBlobFile = BlobStoreConfig.getBlobFile(mBlobId);
}
return mBlobFile;
}
ParcelFileDescriptor openForRead(String callingPackage) throws IOException {
// TODO: Add limit on opened fds
FileDescriptor fd;
try {
fd = Os.open(getBlobFile().getPath(), O_RDONLY, 0);
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
try {
if (BlobStoreConfig.shouldUseRevocableFdForReads()) {
return createRevocableFd(fd, callingPackage);
} else {
return new ParcelFileDescriptor(fd);
}
} catch (IOException e) {
IoUtils.closeQuietly(fd);
throw e;
}
}
@NonNull
private ParcelFileDescriptor createRevocableFd(FileDescriptor fd,
String callingPackage) throws IOException {
final RevocableFileDescriptor revocableFd =
new RevocableFileDescriptor(mContext, fd);
synchronized (mRevocableFds) {
ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
mRevocableFds.get(callingPackage);
if (revocableFdsForPkg == null) {
revocableFdsForPkg = new ArraySet<>();
mRevocableFds.put(callingPackage, revocableFdsForPkg);
}
revocableFdsForPkg.add(revocableFd);
}
revocableFd.addOnCloseListener((e) -> {
synchronized (mRevocableFds) {
final ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
mRevocableFds.get(callingPackage);
if (revocableFdsForPkg != null) {
revocableFdsForPkg.remove(revocableFd);
if (revocableFdsForPkg.isEmpty()) {
mRevocableFds.remove(callingPackage);
}
}
}
});
return revocableFd.getRevocableFileDescriptor();
}
void destroy() {
revokeAllFds();
getBlobFile().delete();
}
private void revokeAllFds() {
synchronized (mRevocableFds) {
for (int i = 0, pkgCount = mRevocableFds.size(); i < pkgCount; ++i) {
final ArraySet<RevocableFileDescriptor> packageFds =
mRevocableFds.valueAt(i);
if (packageFds == null) {
continue;
}
for (int j = 0, fdCount = packageFds.size(); j < fdCount; ++j) {
packageFds.valueAt(j).revoke();
}
}
}
}
boolean shouldBeDeleted(boolean respectLeaseWaitTime) {
// Expired data blobs
if (getBlobHandle().isExpired()) {
return true;
}
// Blobs with no active leases
if ((!respectLeaseWaitTime || hasLeaseWaitTimeElapsedForAll())
&& !hasValidLeases()) {
return true;
}
return false;
}
@VisibleForTesting
boolean hasLeaseWaitTimeElapsedForAll() {
for (int i = 0, size = mCommitters.size(); i < size; ++i) {
final Committer committer = mCommitters.valueAt(i);
if (!hasLeaseWaitTimeElapsed(committer.getCommitTimeMs())) {
return false;
}
}
return true;
}
StatsEvent dumpAsStatsEvent(int atomTag) {
synchronized (mMetadataLock) {
ProtoOutputStream proto = new ProtoOutputStream();
// Write Committer data to proto format
for (int i = 0, size = mCommitters.size(); i < size; ++i) {
final Committer committer = mCommitters.valueAt(i);
final long token = proto.start(
BlobStatsEventProto.BlobCommitterListProto.COMMITTER);
proto.write(BlobStatsEventProto.BlobCommitterProto.UID, committer.uid);
proto.write(BlobStatsEventProto.BlobCommitterProto.COMMIT_TIMESTAMP_MILLIS,
committer.commitTimeMs);
proto.write(BlobStatsEventProto.BlobCommitterProto.ACCESS_MODE,
committer.blobAccessMode.getAccessType());
proto.write(BlobStatsEventProto.BlobCommitterProto.NUM_WHITELISTED_PACKAGE,
committer.blobAccessMode.getNumWhitelistedPackages());
proto.end(token);
}
final byte[] committersBytes = proto.getBytes();
proto = new ProtoOutputStream();
// Write Leasee data to proto format
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
final Leasee leasee = mLeasees.valueAt(i);
final long token = proto.start(BlobStatsEventProto.BlobLeaseeListProto.LEASEE);
proto.write(BlobStatsEventProto.BlobLeaseeProto.UID, leasee.uid);
proto.write(BlobStatsEventProto.BlobLeaseeProto.LEASE_EXPIRY_TIMESTAMP_MILLIS,
leasee.expiryTimeMillis);
proto.end(token);
}
final byte[] leaseesBytes = proto.getBytes();
// Construct the StatsEvent to represent this Blob
return StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeLong(mBlobId)
.writeLong(getSize())
.writeLong(mBlobHandle.getExpiryTimeMillis())
.writeByteArray(committersBytes)
.writeByteArray(leaseesBytes)
.build();
}
}
void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) {
synchronized (mMetadataLock) {
fout.println("blobHandle:");
fout.increaseIndent();
mBlobHandle.dump(fout, dumpArgs.shouldDumpFull());
fout.decreaseIndent();
fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS));
fout.println("Committers:");
fout.increaseIndent();
if (mCommitters.isEmpty()) {
fout.println("<empty>");
} else {
for (int i = 0, count = mCommitters.size(); i < count; ++i) {
final Committer committer = mCommitters.valueAt(i);
fout.println("committer " + committer.toString());
fout.increaseIndent();
committer.dump(fout);
fout.decreaseIndent();
}
}
fout.decreaseIndent();
fout.println("Leasees:");
fout.increaseIndent();
if (mLeasees.isEmpty()) {
fout.println("<empty>");
} else {
for (int i = 0, count = mLeasees.size(); i < count; ++i) {
final Leasee leasee = mLeasees.valueAt(i);
fout.println("leasee " + leasee.toString());
fout.increaseIndent();
leasee.dump(mContext, fout);
fout.decreaseIndent();
}
}
fout.decreaseIndent();
fout.println("Open fds:");
fout.increaseIndent();
if (mRevocableFds.isEmpty()) {
fout.println("<empty>");
} else {
for (int i = 0, count = mRevocableFds.size(); i < count; ++i) {
final String packageName = mRevocableFds.keyAt(i);
final ArraySet<RevocableFileDescriptor> packageFds =
mRevocableFds.valueAt(i);
fout.println(packageName + "#" + packageFds.size());
}
}
fout.decreaseIndent();
}
}
void writeToXml(XmlSerializer out) throws IOException {
synchronized (mMetadataLock) {
XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId);
XmlUtils.writeIntAttribute(out, ATTR_USER_ID, mUserId);
out.startTag(null, TAG_BLOB_HANDLE);
mBlobHandle.writeToXml(out);
out.endTag(null, TAG_BLOB_HANDLE);
for (int i = 0, count = mCommitters.size(); i < count; ++i) {
out.startTag(null, TAG_COMMITTER);
mCommitters.valueAt(i).writeToXml(out);
out.endTag(null, TAG_COMMITTER);
}
for (int i = 0, count = mLeasees.size(); i < count; ++i) {
out.startTag(null, TAG_LEASEE);
mLeasees.valueAt(i).writeToXml(out);
out.endTag(null, TAG_LEASEE);
}
}
}
@Nullable
static BlobMetadata createFromXml(XmlPullParser in, int version, Context context)
throws XmlPullParserException, IOException {
final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID);
final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID);
BlobHandle blobHandle = null;
final ArraySet<Committer> committers = new ArraySet<>();
final ArraySet<Leasee> leasees = new ArraySet<>();
final int depth = in.getDepth();
while (XmlUtils.nextElementWithin(in, depth)) {
if (TAG_BLOB_HANDLE.equals(in.getName())) {
blobHandle = BlobHandle.createFromXml(in);
} else if (TAG_COMMITTER.equals(in.getName())) {
final Committer committer = Committer.createFromXml(in, version);
if (committer != null) {
committers.add(committer);
}
} else if (TAG_LEASEE.equals(in.getName())) {
leasees.add(Leasee.createFromXml(in, version));
}
}
if (blobHandle == null) {
Slog.wtf(TAG, "blobHandle should be available");
return null;
}
final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle, userId);
blobMetadata.setCommitters(committers);
blobMetadata.setLeasees(leasees);
return blobMetadata;
}
static final class Committer extends Accessor {
public final BlobAccessMode blobAccessMode;
public final long commitTimeMs;
Committer(String packageName, int uid, BlobAccessMode blobAccessMode, long commitTimeMs) {
super(packageName, uid);
this.blobAccessMode = blobAccessMode;
this.commitTimeMs = commitTimeMs;
}
long getCommitTimeMs() {
return commitTimeMs;
}
void dump(IndentingPrintWriter fout) {
fout.println("commit time: "
+ (commitTimeMs == 0 ? "<null>" : BlobStoreUtils.formatTime(commitTimeMs)));
fout.println("accessMode:");
fout.increaseIndent();
blobAccessMode.dump(fout);
fout.decreaseIndent();
}
void writeToXml(@NonNull XmlSerializer out) throws IOException {
XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName);
XmlUtils.writeIntAttribute(out, ATTR_UID, uid);
XmlUtils.writeLongAttribute(out, ATTR_COMMIT_TIME_MS, commitTimeMs);
out.startTag(null, TAG_ACCESS_MODE);
blobAccessMode.writeToXml(out);
out.endTag(null, TAG_ACCESS_MODE);
}
@Nullable
static Committer createFromXml(@NonNull XmlPullParser in, int version)
throws XmlPullParserException, IOException {
final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE);
final int uid = XmlUtils.readIntAttribute(in, ATTR_UID);
final long commitTimeMs = version >= XML_VERSION_ADD_COMMIT_TIME
? XmlUtils.readLongAttribute(in, ATTR_COMMIT_TIME_MS)
: 0;
final int depth = in.getDepth();
BlobAccessMode blobAccessMode = null;
while (XmlUtils.nextElementWithin(in, depth)) {
if (TAG_ACCESS_MODE.equals(in.getName())) {
blobAccessMode = BlobAccessMode.createFromXml(in);
}
}
if (blobAccessMode == null) {
Slog.wtf(TAG, "blobAccessMode should be available");
return null;
}
return new Committer(packageName, uid, blobAccessMode, commitTimeMs);
}
}
static final class Leasee extends Accessor {
public final String descriptionResEntryName;
public final CharSequence description;
public final long expiryTimeMillis;
Leasee(@NonNull Context context, @NonNull String packageName,
int uid, int descriptionResId,
@Nullable CharSequence description, long expiryTimeMillis) {
super(packageName, uid);
final Resources packageResources = getPackageResources(context, packageName,
UserHandle.getUserId(uid));
this.descriptionResEntryName = getResourceEntryName(packageResources, descriptionResId);
this.expiryTimeMillis = expiryTimeMillis;
this.description = description == null
? getDescription(packageResources, descriptionResId)
: description;
}
Leasee(String packageName, int uid, @Nullable String descriptionResEntryName,
@Nullable CharSequence description, long expiryTimeMillis) {
super(packageName, uid);
this.descriptionResEntryName = descriptionResEntryName;
this.expiryTimeMillis = expiryTimeMillis;
this.description = description;
}
@Nullable
private static String getResourceEntryName(@Nullable Resources packageResources,
int resId) {
if (!ResourceId.isValid(resId) || packageResources == null) {
return null;
}
return packageResources.getResourceEntryName(resId);
}
@Nullable
private static String getDescription(@NonNull Context context,
@NonNull String descriptionResEntryName, @NonNull String packageName, int userId) {
if (descriptionResEntryName == null || descriptionResEntryName.isEmpty()) {
return null;
}
final Resources resources = getPackageResources(context, packageName, userId);
if (resources == null) {
return null;
}
final int resId = getDescriptionResourceId(resources, descriptionResEntryName,
packageName);
return resId == Resources.ID_NULL ? null : resources.getString(resId);
}
@Nullable
private static String getDescription(@Nullable Resources packageResources,
int descriptionResId) {
if (!ResourceId.isValid(descriptionResId) || packageResources == null) {
return null;
}
return packageResources.getString(descriptionResId);
}
boolean isStillValid() {
return expiryTimeMillis == 0 || expiryTimeMillis >= System.currentTimeMillis();
}
void dump(@NonNull Context context, @NonNull IndentingPrintWriter fout) {
fout.println("desc: " + getDescriptionToDump(context));
fout.println("expiryMs: " + expiryTimeMillis);
}
@NonNull
private String getDescriptionToDump(@NonNull Context context) {
String desc = getDescription(context, descriptionResEntryName, packageName,
UserHandle.getUserId(uid));
if (desc == null) {
desc = description.toString();
}
return desc == null ? "<none>" : desc;
}
void writeToXml(@NonNull XmlSerializer out) throws IOException {
XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName);
XmlUtils.writeIntAttribute(out, ATTR_UID, uid);
XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION_RES_NAME, descriptionResEntryName);
XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis);
XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION, description);
}
@NonNull
static Leasee createFromXml(@NonNull XmlPullParser in, int version)
throws IOException {
final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE);
final int uid = XmlUtils.readIntAttribute(in, ATTR_UID);
final String descriptionResEntryName;
if (version >= XML_VERSION_ADD_DESC_RES_NAME) {
descriptionResEntryName = XmlUtils.readStringAttribute(
in, ATTR_DESCRIPTION_RES_NAME);
} else {
descriptionResEntryName = null;
}
final long expiryTimeMillis = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME);
final CharSequence description;
if (version >= XML_VERSION_ADD_STRING_DESC) {
description = XmlUtils.readStringAttribute(in, ATTR_DESCRIPTION);
} else {
description = null;
}
return new Leasee(packageName, uid, descriptionResEntryName,
description, expiryTimeMillis);
}
}
static class Accessor {
public final String packageName;
public final int uid;
Accessor(String packageName, int uid) {
this.packageName = packageName;
this.uid = uid;
}
public boolean equals(String packageName, int uid) {
return this.uid == uid && this.packageName.equals(packageName);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof Accessor)) {
return false;
}
final Accessor other = (Accessor) obj;
return this.uid == other.uid && this.packageName.equals(other.packageName);
}
@Override
public int hashCode() {
return Objects.hash(packageName, uid);
}
@Override
public String toString() {
return "[" + packageName + ", " + uid + "]";
}
}
}

View File

@@ -0,0 +1,479 @@
/*
* Copyright 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.blob;
import static android.provider.DeviceConfig.NAMESPACE_BLOBSTORE;
import static android.text.format.Formatter.FLAG_IEC_UNITS;
import static android.text.format.Formatter.formatFileSize;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Environment;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.text.TextUtils;
import android.util.DataUnit;
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
import com.android.internal.util.IndentingPrintWriter;
import java.io.File;
import java.util.concurrent.TimeUnit;
class BlobStoreConfig {
public static final String TAG = "BlobStore";
public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
// Initial version.
public static final int XML_VERSION_INIT = 1;
// Added a string variant of lease description.
public static final int XML_VERSION_ADD_STRING_DESC = 2;
public static final int XML_VERSION_ADD_DESC_RES_NAME = 3;
public static final int XML_VERSION_ADD_COMMIT_TIME = 4;
public static final int XML_VERSION_ADD_SESSION_CREATION_TIME = 5;
public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_SESSION_CREATION_TIME;
public static final long INVALID_BLOB_ID = 0;
public static final long INVALID_BLOB_SIZE = 0;
private static final String ROOT_DIR_NAME = "blobstore";
private static final String BLOBS_DIR_NAME = "blobs";
private static final String SESSIONS_INDEX_FILE_NAME = "sessions_index.xml";
private static final String BLOBS_INDEX_FILE_NAME = "blobs_index.xml";
/**
* Job Id for idle maintenance job ({@link BlobStoreIdleJobService}).
*/
public static final int IDLE_JOB_ID = 0xB70B1D7; // 191934935L
public static class DeviceConfigProperties {
/**
* Denotes the max time period (in millis) between each idle maintenance job run.
*/
public static final String KEY_IDLE_JOB_PERIOD_MS = "idle_job_period_ms";
public static final long DEFAULT_IDLE_JOB_PERIOD_MS = TimeUnit.DAYS.toMillis(1);
public static long IDLE_JOB_PERIOD_MS = DEFAULT_IDLE_JOB_PERIOD_MS;
/**
* Denotes the timeout in millis after which sessions with no updates will be deleted.
*/
public static final String KEY_SESSION_EXPIRY_TIMEOUT_MS =
"session_expiry_timeout_ms";
public static final long DEFAULT_SESSION_EXPIRY_TIMEOUT_MS = TimeUnit.DAYS.toMillis(7);
public static long SESSION_EXPIRY_TIMEOUT_MS = DEFAULT_SESSION_EXPIRY_TIMEOUT_MS;
/**
* Denotes how low the limit for the amount of data, that an app will be allowed to acquire
* a lease on, can be.
*/
public static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
"total_bytes_per_app_limit_floor";
public static final long DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
DataUnit.MEBIBYTES.toBytes(300); // 300 MiB
public static long TOTAL_BYTES_PER_APP_LIMIT_FLOOR =
DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR;
/**
* Denotes the maximum amount of data an app can acquire a lease on, in terms of fraction
* of total disk space.
*/
public static final String KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION =
"total_bytes_per_app_limit_fraction";
public static final float DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION = 0.01f;
public static float TOTAL_BYTES_PER_APP_LIMIT_FRACTION =
DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION;
/**
* Denotes the duration from the time a blob is committed that we wait for a lease to
* be acquired before deciding to delete the blob for having no leases.
*/
public static final String KEY_LEASE_ACQUISITION_WAIT_DURATION_MS =
"lease_acquisition_wait_time_ms";
public static final long DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS =
TimeUnit.HOURS.toMillis(6);
public static long LEASE_ACQUISITION_WAIT_DURATION_MS =
DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS;
/**
* Denotes the duration from the time a blob is committed that any new commits of the same
* data blob from the same committer will be treated as if they occurred at the earlier
* commit time.
*/
public static final String KEY_COMMIT_COOL_OFF_DURATION_MS =
"commit_cool_off_duration_ms";
public static final long DEFAULT_COMMIT_COOL_OFF_DURATION_MS =
TimeUnit.HOURS.toMillis(48);
public static long COMMIT_COOL_OFF_DURATION_MS =
DEFAULT_COMMIT_COOL_OFF_DURATION_MS;
/**
* Denotes whether to use RevocableFileDescriptor when apps try to read session/blob data.
*/
public static final String KEY_USE_REVOCABLE_FD_FOR_READS =
"use_revocable_fd_for_reads";
public static final boolean DEFAULT_USE_REVOCABLE_FD_FOR_READS = true;
public static boolean USE_REVOCABLE_FD_FOR_READS =
DEFAULT_USE_REVOCABLE_FD_FOR_READS;
/**
* Denotes how long before a blob is deleted, once the last lease on it is released.
*/
public static final String KEY_DELETE_ON_LAST_LEASE_DELAY_MS =
"delete_on_last_lease_delay_ms";
public static final long DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS =
TimeUnit.HOURS.toMillis(6);
public static long DELETE_ON_LAST_LEASE_DELAY_MS =
DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS;
/**
* Denotes the maximum number of active sessions per app at any time.
*/
public static final String KEY_MAX_ACTIVE_SESSIONS = "max_active_sessions";
public static int DEFAULT_MAX_ACTIVE_SESSIONS = 250;
public static int MAX_ACTIVE_SESSIONS = DEFAULT_MAX_ACTIVE_SESSIONS;
/**
* Denotes the maximum number of committed blobs per app at any time.
*/
public static final String KEY_MAX_COMMITTED_BLOBS = "max_committed_blobs";
public static int DEFAULT_MAX_COMMITTED_BLOBS = 1000;
public static int MAX_COMMITTED_BLOBS = DEFAULT_MAX_COMMITTED_BLOBS;
/**
* Denotes the maximum number of leased blobs per app at any time.
*/
public static final String KEY_MAX_LEASED_BLOBS = "max_leased_blobs";
public static int DEFAULT_MAX_LEASED_BLOBS = 500;
public static int MAX_LEASED_BLOBS = DEFAULT_MAX_LEASED_BLOBS;
/**
* Denotes the maximum number of packages explicitly permitted to access a blob
* (permitted as part of creating a {@link BlobAccessMode}).
*/
public static final String KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = "max_permitted_pks";
public static int DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = 300;
public static int MAX_BLOB_ACCESS_PERMITTED_PACKAGES =
DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES;
/**
* Denotes the maximum number of characters that a lease description can have.
*/
public static final String KEY_LEASE_DESC_CHAR_LIMIT = "lease_desc_char_limit";
public static int DEFAULT_LEASE_DESC_CHAR_LIMIT = 300;
public static int LEASE_DESC_CHAR_LIMIT = DEFAULT_LEASE_DESC_CHAR_LIMIT;
static void refresh(Properties properties) {
if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) {
return;
}
properties.getKeyset().forEach(key -> {
switch (key) {
case KEY_IDLE_JOB_PERIOD_MS:
IDLE_JOB_PERIOD_MS = properties.getLong(key, DEFAULT_IDLE_JOB_PERIOD_MS);
break;
case KEY_SESSION_EXPIRY_TIMEOUT_MS:
SESSION_EXPIRY_TIMEOUT_MS = properties.getLong(key,
DEFAULT_SESSION_EXPIRY_TIMEOUT_MS);
break;
case KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR:
TOTAL_BYTES_PER_APP_LIMIT_FLOOR = properties.getLong(key,
DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR);
break;
case KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION:
TOTAL_BYTES_PER_APP_LIMIT_FRACTION = properties.getFloat(key,
DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION);
break;
case KEY_LEASE_ACQUISITION_WAIT_DURATION_MS:
LEASE_ACQUISITION_WAIT_DURATION_MS = properties.getLong(key,
DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS);
break;
case KEY_COMMIT_COOL_OFF_DURATION_MS:
COMMIT_COOL_OFF_DURATION_MS = properties.getLong(key,
DEFAULT_COMMIT_COOL_OFF_DURATION_MS);
break;
case KEY_USE_REVOCABLE_FD_FOR_READS:
USE_REVOCABLE_FD_FOR_READS = properties.getBoolean(key,
DEFAULT_USE_REVOCABLE_FD_FOR_READS);
break;
case KEY_DELETE_ON_LAST_LEASE_DELAY_MS:
DELETE_ON_LAST_LEASE_DELAY_MS = properties.getLong(key,
DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS);
break;
case KEY_MAX_ACTIVE_SESSIONS:
MAX_ACTIVE_SESSIONS = properties.getInt(key, DEFAULT_MAX_ACTIVE_SESSIONS);
break;
case KEY_MAX_COMMITTED_BLOBS:
MAX_COMMITTED_BLOBS = properties.getInt(key, DEFAULT_MAX_COMMITTED_BLOBS);
break;
case KEY_MAX_LEASED_BLOBS:
MAX_LEASED_BLOBS = properties.getInt(key, DEFAULT_MAX_LEASED_BLOBS);
break;
case KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES:
MAX_BLOB_ACCESS_PERMITTED_PACKAGES = properties.getInt(key,
DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES);
break;
case KEY_LEASE_DESC_CHAR_LIMIT:
LEASE_DESC_CHAR_LIMIT = properties.getInt(key,
DEFAULT_LEASE_DESC_CHAR_LIMIT);
break;
default:
Slog.wtf(TAG, "Unknown key in device config properties: " + key);
}
});
}
static void dump(IndentingPrintWriter fout, Context context) {
final String dumpFormat = "%s: [cur: %s, def: %s]";
fout.println(String.format(dumpFormat, KEY_IDLE_JOB_PERIOD_MS,
TimeUtils.formatDuration(IDLE_JOB_PERIOD_MS),
TimeUtils.formatDuration(DEFAULT_IDLE_JOB_PERIOD_MS)));
fout.println(String.format(dumpFormat, KEY_SESSION_EXPIRY_TIMEOUT_MS,
TimeUtils.formatDuration(SESSION_EXPIRY_TIMEOUT_MS),
TimeUtils.formatDuration(DEFAULT_SESSION_EXPIRY_TIMEOUT_MS)));
fout.println(String.format(dumpFormat, KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
formatFileSize(context, TOTAL_BYTES_PER_APP_LIMIT_FLOOR, FLAG_IEC_UNITS),
formatFileSize(context, DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
FLAG_IEC_UNITS)));
fout.println(String.format(dumpFormat, KEY_TOTAL_BYTES_PER_APP_LIMIT_FRACTION,
TOTAL_BYTES_PER_APP_LIMIT_FRACTION,
DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FRACTION));
fout.println(String.format(dumpFormat, KEY_LEASE_ACQUISITION_WAIT_DURATION_MS,
TimeUtils.formatDuration(LEASE_ACQUISITION_WAIT_DURATION_MS),
TimeUtils.formatDuration(DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS)));
fout.println(String.format(dumpFormat, KEY_COMMIT_COOL_OFF_DURATION_MS,
TimeUtils.formatDuration(COMMIT_COOL_OFF_DURATION_MS),
TimeUtils.formatDuration(DEFAULT_COMMIT_COOL_OFF_DURATION_MS)));
fout.println(String.format(dumpFormat, KEY_USE_REVOCABLE_FD_FOR_READS,
USE_REVOCABLE_FD_FOR_READS, DEFAULT_USE_REVOCABLE_FD_FOR_READS));
fout.println(String.format(dumpFormat, KEY_DELETE_ON_LAST_LEASE_DELAY_MS,
TimeUtils.formatDuration(DELETE_ON_LAST_LEASE_DELAY_MS),
TimeUtils.formatDuration(DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS)));
fout.println(String.format(dumpFormat, KEY_MAX_ACTIVE_SESSIONS,
MAX_ACTIVE_SESSIONS, DEFAULT_MAX_ACTIVE_SESSIONS));
fout.println(String.format(dumpFormat, KEY_MAX_COMMITTED_BLOBS,
MAX_COMMITTED_BLOBS, DEFAULT_MAX_COMMITTED_BLOBS));
fout.println(String.format(dumpFormat, KEY_MAX_LEASED_BLOBS,
MAX_LEASED_BLOBS, DEFAULT_MAX_LEASED_BLOBS));
fout.println(String.format(dumpFormat, KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES,
MAX_BLOB_ACCESS_PERMITTED_PACKAGES,
DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES));
fout.println(String.format(dumpFormat, KEY_LEASE_DESC_CHAR_LIMIT,
LEASE_DESC_CHAR_LIMIT, DEFAULT_LEASE_DESC_CHAR_LIMIT));
}
}
public static void initialize(Context context) {
DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_BLOBSTORE,
context.getMainExecutor(),
properties -> DeviceConfigProperties.refresh(properties));
DeviceConfigProperties.refresh(DeviceConfig.getProperties(NAMESPACE_BLOBSTORE));
}
/**
* Returns the max time period (in millis) between each idle maintenance job run.
*/
public static long getIdleJobPeriodMs() {
return DeviceConfigProperties.IDLE_JOB_PERIOD_MS;
}
/**
* Returns whether a session is expired or not. A session is considered expired if the session
* has not been modified in a while (i.e. SESSION_EXPIRY_TIMEOUT_MS).
*/
public static boolean hasSessionExpired(long sessionLastModifiedMs) {
return sessionLastModifiedMs
< System.currentTimeMillis() - DeviceConfigProperties.SESSION_EXPIRY_TIMEOUT_MS;
}
/**
* Returns the maximum amount of data that an app can acquire a lease on.
*/
public static long getAppDataBytesLimit() {
final long totalBytesLimit = (long) (Environment.getDataSystemDirectory().getTotalSpace()
* DeviceConfigProperties.TOTAL_BYTES_PER_APP_LIMIT_FRACTION);
return Math.max(DeviceConfigProperties.TOTAL_BYTES_PER_APP_LIMIT_FLOOR, totalBytesLimit);
}
/**
* Returns whether the wait time for lease acquisition for a blob has elapsed.
*/
public static boolean hasLeaseWaitTimeElapsed(long commitTimeMs) {
return commitTimeMs + DeviceConfigProperties.LEASE_ACQUISITION_WAIT_DURATION_MS
< System.currentTimeMillis();
}
/**
* Returns an adjusted commit time depending on whether commit cool-off period has elapsed.
*
* If this is the initial commit or the earlier commit cool-off period has elapsed, then
* the new commit time is used. Otherwise, the earlier commit time is used.
*/
public static long getAdjustedCommitTimeMs(long oldCommitTimeMs, long newCommitTimeMs) {
if (oldCommitTimeMs == 0 || hasCommitCoolOffPeriodElapsed(oldCommitTimeMs)) {
return newCommitTimeMs;
}
return oldCommitTimeMs;
}
/**
* Returns whether the commit cool-off period has elapsed.
*/
private static boolean hasCommitCoolOffPeriodElapsed(long commitTimeMs) {
return commitTimeMs + DeviceConfigProperties.COMMIT_COOL_OFF_DURATION_MS
< System.currentTimeMillis();
}
/**
* Return whether to use RevocableFileDescriptor when apps try to read session/blob data.
*/
public static boolean shouldUseRevocableFdForReads() {
return DeviceConfigProperties.USE_REVOCABLE_FD_FOR_READS;
}
/**
* Returns the duration to wait before a blob is deleted, once the last lease on it is released.
*/
public static long getDeletionOnLastLeaseDelayMs() {
return DeviceConfigProperties.DELETE_ON_LAST_LEASE_DELAY_MS;
}
/**
* Returns the maximum number of active sessions per app.
*/
public static int getMaxActiveSessions() {
return DeviceConfigProperties.MAX_ACTIVE_SESSIONS;
}
/**
* Returns the maximum number of committed blobs per app.
*/
public static int getMaxCommittedBlobs() {
return DeviceConfigProperties.MAX_COMMITTED_BLOBS;
}
/**
* Returns the maximum number of leased blobs per app.
*/
public static int getMaxLeasedBlobs() {
return DeviceConfigProperties.MAX_LEASED_BLOBS;
}
/**
* Returns the maximum number of packages explicitly permitted to access a blob.
*/
public static int getMaxPermittedPackages() {
return DeviceConfigProperties.MAX_BLOB_ACCESS_PERMITTED_PACKAGES;
}
/**
* Returns the lease description truncated to
* {@link DeviceConfigProperties#LEASE_DESC_CHAR_LIMIT} characters.
*/
public static CharSequence getTruncatedLeaseDescription(CharSequence description) {
if (TextUtils.isEmpty(description)) {
return description;
}
return TextUtils.trimToLengthWithEllipsis(description,
DeviceConfigProperties.LEASE_DESC_CHAR_LIMIT);
}
@Nullable
public static File prepareBlobFile(long sessionId) {
final File blobsDir = prepareBlobsDir();
return blobsDir == null ? null : getBlobFile(blobsDir, sessionId);
}
@NonNull
public static File getBlobFile(long sessionId) {
return getBlobFile(getBlobsDir(), sessionId);
}
@NonNull
private static File getBlobFile(File blobsDir, long sessionId) {
return new File(blobsDir, String.valueOf(sessionId));
}
@Nullable
public static File prepareBlobsDir() {
final File blobsDir = getBlobsDir(prepareBlobStoreRootDir());
if (!blobsDir.exists() && !blobsDir.mkdir()) {
Slog.e(TAG, "Failed to mkdir(): " + blobsDir);
return null;
}
return blobsDir;
}
@NonNull
public static File getBlobsDir() {
return getBlobsDir(getBlobStoreRootDir());
}
@NonNull
private static File getBlobsDir(File blobsRootDir) {
return new File(blobsRootDir, BLOBS_DIR_NAME);
}
@Nullable
public static File prepareSessionIndexFile() {
final File blobStoreRootDir = prepareBlobStoreRootDir();
if (blobStoreRootDir == null) {
return null;
}
return new File(blobStoreRootDir, SESSIONS_INDEX_FILE_NAME);
}
@Nullable
public static File prepareBlobsIndexFile() {
final File blobsStoreRootDir = prepareBlobStoreRootDir();
if (blobsStoreRootDir == null) {
return null;
}
return new File(blobsStoreRootDir, BLOBS_INDEX_FILE_NAME);
}
@Nullable
public static File prepareBlobStoreRootDir() {
final File blobStoreRootDir = getBlobStoreRootDir();
if (!blobStoreRootDir.exists() && !blobStoreRootDir.mkdir()) {
Slog.e(TAG, "Failed to mkdir(): " + blobStoreRootDir);
return null;
}
return blobStoreRootDir;
}
@NonNull
public static File getBlobStoreRootDir() {
return new File(Environment.getDataSystemDirectory(), ROOT_DIR_NAME);
}
public static void dump(IndentingPrintWriter fout, Context context) {
fout.println("XML current version: " + XML_VERSION_CURRENT);
fout.println("Idle job ID: " + IDLE_JOB_ID);
fout.println("Total bytes per app limit: " + formatFileSize(context,
getAppDataBytesLimit(), FLAG_IEC_UNITS));
fout.println("Device config properties:");
fout.increaseIndent();
DeviceConfigProperties.dump(fout, context);
fout.decreaseIndent();
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 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.blob;
import static com.android.server.blob.BlobStoreConfig.IDLE_JOB_ID;
import static com.android.server.blob.BlobStoreConfig.LOGV;
import static com.android.server.blob.BlobStoreConfig.TAG;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Slog;
import com.android.server.LocalServices;
/**
* Maintenance job to clean up stale sessions and blobs.
*/
public class BlobStoreIdleJobService extends JobService {
@Override
public boolean onStartJob(final JobParameters params) {
AsyncTask.execute(() -> {
final BlobStoreManagerInternal blobStoreManagerInternal = LocalServices.getService(
BlobStoreManagerInternal.class);
blobStoreManagerInternal.onIdleMaintenance();
jobFinished(params, false);
});
return false;
}
@Override
public boolean onStopJob(final JobParameters params) {
Slog.d(TAG, "Idle maintenance job is stopped; id=" + params.getJobId()
+ ", reason=" + JobParameters.getReasonCodeDescription(params.getStopReason()));
return false;
}
static void schedule(Context context) {
final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(
Context.JOB_SCHEDULER_SERVICE);
final JobInfo job = new JobInfo.Builder(IDLE_JOB_ID,
new ComponentName(context, BlobStoreIdleJobService.class))
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.setPeriodic(BlobStoreConfig.getIdleJobPeriodMs())
.build();
jobScheduler.schedule(job);
if (LOGV) {
Slog.v(TAG, "Scheduling the idle maintenance job");
}
}
}

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